<neovim-editor>
Web Component
This component provides <neovim-editor>
, an HTML custom element built on Polymer and flux.
It provides a frontend for the Neovim editor using Neovim's MessagePack API. It allows you to easily embed a Neovim-backed editor
into your application.
This component assumes you have Node.js installed.
You can use this component for modern desktop application frameworks such as Electron or NW.js.
You can even use it in Electron-based editors such as Atom or VS Code.
This component is designed around the Flux architecture.
You can access the UI event notifications and can call Neovim APIs directly via <neovim-editor>
's APIs.
You can install this component as an npm package.
$ npm install neovim-component
Examples
Each example only takes 100~300 lines.
Minimal Example
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="/path/to/webcomponents-lite.min.js"></script>
<link rel="import" href="/path/to/polymer.html" />
<link rel="import" href="/path/to/neovim-editor.html" />
</head>
<body>
<neovim-editor></neovim-editor>
</body>
</html>
Minimal Electron app can be found in the example directory.
The neovim-component
package also has an example executable.
$ npm install -g neovim-component electron-prebuilt
$ simple-neovim-gui
Markdown Editor Example
For a more complicated and realistic example, see the markdown editor example. The markdown previewer is integrated with the Neovim GUI using the <neovim-editor>
component.
Image Popup Example
This is an image popup widget example here. The gi
mapping is defined to show an image under the cursor in a tooltip.
Mini Browser Example
This example shows how to include a mini web-browser using the <webview>
tag from Electron.
Why Did You Create This?
Vim has very powerful editing features, but Vim is an editor (see :help design-not
) and unfortunately lacks support for many graphical tools that writers and programmers like. NyaoVim adds support for graphical features without losing Vim's powerful text editing abilities. Neovim's msgpack APIs provide a perfect way to add a GUI layer using HTML and CSS. NyaoVim is a GUI frontend as a proof of concept.
Architecture
<neovim-editor>
has an editor
property to access the internal APIs of the component.
editor.screen
is a view of the component (using canvas). It receives user input and dispatches input actions to the data store.editor.process
is a process handler to interact with the backing Neovim process via msgpack-rpc APIs. You can call Neovim's APIs via the Neovim client (editor.getClient()
helper).editor.store
is the state of this component. You can access the current state of the editor through this object.
<neovim-editor>
Properties
You can customize <neovim-editor>
with the following properties:
Name | Description | Default |
---|---|---|
width |
Width of the editor in pixels. | null |
height |
Height of the editor in pixels. | null |
font |
Name of the editor's font. | "monospace" |
font-size |
Font-size in pixels. | 12 |
line-height |
Line height rate relative to font size. | 1.3 |
nvim-cmd |
Command used to start Neovim. | "nvim" |
argv |
Arguments passed with the Neovim command. | [] |
on-quit |
Callback function to run when Neovim quits. | null |
on-error |
Callback function for Neovim errors. | null |
disable-alt-key |
Input {char} on <A-{char}> . |
false |
cursor-draw-delay |
Delay in millisec before drawing cursor. | 10 |
blink-cursor |
Blink cursor or not. | true |
<neovim-editor>
APIs
Receive internal various events
You can receive various events (including UI redraw notifications) from the store.
The store
is a part of flux architecture. It's a global instance of EventEmitter.
You can also access the state of editor via the store
. Note that all values are read only.
Do not change the values of the store
directly, it will break the internal state of the component.
const neovim_element = document.getElementById('neovim');
const Store = neovim_element.editor.store;
// Handle cursor movements
Store.on('cursor', () => console.log('Cursor is moved to ', Store.cursor));
// Handle mode changes
Store.on('mode', () => console.log('Mode is changed to ', Store.mode));
// Handle text redraws
Store.on('put', () => console.log('UI was redrawn'));
// Accessing the state of the editor.
const bounds = [ Store.size.lines, Store.size.cols ];
const cursor_pos = [ Store.cursor.line, Store.cursor.col ];
Call Neovim APIs
You can call Neovim APIs via the client. When you call APIs via the client, it sends the call to the underlying Neovim process via MessagePack RPC and will return a Promise which resolves to the returned value.
<neovim-component>
uses promised-neovim-client package.
You can see the all API definitions here. If you know further about Neovim APIs, python client implementation may be helpful.
const neovim_element = document.getElementById('neovim');
const client = neovim_element.editor.getClient();
// Send a command
client.command('vsplit');
// Send input
client.input('<C-w><C-l>');
// Evaluate a Vimscript expression
client.eval('"aaa" . "bbb"').then(result => console.log(result));
// Get the 'b:foo' variable
client.getCurrentBuffer()
.then(buf => buf.getVar('foo'))
.then(v => console.log(v));
// Query something (windows, buffers, etc.)
// Move to the neighbor window and show its information.
client.getWindows()
.then(windows => client.secCurrentWindow(windows[1]))
.then(() => client.getCurrentWindow())
.then(win => console.log(win));
// Receive an RPC request from Neovim
client.on('request', (n, args, res) => console.log(`Name: ${n}, Args: ${JSON.stringify(args)}, Response: ${res}`));
Editor lifecycle
You can receive notifications related to lifecycle of the editor.
const neovim_element = document.getElementById('neovim');
// Called when the Neovim background process attaches
neovim_element.editor.on('process-attached', () => console.log('Neovim process is ready'));
// Called when the Neovim process is disconnected (usually by :quit)
neovim_element.editor.on('quit', () => console.log('Neovim process died'));
// Called when the <neovim-component> detaches
neovim_element.editor.on('detach', () => console.log('Element does not exist in DOM.'));
// Called upon experiencing an error in the internal process
neovim_element.editor.on('error', err => alert(err.message));
View APIs
- Resize screen
const editor = document.getElementById('neovim').editor;
editor.screen.resize(80, 100); // Resize screen to 80 lines and 100 columns
editor.screen.resizeWithPixels(1920, 1080); // Resize screen to 1920px x 1080px
- Change font size
const editor = document.getElementById('neovim').editor;
editor.screen.changeFontSize(18); // Change font size to 18px
- Convert pixels to lines/cols.
const editor = document.getElementById('neovim').editor;
const loc = editor.screen.convertPositionToLocation(80, 24);
console.log(loc.x, loc.y); // Coordinates in pixels of (line, col) = (80, 24)
const pos = editor.screen.convertLocationToPosition(400, 300);
const.log(pos.col, pos.line); // line/col of location (400px, 300px)
- Notify of screen-size changes:
When some process has changed the screen-size you must notify the screen
. The internal <canvas>
element has a fixed size and must update itself if there are size changes. Call screen.checkShouldResize()
if the screen size may have changed
Note that you need not care about resize
event of <body>
element. <neovim-editor>
component automatically detects this particular resize event and updates automatically. screen.checkShouldResize()
will simply be ignored if nothing has actually changed.
const editor = document.getElementById('neovim').editor;
function showUpSomeElementInNeovim() {
const e = document.getElementById('some-elem');
// New element shows up! The screen may be resized by the change.
// 'none' -> 'block'
e.style.display = 'block';
// This call tells to editor to adjust itself in the case that it has been resized
editor.screen.checkShouldResize();
}
Other APIs
- Setting arguments afterwards:
If your app doesn't use Polymer you can set arguments afterwards using JavaScript
Note that it is better to use argv
property of <neovim-element>
if possible.
const editor = document.getElementById('neovim').editor;
editor.setArgv(['README.md']);
- Focusing the editor
<neovim-editor>
is just a web-component, so it can be focused just like other elements.
If it loses focus the editor won't receive any input events.
The editor
instance has a method to re-focus the editor in JavaScript.
The store
instance contains the current focus state.
const editor = document.getElementById('neovim').editor;
console.log(editor.store.focused);
editor.store.on('focus-changed', () => {
console.log('Focus was changed: ' + editor.store.focused);
});
// Refocus the editor to ensure it receives user input.
editor.focus();
Log Levels
<neovim-component>
prints logs in the browser console. The log level is controlled by the NODE_ENV
environment variable:
NODE_ENV=debug
will log everything.NODE_ENV=production
ignores all logs except for warnings and errors.- Setting
NODE_ENV
to empty string or some other value enables logging for info, warnings, and errors.