runtime: decouple Open and Launch commands and gx mapping from netrw

closes: #16494
fixes: ##16486

Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/runtime/autoload/dist/vim9.vim b/runtime/autoload/dist/vim9.vim
index 2277f24..a2b1451 100644
--- a/runtime/autoload/dist/vim9.vim
+++ b/runtime/autoload/dist/vim9.vim
@@ -2,19 +2,128 @@
 
 # Vim runtime support library
 #
-# Maintainer:	The Vim Project <https://github.com/vim/vim>
-# Last Change:	2023 Oct 25
+# Maintainer:   The Vim Project <https://github.com/vim/vim>
+# Last Change:  2025 Jan 24
 
 export def IsSafeExecutable(filetype: string, executable: string): bool
-    if empty(exepath(executable))
-      return v:false
+  if empty(exepath(executable))
+    return v:false
+  endif
+  var cwd = getcwd()
+  return get(g:, filetype .. '_exec', get(g:, 'plugin_exec', 0))
+    && (fnamemodify(exepath(executable), ':p:h') !=# cwd
+      || (split($PATH, has('win32') ? ';' : ':')->index(cwd) != -1
+        && cwd != '.'))
+enddef
+
+def Redir(): string
+  if get(g:, 'netrw_suppress_gx_mesg', true)
+    if &srr =~# "%s"
+      return printf(&srr, has("win32") ? "nul" : "/dev/null")
+    elseif &srr =~# '>&\?$'
+      return &srr .. (has("win32") ? "nul" : "/dev/null")
+    else
+      return &srr .. (has("win32") ? "> nul" : "> /dev/null")
     endif
-    var cwd = getcwd()
-    return get(g:, filetype .. '_exec', get(g:, 'plugin_exec', 0))
-      && (fnamemodify(exepath(executable), ':p:h') !=# cwd
-          || (split($PATH, has('win32') ? ';' : ':')->index(cwd) != -1
-              && cwd != '.'))
+  endif
+  return ''
+enddef
+
+if has('unix')
+  if has('win32unix')
+    # Cygwin provides cygstart
+    if executable('cygstart')
+      export def Launch(args: string)
+        execute 'silent ! cygstart --hide' args Redir() | redraw!
+      enddef
+    elseif !empty($MSYSTEM) && executable('start')
+      # MSYS2/Git Bash comes by default without cygstart; see
+      # https://www.msys2.org/wiki/How-does-MSYS2-differ-from-Cygwin
+      # Instead it provides /usr/bin/start script running `cmd.exe //c start`
+      # Adding "" //b` sets void title, hides cmd window and blocks path conversion
+      # of /b to \b\ " by MSYS2; see https://www.msys2.org/docs/filesystem-paths/
+      export def Launch(args: string)
+        execute 'silent !start "" //b' args Redir() | redraw!
+      enddef
+    else
+      # imitate /usr/bin/start script for other environments and hope for the best
+      export def Launch(args: string)
+        execute 'silent !cmd //c start "" //b' args Redir() | redraw!
+      enddef
+    endif
+  elseif exists('$WSL_DISTRO_NAME') # use cmd.exe to start GUI apps in WSL
+    export def Launch(args: string)
+      execute 'silent !' ..
+            ((args =~? '\v<\f+\.(exe|com|bat|cmd)>') ?
+            $'cmd.exe /c start /b {args} {Redir()}' :
+            $'nohup {args} {Redir()} &')
+            | redraw!
+    enddef
+  else
+    export def Launch(args: string)
+      execute ':silent ! nohup' args Redir() (has('gui_running') ? '' : '&') | redraw!
+    enddef
+  endif
+elseif has('win32')
+  export def Launch(args: string)
+    execute 'silent !' .. (&shell =~? '\<cmd\.exe\>' ? '' : 'cmd.exe /c')
+          'start "" /b' args Redir() | redraw!
+  enddef
+else
+  export def Launch(dummy: string)
+    echom 'No common launcher found'
+  enddef
+endif
+
+var os_viewer = null_string
+# Git Bash
+if has('win32unix')
+  # (cyg)start suffices
+  os_viewer = ''
+# Windows / WSL
+elseif executable('explorer.exe')
+  os_viewer = 'explorer.exe'
+# Linux / BSD
+elseif executable('xdg-open')
+  os_viewer = 'xdg-open'
+# MacOS
+elseif executable('open')
+  os_viewer = 'open'
+endif
+
+def Viewer(): string
+  # g:Openprg could be a string of program + its arguments, test if first
+  # argument is executable
+  var user_viewer = get(g:, "Openprg", get(g:, "netrw_browsex_viewer", ""))
+
+  # Take care of an off-by-one check for "for" too
+  if executable(trim(user_viewer))
+    return user_viewer
+  endif
+
+  var args = split(user_viewer, '\s\+\zs')
+  var viewer = get(args, 0, '')
+
+  for arg in args[1 :]
+    if executable(trim(viewer))
+      return user_viewer
+    endif
+
+    viewer ..= arg
+  endfor
+
+  if os_viewer == null
+    echoerr "No program to open this path found. See :help Open for more information."
+  endif
+
+  return os_viewer
+enddef
+
+export def Open(file: string)
+  Launch($"{Viewer()} {shellescape(file, 1)}")
 enddef
 
 # Uncomment this line to check for compilation errors early
 # defcompile
+
+# vim: ts=8 sts=2 sw=2 et