Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Firenvim work with a Windows browser and WSL neovim #839

Closed
MuizU opened this issue Dec 21, 2020 · 11 comments
Closed

Make Firenvim work with a Windows browser and WSL neovim #839

MuizU opened this issue Dec 21, 2020 · 11 comments
Labels
os:windows This issue only happens on windows

Comments

@MuizU
Copy link

MuizU commented Dec 21, 2020

I am unable to get this to work with WSL, is there a workaround or is there no support

@glacambre
Copy link
Owner

For firenvim to work in WSL, you would need to run your browser in WSL. If you want to use Firenvim on a windows machine, you need to install neovim on windows. :)

@MuizU
Copy link
Author

MuizU commented Dec 21, 2020

For firenvim to work in WSL, you would need to run your browser in WSL. If you want to use Firenvim on a windows machine, you need to install neovim on windows. :)

If I symlink the brave folder from Program Files to wsl home will that work?

@glacambre
Copy link
Owner

No, it won't, windows and WSL are separate and do not run the same programs.

@alerque
Copy link
Contributor

alerque commented Dec 21, 2020

WSL is a virtualization layer that isolates processes from other layers. If you want processes to play together they have to be running in the same system. This is no different that almost any other virtualization or containerization system (VMWare, Vbox, Xen, Docker, Podman, etc.). You can interact through APIs provided by the hypervisor/virtualization/container system, but not directly with processes inside other layers.

@glacambre
Copy link
Owner

I'm closing this but feel free to ask more questions if you still don't understand what's going on/can't get firenvim to work on windows :)

@hwine
Copy link

hwine commented Dec 31, 2020

No, it won't, windows and WSL are separate and do not run the same programs.

While that is true, there is a high degree of integration between wsl & windows, so it doesn't really matter if you're communicating with neovim over shared abstractions (e.g. tcp). While wsl is a traditional VM, windows & wsl do share the same host (i.e. network ports), & filesystems are accessible from either environment. (You can start apps in either environment from either system -- the app will run in the correct environment -- think "shebang processing on steroids".) Unless you're using os specific RPC, it may "just work".

A working example is nvim (on wsl) embedded in vscode (on windows), using the VSCode Neo Vim extension.

