Nvim msgpack API client and remote plugin provider for Node.js
npx @tessl/cli install tessl/npm-neovim@5.4.0A comprehensive Node.js client library for Neovim that enables programmatic interaction with Neovim instances through msgpack-based RPC communication. This package provides a complete API client for controlling Neovim processes and serves as the official Node.js remote plugin host.
npm install neovim (for general use) or npm install -g neovim (for remote plugin development)import { attach, findNvim, Neovim, NeovimClient, Buffer, Window, Tabpage } from 'neovim';For plugin development:
import { Plugin, Command, Function, Autocmd, NvimPlugin, loadPlugin } from 'neovim';For buffer events (Symbol imports):
import { ATTACH, DETACH } from 'neovim/lib/api/Buffer';CommonJS:
const { attach, findNvim, Neovim } = require('neovim');import * as child_process from 'node:child_process';
import { attach, findNvim } from 'neovim';
// Find and spawn Neovim
const found = findNvim({ minVersion: '0.9.0' });
const nvim_proc = child_process.spawn(found.matches[0].path, ['--embed'], {});
// Attach to the Neovim process
const nvim = attach({ proc: nvim_proc });
// Execute commands
await nvim.command('vsp');
// Access windows and buffers
const windows = await nvim.windows;
const currentBuffer = await nvim.buffer;
// Manipulate buffer content
const lines = await currentBuffer.lines;
await currentBuffer.replace(['Hello from Node.js!'], 0);
// Clean up
nvim.quit();The neovim package is organized around several key components:
attach() and findNvim() handle discovering and connecting to Neovim instances via various transport methods (stdio, sockets, pipes)Neovim and NeovimClient classes provide the main interface to all Neovim API operationsBuffer, Window, and Tabpage represent Neovim entities with their specific operations@Plugin, @Command, etc.) and class-based (NvimPlugin) approaches for creating remote pluginsconsole to avoid breaking stdio RPC channelsCore functionality for locating Neovim executables and establishing connections to Neovim instances via various transport methods.
function attach(options: Attach): NeovimClient;
interface Attach {
reader?: NodeJS.ReadableStream;
writer?: NodeJS.WritableStream;
proc?: NodeJS.Process | child_process.ChildProcess;
socket?: string;
options?: { logger?: any };
}
function findNvim(opt?: FindNvimOptions): Readonly<FindNvimResult>;
interface FindNvimOptions {
minVersion?: string;
orderBy?: 'desc' | 'none';
firstMatch?: boolean;
paths?: string[];
dirs?: string[];
}
interface FindNvimResult {
matches: ReadonlyArray<NvimVersion>;
invalid: ReadonlyArray<NvimVersion>;
}
interface NvimVersion {
path: string;
nvimVersion?: string;
buildType?: string;
luaJitVersion?: string;
error?: Readonly<Error>;
}Complete access to all Neovim API operations including command execution, evaluation, buffer/window/tabpage management, UI operations, and event subscriptions.
class Neovim {
// Properties
static Buffer: typeof Buffer;
static Window: typeof Window;
static Tabpage: typeof Tabpage;
// Key getters/setters
apiInfo: Promise<[number, ApiInfo]>;
buffers: Promise<Buffer[]>;
buffer: AsyncBuffer;
windows: Promise<Window[]>;
window: AsyncWindow;
tabpages: Promise<Tabpage[]>;
tabpage: AsyncTabpage;
line: string | Promise<string>;
mode: Promise<{ mode: string; blocking: boolean }>;
// Command execution
command(arg: string): Promise<any>;
commandOutput(arg: string): Promise<string>;
// Evaluation
eval(expr: string): Promise<VimValue>;
lua(code: string, args?: VimValue[]): Promise<VimValue>;
call(fname: string, args?: VimValue | VimValue[]): Promise<any>;
callAtomic(calls: VimValue[]): Promise<[any[], boolean]>;
// Window/buffer creation
createBuffer(listed: boolean, scratch: boolean): Promise<Buffer | number>;
openWindow(buffer: Buffer, enter: boolean, options: OpenWindowOptions): Promise<Window | number>;
// Event management
subscribe(event: string): Promise<void>;
unsubscribe(event: string): Promise<void>;
// UI operations
uiAttach(width: number, height: number, options: UiAttachOptions): Promise<void>;
uiDetach(): Promise<void>;
// Process control
quit(): void;
}
class NeovimClient extends Neovim {
channelId: Promise<number>;
isApiReady: boolean;
attach({ reader, writer }: { reader: NodeJS.ReadableStream; writer: NodeJS.WritableStream }): void;
close(): Promise<void>;
[Symbol.asyncDispose](): Promise<void>;
}
type VimValue = number | boolean | string | number[] | { [key: string]: any };
type AsyncBuffer = Promise<Buffer> & Omit<Buffer, 'id' | 'isAttached'>;
type AsyncWindow = Promise<Window> & Omit<Window, 'id'>;
type AsyncTabpage = Promise<Tabpage>;Buffer manipulation including line operations, highlights, virtual text, buffer events, and buffer-scoped variables and options.
class Buffer {
id: number;
isAttached: boolean;
// Key properties
length: Promise<number>;
lines: Promise<string[]>;
name: string | Promise<string>;
changedtick: Promise<number>;
valid: Promise<boolean>;
loaded: Promise<boolean>;
// Line operations
getLines(options?: { start?: number; end?: number; strictIndexing?: boolean }): Promise<string[]>;
setLines(lines: string | string[], options?: BufferSetLines): Promise<void>;
insert(lines: string[] | string, start: number): Promise<void>;
replace(lines: string[] | string, start: number): Promise<void>;
remove(start: number, end: number, strictIndexing?: boolean): Promise<void>;
append(lines: string[] | string): Promise<void>;
// Highlights and virtual text
addHighlight(options: BufferHighlight): Promise<number>;
clearNamespace(args: BufferClearNamespace): void;
setVirtualText(nsId: number, line: number, chunks: VirtualTextChunk[], opts?: object): Promise<number>;
// Event handling
listen(eventName: string, cb: Function): Function;
unlisten(eventName: string, cb: Function): void;
// Variables and options (inherited from BaseApi)
getVar(name: string): Promise<VimValue>;
setVar(name: string, value: VimValue): Promise<void>;
getOption(name: string): Promise<VimValue>;
setOption(name: string, value: VimValue): Promise<void>;
}
interface BufferSetLines {
start?: number;
end?: number;
strictIndexing?: boolean;
}
interface BufferHighlight {
hlGroup: string;
line: number;
colStart?: number;
colEnd?: number;
srcId?: number;
}
interface BufferClearNamespace {
nsId: number;
lineStart?: number;
lineEnd?: number;
}
type VirtualTextChunk = [string, string];Window and tabpage management including cursor positioning, dimensions, and window/tabpage-scoped variables.
class Window {
id: number;
// Properties
buffer: AsyncBuffer;
tabpage: AsyncTabpage;
cursor: [number, number] | Promise<[number, number]>;
height: number | Promise<number>;
width: number | Promise<number>;
position: Promise<[number, number]>;
row: Promise<number>;
col: Promise<number>;
valid: Promise<boolean>;
number: Promise<number>;
// Methods
close(force?: boolean): Promise<void>;
config(options?: object): any;
// Variables and options (inherited from BaseApi)
getVar(name: string): Promise<VimValue>;
setVar(name: string, value: VimValue): Promise<void>;
getOption(name: string): Promise<VimValue>;
setOption(name: string, value: VimValue): Promise<void>;
}
class Tabpage {
// Properties
windows: Promise<Window[]>;
window: AsyncWindow;
valid: Promise<boolean>;
number: Promise<number>;
// Variables (inherited from BaseApi)
getVar(name: string): Promise<VimValue>;
setVar(name: string, value: VimValue): Promise<void>;
}Decorator-based and class-based approaches for creating Neovim remote plugins with commands, functions, and autocommands.
// Decorator-based approach
function Plugin(target: any): any;
function Plugin(options: PluginDecoratorOptions): (target: any) => any;
function Command(name: string, options?: CommandOptions): MethodDecorator;
function Function(name: string, options?: NvimFunctionOptions): MethodDecorator;
function Autocmd(name: string, options: AutocmdOptions): MethodDecorator;
interface PluginDecoratorOptions {
dev?: boolean;
}
interface CommandOptions {
sync?: boolean;
range?: string;
nargs?: string;
complete?: string;
}
interface NvimFunctionOptions {
sync?: boolean;
range?: [number, number];
eval?: string;
}
interface AutocmdOptions {
pattern: string;
eval?: string;
sync?: boolean;
}
// Class-based approach
class NvimPlugin {
filename: string;
nvim: Neovim;
instance: any;
dev: boolean;
alwaysInit: boolean;
constructor(filename: string, plugin: any, nvim: Neovim);
setOptions(options: NvimPluginOptions): void;
registerAutocmd(name: string, fn: Function | [any, Function], options: AutocmdOptions): void;
registerCommand(name: string, fn: Function | [any, Function], options?: CommandOptions): void;
registerFunction(name: string, fn: Function | [any, Function], options?: NvimFunctionOptions): void;
handleRequest(name: string, type: string, args: any[]): Promise<any>;
}
interface NvimPluginOptions {
dev?: boolean;
alwaysInit?: boolean;
}
function loadPlugin(filename: string, nvim: Neovim, options?: LoadPluginOptions): NvimPlugin | null;
interface LoadPluginOptions {
cache?: boolean;
}type VimValue = number | boolean | string | number[] | { [key: string]: any };interface ApiInfo {
// Neovim API metadata (version, functions, ui_events, etc.)
[key: string]: any;
}
interface Channel {
// Channel information
[key: string]: any;
}
type Ui = UiAttachOptions & {
height: number;
width: number;
chan?: number;
}
interface Proc {
// Process information
[key: string]: any;
}
interface Command {
name: string;
nargs: string;
range: string;
bang: boolean;
bar: boolean;
register: boolean;
definition: string;
script_id: number;
complete?: string | null;
addr?: any;
count?: any;
complete_arg?: any;
}interface OpenWindowOptions {
external?: boolean;
focusable?: boolean;
width?: number;
height?: number;
bufpos?: [number, number];
row?: number;
col?: number;
anchor?: string;
relative?: string;
style?: string;
border?: string | string[];
}
interface UiAttachOptions {
rgb?: boolean;
override?: boolean;
ext_cmdline?: boolean;
ext_hlstate?: boolean;
ext_linegrid?: boolean;
ext_messages?: boolean;
ext_multigrid?: boolean;
ext_popupmenu?: boolean;
ext_tabline?: boolean;
ext_termcolors?: boolean;
ext_wildmenu?: boolean;
}The package uses Winston for logging and monkey-patches console to prevent logs from breaking stdio RPC channels.
Environment Variables:
NVIM_NODE_LOG_FILE - Path to log fileNVIM_NODE_LOG_LEVEL - Log level (error, warn, info, verbose, debug, silly)ALLOW_CONSOLE - Allow console output (breaks stdio RPC)NVIM_NODE_HOST_DEBUG - Enable debugger for plugin hostCustom Logger:
const nvim = attach({
proc: nvim_proc,
options: { logger: customLogger }
});console by default - use the logger from NeovimClient or pass a custom loggernpm install -g neovimbuffer.listen() or buffer[ATTACH]()ATTACH and DETACH symbols must be imported from neovim/lib/api/Buffer (not from main package)sync: true for blocking behavior$NVIM_LISTEN_ADDRESS to connect to existing Neovim instances via socket