https://github.com/glacambre/firenvim Skip to content Toggle navigation Sign up * Product + Actions Automate any workflow + Packages Host and manage packages + Security Find and fix vulnerabilities + Codespaces Instant dev environments + Copilot Write better code with AI + Code review Manage code changes + Issues Plan and track work + Discussions Collaborate outside of code + Explore + All features + Documentation + GitHub Skills + Blog * Solutions + For + Enterprise + Teams + Startups + Education + By Solution + CI/CD & Automation + DevOps + DevSecOps + Case Studies + Customer Stories + Resources * Open Source + GitHub Sponsors Fund open source developers + The ReadME Project GitHub community articles + Repositories + Topics + Trending + Collections * Pricing [ ] * # In this repository All GitHub | Jump to | * No suggested jump to results * # In this repository All GitHub | Jump to | * # In this user All GitHub | Jump to | * # In this repository All GitHub | Jump to | Sign in Sign up {{ message }} glacambre / firenvim Public * * Notifications * Fork 118 * Star 3.3k Embed Neovim in Chrome, Firefox & others. License GPL-3.0 license 3.3k stars 118 forks Star Notifications * Code * Issues 52 * Pull requests 12 * Actions * Wiki * Security * Insights More * Code * Issues * Pull requests * Actions * Wiki * Security * Insights glacambre/firenvim This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. master Switch branches/tags [ ] Branches Tags Could not load branches Nothing to show {{ refName }} default View all branches Could not load tags Nothing to show {{ refName }} default View all tags Name already in use A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch? Cancel Create 14 branches 42 tags Code * Local * Codespaces * Clone HTTPS GitHub CLI [https://github.com/g] Use Git or checkout with SVN using the web URL. [gh repo clone glacam] Work fast with our official CLI. Learn more. * Open with GitHub Desktop * Download ZIP Sign In Required Please sign in to use Codespaces. Launching GitHub Desktop If nothing happens, download GitHub Desktop and try again. Launching GitHub Desktop If nothing happens, download GitHub Desktop and try again. Launching Xcode If nothing happens, download Xcode and try again. Launching Visual Studio Code Your codespace will open once ready. There was a problem preparing your codespace, please try again. Latest commit @glacambre glacambre Improve error handling some more for #1483 ... d1909c7 Jan 5, 2023 Improve error handling some more for #1483 d1909c7 Git stats * 1,369 commits Files Permalink Failed to load latest commit information. Type Name Latest commit message Commit time .github Add CodeQL workflow for GitHub code scanning Nov 10, 2022 autoload Improve error handling some more for #1483 Jan 5, 2023 lua Fix breakage caused by removal of $NVIM_LISTEN_ADDRESS May 15, 2022 plugin Add filetype-detection mechanism Mar 24, 2020 src Fix IME handling Nov 5, 2022 static Remove svg files Oct 13, 2019 tests Fix unused import/missing semicolons codeql messages Nov 10, 2022 .dockerignore Add Dockerfile Apr 13, 2021 .eslintrc.json Handle font-fallback better Apr 25, 2021 .gitignore .gitignore: ignore generated failures.txt Jan 24, 2021 .luacheckrc Add luacheck to CI May 13, 2020 .vintrc.yaml Add configuration for Vint vimscript linter Oct 23, 2019 CONTRIBUTING.md Remove thunderbird support Nov 2, 2022 Dockerfile Switch Docker builder base image from Debian to Alpine Apr 13, 2021 LICENSE.md Add build instructions to README.md, add LICENSE.md Mar 8, 2019 README.md Implement localSettings.cmdline == 'none' (#1442) Nov 2, 2022 SECURITY.md Add additional non-working attack to SECURITY.md Mar 20, 2021 TROUBLESHOOTING.md TROUBLESHOOTING.md: update print-debugging instructions Nov 1, 2022 firenvim.gif Add firenvim.gif to readme Mar 19, 2019 package-lock.json Run npm audit fix --force Nov 5, 2022 package.json package.json: bump version 0.2.13 -> 0.2.14 Nov 5, 2022 release.sh release.sh: gzip git-archive's output Nov 5, 2022 tsconfig.json Add addons-linter to ci and release script Jun 4, 2021 webpack.config.js Remove thunderbird support Nov 2, 2022 View code [ ] Firenvim How to use Installing Other browsers Permissions Configuring Firenvim Manually triggering Firenvim Temporarily disabling Firenvim in a tab Building a Firenvim-specific config Using different settings depending on the page/element being edited Understanding Firenvim's configuration object Configuring what elements Firenvim should appear on Configuring Firenvim to not always take over elements Choosing a command line Editing HTML directly Special characters on MacOS Making Firenvim ignore keys Interacting with the page Automatically syncing changes to the page Configuring message timeout Configuring the filename Drawbacks You might also like README.md Firenvim Build & Test Total alerts Vint Luacheck Matrix Wiki Turn your browser1 into a Neovim client (demos: justinmk , Sean Feng ). 1 [Firefox and Chrome are specifically supported. Other Chromium based browsers such as Brave, Vivaldi, and Opera should also work but are not specifically tested.] Firenvim demo How to use Just click on any textarea and it will be immediately replaced by an instance of Firenvim. To set the content of the now hidden textarea to the content of the Neovim instance, simply :w. If you want to close the Firenvim overlay and return to the textarea, use :q. If you selected an element where you expected the Firenvim frame to appear and it didn't, try pressing . Installing Before installing anything please read SECURITY.md and make sure you're okay with everything mentioned. In the event you think of a way to compromise Firenvim, please send me an email (you can find my address in my commits). 1. Make sure you are using Neovim 0.6.0 or later. This plugin will not work with vanilla VIM or Vimr. Also make sure that your browser hasn't been installed with Snap or Flatpak - these prevent Firenvim from starting Neovim. 2. Install Firenvim as a VIM plugin as you would any other, then run the built-in post-install script. + vim-plug Plug 'glacambre/firenvim', { 'do': { _ -> firenvim#install(0) } } + dein call dein#add('glacambre/firenvim', { 'hook_post_update': { _ -> firenvim#install(0) } }) + packer use { 'glacambre/firenvim', run = function() vim.fn['firenvim#install'](0) end } + minpac call minpac#add('glacambre/firenvim', { 'type': 'opt', 'do': 'packadd firenvim | call firenvim#install(0)'}) if exists('g:started_by_firenvim') packadd firenvim endif + pathogen, vundle, others Install the plugin as you usually would, then run this shell command: $ nvim --headless "+call firenvim#install(0) | q" 3. Finally, install the Firenvim addon for your browser from Mozilla's store or Google's. If you would rather build and install Firenvim from source, check CONTRIBUTING.md. Other browsers Other browsers aren't supported for now. Opera, Vivaldi and other Chromium-based browsers should however work just like in Chromium and have similar install steps. Brave and Edge might work, Safari doesn't (it doesn't support Webextensions). Permissions Firenvim currently requires the following permissions for the following reasons: * Access your data for all websites: this is necessary in order to be able to append elements (= the neovim iframe) to the DOM. * Exchange messages with programs other than Firefox: this is necessary in order to be able to start neovim instances. Configuring Firenvim Manually triggering Firenvim You can configure the keybinding to manually trigger Firenvim ( by default) in the shortcuts menu in about://addons on Firefox, or in chrome://extensions/shortcuts on Chrome. Temporarily disabling Firenvim in a tab Temporarily disabling (and re-enabling) Firenvim in a tab can be done either by clicking on the Firenvim button next to the urlbar or by configuring a browser shortcut (see the previous section to find out how browser shortcuts can be configured). Building a Firenvim-specific config When it starts Neovim, Firenvim sets the variable g:started_by_firenvim which you can check to run different code in your init.vim. For example: if exists('g:started_by_firenvim') set laststatus=0 else set laststatus=2 endif Alternatively, you can detect when Firenvim connects to Neovim by using the UIEnter autocmd event: function! OnUIEnter(event) abort if 'Firenvim' ==# get(get(nvim_get_chan_info(a:event.chan), 'client', {}), 'name', '') set laststatus=0 endif endfunction autocmd UIEnter * call OnUIEnter(deepcopy(v:event)) Similarly, you can detect when Firenvim disconnects from a Neovim instance with the UILeave autocommand. Using different settings depending on the page/element being edited If you want to use different settings depending on the textarea you're currently editing, you can use autocommands to do that too. All buffers are named like this: domainname_page_selector.txt (see the toFileName function). For example, this will set file type to markdown for all GitHub buffers: au BufEnter github.com_*.txt set filetype=markdown Understanding Firenvim's configuration object You can configure everything else about Firenvim by creating a dictionary named g:firenvim_config in your init.vim and setting the keys "globalSettings" and "localSettings". In the dictionary g:firenvim_config["localSettings"] you can map Javascript patterns that match against the full URL to settings that are used for all URLs matched by that pattern. When multiple patterns match a URL, the pattern with the highest "priority" value is used. Here is an example (the settings and their possible values will be explained in the next subsections): let g:firenvim_config = { \ 'globalSettings': { \ 'alt': 'all', \ }, \ 'localSettings': { \ '.*': { \ 'cmdline': 'neovim', \ 'content': 'text', \ 'priority': 0, \ 'selector': 'textarea', \ 'takeover': 'always', \ }, \ } \ } With this configuration, takeover will be set to always on all websites. If we wanted to override this value on british websites, we could add the following lines to our init.vim. Notice how the priority of this new regex is higher than that of the .* regex: let fc = g:firenvim_config['localSettings'] let fc['https?://[^/]+\.co\.uk/'] = { 'takeover': 'never', 'priority': 1 } From now on, localSettings examples will use the let fc[...] = ... shorthand, assuming that you have defined a g:firenvim_config object and that you have a line like let fc = g:firenvim_config ['localSettings'] in your config. Configuring what elements Firenvim should appear on The selector attribute of a localSetting controls what elements Firenvim automatically takes over. Here's the default value: let fc['.*'] = { 'selector': 'textarea:not([readonly]), div[role="textbox"]' } If you don't want to use Firenvim with rich text editors (e.g. Gmail, Outlook, Slack...) as a general rule, you might want to restrict Firenvim to simple textareas: let fc['.*'] = { 'selector': 'textarea' } Since selector is just a CSS selector, you have access to all of CSS's pseudo selectors, including :not(), which allows you to exclude elements that have certain attributes, like this: let fc['.*'] = { 'selector': 'textarea:not([class=xxx])' } Configuring Firenvim to not always take over elements Firenvim has a setting named takeover that can be set to always, empty, never, nonempty or once. When set to always, Firenvim will always take over elements for you. When set to empty, Firenvim will only take over empty elements. When set to never, Firenvim will never automatically appear, thus forcing you to use a keyboard shortcut in order to make the Firenvim frame appear. When set to nonempty, Firenvim will only take over elements that aren't empty. When set to once, Firenvim will take over elements the first time you select them, which means that after :q'ing Firenvim, you'll have to use the keyboard shortcut to make it appear again. Here's how to use the takeover setting: let fc['.*'] = { 'takeover': 'always' } Choosing a command line You can chose between neovim's built-in command line, firenvim's command line and no command line at all by setting the localSetting named cmdline to either neovim, firenvim or none, e.g.: let fc['.*'] = { 'cmdline' : 'firenvim' } Choosing none does not make sense unless you have alternative way to display the command line such as noice.nvim. Editing HTML directly The content localSetting controls how Firenvim should read the content of an element. Setting it to html will make Firenvim fetch the content of elements as HTML, text will make it use plaintext. The default value is text: let fc['.*'] = { 'content' : 'html' } Special characters on MacOS On MacOS, the default keyboard layouts emit special characters when the alt (i.e. option) key is held down. From the perspective of the browser, these special characters replace the underlying "main" character of a keystroke event while retaining the modifier. For example, in the standard US layout the key chord alt-o is received in the browser as alt-o rather than alt-o. Further, certain alt-chords represent "dead keys", which apply a diacritic to the next character entered. Pressing alt-e followed by a produces the single character "a" while alt-u followed by a produces "a". To produce this behavior, diacritic-mapped strokes like alt-e and alt-u are themselves mapped to a "Dead key" character. These behaviors complicate the support of special character and alt/ meta (A- or M-) vim mappings on MacOS in two ways: 1. There is no way to generate unmodified special character key events. For example, since the only way to generate the character "o" via the keyboard is by holding down alt, any key event with the "o" character will also have an alt modifier. If we forward this directly to Vim, it will be received as . 2. There is no way to generate alt-modified plain alphanumeric characters. For example, an mapping won't work because pressing alt-o generates rather than . Terminal and standalone GUI applications can solve these problems by changing the interpretation of the alt key at the application level. Terminal.app and iTerm2, for instance, both provide a "use Option as Meta key" preference that converts incoming alt-chords at the application level. Firenvim, however, is a browser extension that operates off of browser keystroke events rather than application-level events. At present, we are unsure how to implement this "use option as meta" functionality at the browser event level (help here is welcome!). However, there are some workarounds. For problem (1), Firenvim will by default drop the alt key on MacOS for any special character, defined here as non-alphanumeric (not matching /[a-zA-Z0-9]/). This means alt-o will be forwarded to NeoVim as "o" rather than "M-o". Note that this behavior can be changed by setting the alt setting of the globalSettings configuration to all, like this: Making Firenvim ignore keys You can make Firenvim ignore key presses (thus letting the browser handle them) by setting key-value pairs in globalSettings.ignoreKeys. The key needs to be the neovim mode the key press should be ignored in and the value should be an array containing the textual representation of the key press you want ignored. If you want to ignore a key press in all modes, you can use all as mode key. For example, if you want to make Firenvim ignore and in normal mode and in all modes to let your browser handle them, you should define ignoreKeys like this: let g:firenvim_config = { \ 'globalSettings': { \ 'ignoreKeys': { \ 'all': [''], \ 'normal': ['', ''] \ } \ } \ } Mode names are defined in Neovim's cursor_shape.c. Note that if the key press contains multiple modifiers, Shift needs to be first, Alt second, Control third and OS/Meta last (e.g. Ctrl+Alt+Shift+1 needs to be ). If your keyboard layout requires you to press shift in order to press numbers, shift should be present in the key representation (e.g. on french azerty keyboards, should actually be ). Interacting with the page You can execute javascript in the page by using firenvim#eval_js. The code has to be a valid javascript expression (NOT a statement). You can provide the name of a function that should be executed with the result of the expression. Note that some pages prevent evaluating JavaScript with their CSP and this can't be worked around. Here's an example: call firenvim#eval_js('alert("Hello World!")', 'MyFunction') You can move focus from the editor back to the page or the input field by calling firenvim#focus_page or firenvim#focus_input. Here's an example that does exactly this if you press twice while in normal mode: nnoremap :call firenvim#focus_page() There is also a function named firenvim#hide_frame() which will temporarily hide the Firenvim frame. You will then be able to bring the neovim frame back either by unfocusing and refocusing the textarea or by using the keybinding to manually trigger Firenvim. nnoremap :call firenvim#hide_frame() A function named firenvim#press_keys() will allow you to send key events to the underlying input field by taking a list of vim-like keys (e.g. a, , ...) as argument. Note that this only "triggers" an event, it does not add text to the input field. For example if you'd like firenvim to send to the webpage when you press in the editor, you can use the following mapping which is useful with chat apps: au BufEnter riot.im_* inoremap :w:call firenvim#press_keys("CR>")ggdGa Note that our goal is to make the mapping type firenvim#press_keys(" ") in vim's command prompt and then execute it. Since we want the keys to be typed and not Enter to be pressed, we can't use because it would be interpreted by inoremap. Hence we use CR> in order to type the keys . Similarly, if you want to type the keys you'd use C-CR>. Known Issues: some websites do not react to firenvim#press_keys (e.g. Slack). Automatically syncing changes to the page Since Firenvim simply uses the BufWrite event in order to detect when it needs to write neovim's buffers to the page, Firenvim can be made to automatically synchronize all changes like this: au TextChanged * ++nested write au TextChangedI * ++nested write Depending on how large the edited buffer is, this could be a little slow. This more sophisticated approach will throttle writes: let g:timer_started = v:false function! My_Write(timer) abort let g:timer_started = v:false write endfunction function! Delay_My_Write() abort if g:timer_started return end let g:timer_started = v:true call timer_start(10000, 'My_Write') endfunction au TextChanged * ++nested call Delay_My_Write() au TextChangedI * ++nested call Delay_My_Write() Configuring message timeout Due to space constraints, the external command line covers part of the buffer. This can be a problem as sometimes neovim will send a message that tells Firenvim to draw the command line, and then never send the message to tell Firenvim to stop displaying it. In order to work around this problem, a "cmdlineTimeout" configuration option has been implemented, which makes Firenvim hide the external command line after the cursor has moved and some amount of milliseconds have passed: let g:firenvim_config = { \ 'globalSettings': { \ 'cmdlineTimeout': 3000, \ } \ } Configuring the filename It is possible to configure the name of the file used by Firenvim with the filename localSetting. This setting is a format string where each element in curly braces will be replaced with a value and where the maximum length can be specified with a percentage. Possible format elements are hostname (= the domain name of the website), pathname (= the path of the page), selector (= the CSS selector of the text area), timestamp (= the current date) and extension (the language extension when using Firenvim on a code editor or txt otherwise). For example: let g:firenvim_config = { \ 'localSettings': { \ '.*': {, \ 'filename': '/tmp/{hostname}_{pathname%10}.{extension}', \ } \ } Will result in Firenvim using /tmp/github.com_issues-new.txt on Github's new issue page. The default value of this setting is {hostname%32}_{pathname%32}_{selector%32}_{timestamp%32}.{extension}. Drawbacks Some keybindings, such as , and are not overridable through usual means. This means that you have to tell your browser to let Firenvim override them by using the shortcuts menu in about:// addons on Firefox and chrome://extensions/shortcuts in Chrome. When it is possible to do so, if you press one of these keyboard shortcuts while not in a Firenvim frame, Firenvim will attempt to emulate the expected behavior of the shortcut. For example, pressing in a Firenvim frame will tell neovim you pressed , but outside of it it will tell the browser to close the current tab. Controlling whether Firenvim should attempt to emulate the browser's default behavior can be done with global settings. The following snippet will tell Firenvim to simulate 's default behavior while never simulating 's: let g:firenvim_config = { \ 'globalSettings': { \ '': 'noop', \ '': 'default', \ } \ } Note that on Firefox on Linux some keyboard shortcuts might not be overridable. I circumvent this issue by running a patched version of Firefox (note: once Firefox is patched, you won't need to setup webextension keyboard shortcuts). You might also like * Tridactyl, provides vim-like keybindings to use Firefox. Also lets you edit input fields and text areas in your favourite editor with its :editor command. * GhostText, lets you edit text areas in your editor with a single click. Requires installing a plugin in your editor too. Features live updates! * Textern, a Firefox addon that lets you edit text areas in your editor without requiring you to install a plugin in your editor. * withExEditor, same thing as Textern, except you can also edit/ view a page's source with your editor. About Embed Neovim in Chrome, Firefox & others. Resources Readme License GPL-3.0 license Security policy Security policy Stars 3.3k stars Watchers 15 watching Forks 118 forks Releases 6 0.2.14 Latest Nov 5, 2022 + 5 releases Sponsor this project * * liberapay liberapay.com/glacambre Learn more about GitHub Sponsors Packages 0 No packages published Contributors 30 * * * * * * * * * * * + 19 contributors Languages * TypeScript 74.6% * Vim Script 13.0% * Lua 5.8% * HTML 3.5% * JavaScript 1.8% * Shell 1.2% * Dockerfile 0.1% Footer (c) 2023 GitHub, Inc. Footer navigation * Terms * Privacy * Security * Status * Docs * Contact GitHub * Pricing * API * Training * Blog * About You can't perform that action at this time. You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.