[NB: I'm not advocating to reopen, just sharing info.]

@glacambre
Copy link
Owner

@hwine Interesting, thanks for the info. For Firenvim to work with a WSL neovim and a Windows browser, we'd need to be able to do the following things:

  • Edit Windows' registry from WSL
  • Write to the disk in a location available to both windows and wsl
  • Share stdin and stdout across this boundary

Is all of this possible with WSL?

@hwine
Copy link

hwine commented Dec 31, 2020

@hwine Interesting, thanks for the info. For Firenvim to work with a WSL neovim and a Windows browser, we'd need to be able to do the following things:

I'm just a wsl & windows user, so there may be edge cases I'm missing

  • Edit Windows' registry from WSL

You can run regedit for sure -- I doubt there are "wsl native" methods, but I'm not a windows coder.

  • Write to the disk in a location available to both windows and wsl

Yes. Very easy if you can support UNC paths, as the default wsl volume is mapped to \\wsl$\. Otherwise, a drive mapping may need to be established as part of the extension installation. Note that one "side" will be reading/writing a foreign file system. That happens automatically, but there could be a performance difference based on which side is using "native".

  • Share stdin and stdout across this boundary

Yes, this works, although I don't know if there are buffering issues.

Is all of this possible with WSL?

To be specific, it works on wsl2 -- latest GA versions of both wsl and win10. I have no idea if anyone still runs the original wsl -- there were significant disk i/o improvements with wsl2.

If you have specific tests you'd like me to run, Win10/wsl2 is my daily setup, so I could run them easily.

@glacambre
Copy link
Owner

@hwine Great, thanks a lot for your answers! I didn't know much about wsl, except for the architectural differences between wsl and wsl2 (running syscalls directly vs running linux in a VM) which made me assume that shared stdin/out wouldn't be possible.

For Firenvim to work with a wsl neovim, I think we only need to update the firenvim installation script like this:

  • Get s:get_executable_script to return the shell script under wsl2 (maybe already happening?).
  • In firenvim#install, detect wsl2 and write the shell script to a location available to both wsl2 and windows.
  • In firenvim#install, detect wsl2 and write the firenvim browser manifest to a path available to both windows and wsl.
  • In firenvim#install, detect wsl2 and create registry keys just like on windows.
  • Also update the various s:BROWSER_config_exists functions to detect browsers installed on windows.

After that, Firenvim will probably just work. I don't have time to work on this at the moment (I'm currently working on #805 which is much harder than expected), so those who'd like to see wsl supported in the near future should work on it themselves and open a PR - I'll be very happy to review it and provide help/advice should this be necessary :).

@glacambre glacambre reopened this Jan 1, 2021
@glacambre glacambre changed the title Doesn't work in WSL Make Firenvim work with a Windows browser and WSL neovim Jan 1, 2021
@glacambre glacambre added the os:windows This issue only happens on windows label Jan 8, 2021
@glacambre
Copy link
Owner

The following patch makes firenvim work on a windows firefox+wsl2 firenvim combination. I have yet to test other browsers/make sure that I haven't broken other types of setup. I'm uploading it here as I can't push from a windows machine.

diff --git a/autoload/firenvim.vim b/autoload/firenvim.vim
index 1dff1aa..9b2db99 100644
--- a/autoload/firenvim.vim
+++ b/autoload/firenvim.vim
@@ -57,15 +57,61 @@ function! firenvim#press_keys(...) abort
         call rpcnotify(firenvim#get_chan(), 'firenvim_press_keys', l:keys)
 endfunction
 
+let s:is_wsl = !empty($WSLENV) || !empty($WSL_DISTRO_NAME) || !empty ($WSL_INTEROP) 
+
+" Turns a wsl path (forward slashes) into a windows one (backslashes)
+function! s:to_windows_path(path) abort
+        if a:path[0] != '/'
+                return a:path
+        endif
+        let l:path_components = split(a:path, '/')
+        return join([toupper(l:path_components[1]) . ':'] + path_components[2:-1], '\')
+endfunction
+
+" Turns a windows path (backslashes) into a wsl one (forward slashes)
+function! s:to_wsl_path(path) abort
+        if a:path[0] == '/'
+                return a:path
+        endif
+        let l:path_components = split(a:path, '\\')
+        return join(['/mnt', tolower(path_components[0][0:-2])] + l:path_components[1:-1], '/')
+endfunction
+
+
 " Simple helper to build the right path depending on the platform.
 function! s:build_path(list) abort
         let l:path_separator = '/'
         if has('win32')
                 let l:path_separator = "\\"
         endif
+        if s:is_wsl
+                let a:list[0] = s:to_wsl_path(a:list[0])
+        endif
         return join(a:list, l:path_separator)
 endfunction
 
+" Retrieves a windows env var from wsl. Retrieves a windows path (with
+" backslashes!)
+function! s:get_windows_env_path(env) abort
+        if has('win32')
+                let l:env = a:env
+                if l:env[0] == '%'
+                        let l:env = '$' . l:env[1:-2]
+                endif
+                return expand(l:env)
+        endif
+        if s:is_wsl
+                let l:env = a:env
+                if l:env[0] == '$'
+                        let l:env = '%' . l:env[1:-1] . '%'
+                endif
+                let l:cmd_output = system(['cmd.exe', '/c', 'echo', l:env])
+                return cmd_output[match(l:cmd_output, 'C:\\'):-3]
+        endif
+        throw "Used get_windows_env_path on non-windows platform!"
+endfunction
+
+
 " Entry point of the vim-side of the extension.
 " This function does the following things:
 " - Get a security token from neovim's stdin
@@ -124,23 +170,27 @@ function! firenvim#run() abort
         let l:chanid = stdioopen({ 'on_stdin': 'OnStdin' })
 endfunction
 
+" Returns the name of the script that should be executed by the browser.
 function! s:get_executable_name() abort
-        if has('win32')
+        if has('win32') || s:is_wsl
                 return 'firenvim.bat'
         endif
         return 'firenvim'
 endfunction
 
+" Returns the path of the directory in which firenvim will run when the
+" browser launches it.
+" On wsl, this is a path living on the linux side.
 function! s:get_runtime_dir_path() abort
         let l:xdg_runtime_dir = $XDG_RUNTIME_DIR
         if l:xdg_runtime_dir ==# ''
-                if has('win32')
-                        let l:xdg_runtime_dir = expand('$TEMP')
+                if has('win32') || s:is_wsl
+                        let l:xdg_runtime_dir = s:get_windows_env_path('$TEMP')
                         if l:xdg_runtime_dir ==# ''
-                                let l:xdg_runtime_dir = expand('$TMP')
+                                let l:xdg_runtime_dir = s:get_windows_env_path('$TMP')
                         endif
                         if l:xdg_runtime_dir ==# ''
-                                let l:xdg_runtime_dir = expand('$USERPROFILE')
+                                let l:xdg_runtime_dir = s:get_windows_env_path('$USERPROFILE')
                                 if l:xdg_runtime_dir ==# ''
                                         let l:xdg_runtime_dir = fnamemodify(stdpath('data'), ':h')
                                 else
@@ -157,9 +207,12 @@ function! s:get_runtime_dir_path() abort
         return s:build_path([l:xdg_runtime_dir, 'firenvim'])
 endfunction
 
+" Returns the directory in which the firenvim script is written.
 function! s:get_data_dir_path() abort
         let l:xdg_data_home = $XDG_DATA_HOME
-        if l:xdg_data_home ==# ''
+        if s:is_wsl
+                let l:xdg_data_home = s:get_windows_env_path('%LOCALAPPDATA%')
+        elseif l:xdg_data_home ==# ''
                 let l:xdg_data_home = fnamemodify(stdpath('data'), ':h')
         endif
         return s:build_path([l:xdg_data_home, 'firenvim'])
@@ -171,14 +224,17 @@ function! s:firefox_config_exists() abort
                 let l:p = [$HOME, 'Library', 'Application Support', 'Mozilla']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Roaming', 'Mozilla', 'Firefox']
-        end
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%APPDATA%'), 'Mozilla', 'Firefox']
+        endif
+        echo l:p
         return isdirectory(s:build_path(l:p))
 endfunction
 
 function! s:get_firefox_manifest_dir_path() abort
         if has('mac')
                 return s:build_path([$HOME, 'Library', 'Application Support', 'Mozilla', 'NativeMessagingHosts'])
-        elseif has('win32')
+        elseif has('win32') || s:is_wsl
                 return s:get_data_dir_path()
         end
         return s:build_path([$HOME, '.mozilla', 'native-messaging-hosts'])
@@ -190,6 +246,8 @@ function! s:brave_config_exists() abort
                 let l:p = [$HOME, 'Library', 'Application Support', 'BraveSoftware']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Local', 'BraveSoftware']
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%LOCALAPPDATA%'), 'BraveSoftware']
         elseif !empty($XDG_CONFIG_HOME)
                 let l:p = [$XDG_CONFIG_HOME, 'BraveSoftware']
         end
@@ -202,6 +260,8 @@ function! s:opera_config_exists() abort
                 let l:p = [$HOME, 'Library', 'Application Support', 'com.operasoftware.Opera']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Local', 'Opera Software']
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%LOCALAPPDATA%'), 'Opera Software']
         elseif !empty($XDG_CONFIG_HOME)
                 let l:p = [$XDG_CONFIG_HOME, 'opera']
         end
@@ -214,6 +274,8 @@ function! s:vivaldi_config_exists() abort
                 let l:p = [$HOME, 'Library', 'Application Support', 'Vivaldi']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Local', 'Vivaldi']
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%LOCALAPPDATA%'), 'Vivaldi']
         elseif !empty($XDG_CONFIG_HOME)
                 let l:p = [$XDG_CONFIG_HOME, 'vivaldi']
         end
@@ -226,6 +288,8 @@ function! s:chrome_config_exists() abort
                 let l:p = [$HOME, 'Library', 'Application Support', 'Google', 'Chrome']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Local', 'Google', 'Chrome']
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%LOCALAPPDATA%'), 'Google', 'Chrome']
         elseif !empty($XDG_CONFIG_HOME)
                 let l:p = [$XDG_CONFIG_HOME, 'google-chrome']
         end
@@ -243,7 +307,7 @@ endfunction
 function! s:get_chrome_manifest_dir_path() abort
         if has('mac')
                 return s:build_path([$HOME, 'Library', 'Application Support', 'Google', 'Chrome', 'NativeMessagingHosts'])
-        elseif has('win32')
+        elseif has('win32') || s:is_wsl
                 return s:get_data_dir_path()
         end
         if !empty($XDG_CONFIG_HOME)
@@ -255,7 +319,7 @@ endfunction
 function! s:get_chrome_dev_manifest_dir_path() abort
         if has('mac')
                 throw 'No chrome dev on mac.'
-        elseif has('win32')
+        elseif has('win32') || s:is_wsl
                 throw 'No chrome dev on win32.'
         end
         if !empty($XDG_CONFIG_HOME)
@@ -267,7 +331,7 @@ endfunction
 function! s:get_brave_manifest_dir_path() abort
         if has('mac')
                 return s:get_chrome_manifest_dir_path()
-        elseif has('win32')
+        elseif has('win32') || s:is_wsl
                 return s:get_chrome_manifest_dir_path()
         end
         if !empty($XDG_CONFIG_HOME)
@@ -281,6 +345,8 @@ function! s:canary_config_exists() abort
                 let l:p = [$HOME, 'Library', 'Application Support', 'Google', 'Chrome Canary']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Local', 'Google', 'Chrome SxS']
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%LOCALAPPDATA%'), 'Google', 'Chrome SxS']
         else
                 " Chrome canary doesn't exist on linux
                 return v:false
@@ -291,19 +357,20 @@ endfunction
 function! s:get_canary_manifest_dir_path() abort
         if has('mac')
                 return s:build_path([$HOME, 'Library', 'Application Support', 'Google', 'Chrome Canary', 'NativeMessagingHosts'])
-        elseif has('win32')
+        elseif has('win32') || s:is_wsl
                 return s:get_data_dir_path()
         end
         throw "Chrome Canary doesn't exist on Linux"
 endfunction
 
-
 function! s:chromium_config_exists() abort
         let l:p = [$HOME, '.config', 'chromium']
         if has('mac')
                 let l:p = [$HOME, 'Library', 'Application Support', 'Chromium']
         elseif has('win32')
                 let l:p = [$HOME, 'AppData', 'Local', 'Chromium']
+        elseif s:is_wsl
+                let l:p = [s:get_windows_env_path('%LOCALAPPDATA%'), 'Chromium']
         end
         if !empty($XDG_CONFIG_HOME)
                 let l:p = [$XDG_CONFIG_HOME, 'chromium', 'NativeMessagingHosts']
@@ -314,7 +381,7 @@ endfunction
 function! s:get_chromium_manifest_dir_path() abort
         if has('mac')
                 return s:build_path([$HOME, 'Library', 'Application Support', 'Chromium', 'NativeMessagingHosts'])
-        elseif has('win32')
+        elseif has('win32') || s:is_wsl
                 return s:get_data_dir_path()
         end
         if !empty($XDG_CONFIG_HOME)
@@ -372,12 +439,17 @@ function! s:get_progpath() abort
 endfunction
 
 function! s:get_executable_content(data_dir, prolog) abort
-        if has('win32')
+        if has('win32') || s:is_wsl
+                let l:wsl_prefix = ''
+                if s:is_wsl
+                        let l:wsl_prefix = 'wsl'
+                endif
+                let l:dir = s:to_windows_path(a:data_dir)
                 return  "@echo off\r\n" .
-                                        \ "mkdir \"" . a:data_dir . "\" 2>nul\r\n" .
-                                        \ "cd \"" . a:data_dir . "\"\r\n" .
+                                        \ "mkdir \"" . l:dir . "\" 2>nul\r\n" .
+                                        \ "cd \"" . l:dir . "\"\r\n" .
                                         \ a:prolog . "\r\n" .
-                                        \ "\"" . s:get_progpath() . "\" --headless --cmd \"let g:started_by_firenvim = v:true\" -c \"call firenvim#run()\"\r\n"
+                                        \ l:wsl_prefix . ' ' . "\"" . s:get_progpath() . "\" --headless --cmd \"let g:started_by_firenvim = v:true\" -c \"call firenvim#run()\"\r\n"
         endif
         return "#!/bin/sh\n" .
                                 \ 'mkdir -p ' . a:data_dir . "\n" .
@@ -422,7 +494,7 @@ function! s:key_to_ps1_str(key, manifest_path) abort
         " Then, assign a value to it
         return l:ps1_content . "\nSet-Item -Path \"" .
                                 \ a:key .
-                                \ '\" -Value "' . a:manifest_path . '"'
+                                \ '\" -Value "' . s:to_windows_path(a:manifest_path) . '"'
 endfunction
 
 function! s:get_browser_configuration() abort
@@ -520,11 +592,18 @@ function! firenvim#install(...) abort
         " Decide where the script responsible for starting neovim should be
         let l:data_dir = s:get_data_dir_path()
         let l:execute_nvim_path = s:build_path([l:data_dir, s:get_executable_name()])
+
         " Write said script to said path
         let l:execute_nvim = s:get_executable_content(s:get_runtime_dir_path(), l:script_prolog)
 
         call mkdir(l:data_dir, 'p', 0700)
+        if s:is_wsl
+                let l:execute_nvim_path = s:to_wsl_path(l:execute_nvim_path)
+        endif
         call writefile(split(l:execute_nvim, "\n"), l:execute_nvim_path)
+        if s:is_wsl
+                let l:execute_nvim_path = s:to_windows_path(l:execute_nvim_path)
+        endif
         call setfperm(l:execute_nvim_path, 'rwx------')
 
         let l:browsers = s:get_browser_configuration()
@@ -546,7 +625,7 @@ function! firenvim#install(...) abort
                         continue
                 endtry
 
-                if has('win32')
+                if has('win32') || s:is_wsl
                         let l:manifest_path = s:build_path([l:manifest_dir_path, 'firenvim-' . l:name . '.json'])
                 endif
 
@@ -556,7 +635,7 @@ function! firenvim#install(...) abort
 
                 echo 'Installed native manifest for ' . l:name . '.'
 
-                if has('win32')
+                if has('win32') || s:is_wsl
                         " On windows, also create a registry key. We do this
                         " by writing a powershell script to a file and
                         " executing it.
@@ -566,7 +645,7 @@ function! firenvim#install(...) abort
                         echo 'Creating registry key for ' . l:name . '. This may take a while. Script: ' . l:ps1_path
                         call writefile(split(l:ps1_content, "\n"), l:ps1_path)
                         call setfperm(l:ps1_path, 'rwx------')
-                        let o = system(['powershell', '-Command', '-'], readfile(l:ps1_path))
+                        let o = system(['powershell.exe', '-Command', '-'], readfile(l:ps1_path))
                         if v:shell_error
                           echo o
                         endif
@@ -598,10 +677,10 @@ function! firenvim#uninstall() abort
                         let l:manifest_path = s:build_path([l:manifest_dir_path, 'firenvim-' . l:name . '.json'])
                 endif
 
-                if has('win32')
+                if has('win32') || s:is_wsl
                         echo 'Removing registry key for ' . l:name . '. This may take a while.'
                         let l:ps1_content = 'Remove-Item -Path "' . l:cur_browser['registry_key'] . '" -Recurse'
-                        let o = system(['powershell', '-Command', '-'], [l:ps1_content])
+                        let o = system(['powershell.exe', '-Command', '-'], [l:ps1_content])
                         if v:shell_error
                           echo o
                         endif

glacambre added a commit that referenced this issue Jan 31, 2021
@glacambre
Copy link
Owner

Now implemented in master - update the firenvim plugin and run :call firenvim#install() again from wsl :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
os:windows This issue only happens on windows
Projects
None yet
Development

No branches or pull requests

4 participants