or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

api-client.mdbuffer.mdconnection.mdindex.mdplugin-development.mdwindow-tabpage.md
tile.json

connection.mddocs/

Connection and Discovery

Functions for locating Neovim executables on the system and establishing connections to Neovim instances through various transport mechanisms.

Capabilities

Attach to Neovim

Establishes a connection to a Neovim instance via streams, child process, or socket.

/**
 * Attaches to a Neovim instance using the specified transport method.
 * Returns a NeovimClient instance for interacting with Neovim.
 *
 * @param options - Connection options specifying transport method
 * @returns NeovimClient instance connected to Neovim
 */
function attach(options: Attach): NeovimClient;

interface Attach {
  /** Input stream for reading from Neovim */
  reader?: NodeJS.ReadableStream;
  /** Output stream for writing to Neovim */
  writer?: NodeJS.WritableStream;
  /** Child process with stdin/stdout connected to Neovim */
  proc?: NodeJS.Process | child_process.ChildProcess;
  /** Unix socket or Windows named pipe path */
  socket?: string;
  /** Optional configuration */
  options?: {
    /** Custom logger instance (prevents console monkey-patching) */
    logger?: any;
  };
}

Usage Examples:

import * as child_process from 'node:child_process';
import * as net from 'node:net';
import { attach } from 'neovim';

// Attach via child process (most common)
const nvim_proc = child_process.spawn('nvim', ['--embed'], {});
const nvim1 = attach({ proc: nvim_proc });

// Attach via Unix socket
const nvim2 = attach({ socket: '/tmp/nvim.sock' });

// Attach via Windows named pipe
const nvim3 = attach({ socket: '\\\\.\\pipe\\nvim-pipe' });

// Attach via streams
const socket = net.createConnection({ path: '/tmp/nvim.sock' });
const nvim4 = attach({
  reader: socket,
  writer: socket
});

// Attach with custom logger
const customLogger = {
  level: 'info',
  info: (msg: string) => { /* ... */ },
  warn: (msg: string) => { /* ... */ },
  error: (msg: string) => { /* ... */ },
  debug: (msg: string) => { /* ... */ }
};
const nvim5 = attach({
  proc: nvim_proc,
  options: { logger: customLogger }
});

// Use NeovimClient
await nvim1.command('echo "Hello from Node.js!"');
const buf = await nvim1.buffer;
await buf.append(['Line 1', 'Line 2']);

// Clean up
nvim1.quit();

Find Neovim Executable

Searches for Neovim executables on the system and validates their versions.

/**
 * Searches for Neovim executable on the system.
 * Checks common installation locations and validates versions.
 *
 * @param opt - Search options
 * @returns Result containing valid and invalid matches
 */
function findNvim(opt?: FindNvimOptions): Readonly<FindNvimResult>;

interface FindNvimOptions {
  /** Minimum required Neovim version (inclusive, semver format) */
  minVersion?: string;
  /** Sort order for results: 'desc' (newest first, default) or 'none' */
  orderBy?: 'desc' | 'none';
  /** Stop searching after first valid match */
  firstMatch?: boolean;
  /** Specific file paths to check first (before searching directories) */
  paths?: string[];
  /** Additional directories to search beyond default locations */
  dirs?: string[];
}

interface FindNvimResult {
  /** Valid Neovim executables that meet criteria */
  matches: ReadonlyArray<NvimVersion>;
  /** Invalid or failed executables */
  invalid: ReadonlyArray<NvimVersion>;
}

interface NvimVersion {
  /** Absolute path to nvim executable */
  path: string;
  /** Neovim version string (e.g., '0.9.4') if valid */
  nvimVersion?: string;
  /** Build type (e.g., 'Release', 'Debug') if valid */
  buildType?: string;
  /** LuaJIT version if valid */
  luaJitVersion?: string;
  /** Error object if validation failed */
  error?: Readonly<Error>;
}

Usage Examples:

import { findNvim } from 'neovim';

// Find any Neovim executable
const result1 = findNvim();
if (result1.matches.length > 0) {
  console.log('Found Neovim at:', result1.matches[0].path);
  console.log('Version:', result1.matches[0].nvimVersion);
}

// Find Neovim with minimum version
const result2 = findNvim({ minVersion: '0.9.0' });
if (result2.matches.length === 0) {
  throw new Error('Neovim 0.9.0 or higher not found');
}

// Get newest version only
const result3 = findNvim({
  minVersion: '0.8.0',
  orderBy: 'desc',
  firstMatch: true
});
const newestNvim = result3.matches[0];

// Search specific paths first
const result4 = findNvim({
  paths: ['/usr/local/bin/nvim', '/opt/nvim/bin/nvim'],
  minVersion: '0.9.0'
});

// Search additional directories
const result5 = findNvim({
  dirs: ['/home/user/custom-nvim/bin', '/opt/applications'],
  minVersion: '0.10.0'
});

// Handle invalid installations
const result6 = findNvim();
console.log('Valid installations:', result6.matches.length);
console.log('Invalid installations:', result6.invalid.length);
for (const invalid of result6.invalid) {
  console.log(`Failed to validate ${invalid.path}:`, invalid.error?.message);
}

// Use with spawn
import * as child_process from 'node:child_process';
import { attach } from 'neovim';

const found = findNvim({ minVersion: '0.9.0', orderBy: 'desc' });
if (found.matches.length === 0) {
  throw new Error('Compatible Neovim not found');
}

const nvimPath = found.matches[0].path;
const nvim_proc = child_process.spawn(nvimPath, ['--embed', '--clean'], {});
const nvim = attach({ proc: nvim_proc });

await nvim.command('echo "Using Neovim ' + found.matches[0].nvimVersion + '"');

Connection Lifecycle

Connecting to Existing Instance

Connect to an already-running Neovim instance using $NVIM_LISTEN_ADDRESS:

import { attach } from 'neovim';

// Neovim running with: NVIM_LISTEN_ADDRESS=/tmp/nvim nvim
const nvim = attach({ socket: process.env.NVIM_LISTEN_ADDRESS || '/tmp/nvim' });

// Now you can control the running instance
await nvim.command('vsp');
const buffers = await nvim.buffers;

Spawning New Instance

Spawn a new Neovim instance as a child process:

import * as child_process from 'node:child_process';
import { attach, findNvim } from 'neovim';

// Find Neovim
const found = findNvim({ minVersion: '0.9.0' });
const nvimPath = found.matches[0].path;

// Spawn with options
const nvim_proc = child_process.spawn(nvimPath, [
  '--embed',        // Enable msgpack-rpc
  '--clean',        // Skip config files
  '--headless',     // No UI (optional)
  '-u', 'NONE',     // No vimrc (optional)
], {
  env: { ...process.env, NVIM_LOG_FILE: '/tmp/nvim.log' }
});

const nvim = attach({ proc: nvim_proc });

// Handle process events
nvim_proc.on('exit', (code) => {
  console.log('Neovim exited with code:', code);
});

nvim_proc.on('error', (err) => {
  console.error('Process error:', err);
});

Closing Connection

Properly close connections to Neovim:

import { attach } from 'neovim';

const nvim = attach({ proc: nvim_proc });

// Option 1: Call quit() to exit Neovim
nvim.quit();

// Option 2: Close client connection
await nvim.close();

// Option 3: Use async disposal (Node.js 20+)
{
  await using nvim = attach({ proc: nvim_proc });
  // Use nvim...
} // Automatically disposed

// Wait for process to exit
nvim_proc.on('exit', () => {
  console.log('Neovim process ended');
});

Transport Methods

stdio (Child Process)

Most common method - spawn Neovim as child process:

import * as child_process from 'node:child_process';
import { attach } from 'neovim';

const nvim_proc = child_process.spawn('nvim', ['--embed']);
const nvim = attach({ proc: nvim_proc });

Pros:

  • Simple and reliable
  • Automatic process management
  • Works on all platforms

Cons:

  • Creates new Neovim instance each time
  • Cannot connect to existing instance

Unix Socket / Named Pipe

Connect to existing Neovim instance via socket:

import { attach } from 'neovim';

// Unix socket (Linux/macOS)
const nvim1 = attach({ socket: '/tmp/nvim.sock' });

// Named pipe (Windows)
const nvim2 = attach({ socket: '\\\\.\\pipe\\nvim-pipe' });

Pros:

  • Can connect to existing instance
  • Multiple clients can connect
  • Useful for remote control

Cons:

  • Requires Neovim to be started with socket
  • More complex setup

Starting Neovim with socket:

# Unix
$ NVIM_LISTEN_ADDRESS=/tmp/nvim.sock nvim

# Or explicitly
$ nvim --listen /tmp/nvim.sock

Custom Streams

Use custom read/write streams:

import * as net from 'node:net';
import { attach } from 'neovim';

const socket = net.createConnection({ path: '/tmp/nvim.sock' });
const nvim = attach({
  reader: socket,
  writer: socket
});

// Or use separate streams
const nvim2 = attach({
  reader: process.stdin,
  writer: process.stdout
});

Pros:

  • Maximum flexibility
  • Can use any stream implementation
  • Useful for custom transport layers

Cons:

  • More complex
  • Need to manage stream lifecycle

Error Handling

import { attach, findNvim } from 'neovim';
import * as child_process from 'node:child_process';

try {
  // Find Neovim
  const found = findNvim({ minVersion: '0.9.0' });
  if (found.matches.length === 0) {
    throw new Error('Neovim 0.9.0+ not found on system');
  }

  // Spawn and attach
  const nvimPath = found.matches[0].path;
  const nvim_proc = child_process.spawn(nvimPath, ['--embed']);

  nvim_proc.on('error', (err) => {
    console.error('Failed to spawn Neovim:', err);
  });

  const nvim = attach({ proc: nvim_proc });

  // Handle RPC errors
  try {
    await nvim.command('invalid command');
  } catch (err) {
    console.error('Command failed:', err);
  }

  // Always clean up
  nvim.quit();

} catch (err) {
  console.error('Setup failed:', err);
}

Environment Variables

  • NVIM_LISTEN_ADDRESS - Socket path for connecting to existing instance
  • NVIM_NODE_LOG_FILE - Path to log file
  • NVIM_NODE_LOG_LEVEL - Log level (error, warn, info, verbose, debug, silly)
  • ALLOW_CONSOLE - Enable console output (breaks stdio RPC, use only for debugging)

Important Notes

  • When using stdio transport (child process), do not write to stdout as it will break the RPC channel
  • The package monkey-patches console by default - pass a custom logger to avoid this
  • For remote plugins, Neovim expects neovim package to be installed globally
  • Socket connections require Neovim to be started with --listen or NVIM_LISTEN_ADDRESS
  • Use findNvim() to avoid hardcoding paths and support multiple installations
  • Always handle process exit events when spawning Neovim
  • The NeovimClient class returned by attach() extends Neovim with connection management methods