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

buffer.mddocs/

Buffer Operations

The Buffer class represents a Neovim buffer and provides methods for manipulating buffer content, managing highlights and virtual text, handling buffer events, and accessing buffer-scoped variables and options.

Capabilities

Buffer Class

/**
 * Represents a Neovim buffer.
 * Extends BaseApi and provides buffer-specific operations.
 */
class Buffer {
  /** Buffer number (bufnr) - unique identifier that doesn't change */
  id: number;

  /** Whether buffer has event listeners attached */
  isAttached: boolean;

  // Properties
  /** Total number of lines in buffer */
  length: Promise<number>;

  /** All buffer lines */
  lines: Promise<string[]>;

  /** Buffer name (full file path or empty for unnamed buffers) */
  name: string | Promise<string>;

  /** Buffer change tick (increments on each change) */
  changedtick: Promise<number>;

  /** Whether buffer handle is valid */
  valid: Promise<boolean>;

  /** Whether buffer is loaded in memory */
  loaded: Promise<boolean>;

  /** Buffer-local commands */
  commands: Promise<Record<string, any>>;

  // Inherited from BaseApi
  /**
   * Get buffer variable (b:var).
   *
   * @param name - Variable name
   * @returns Variable value
   */
  getVar(name: string): Promise<VimValue>;

  /**
   * Set buffer variable (b:var).
   *
   * @param name - Variable name
   * @param value - Variable value
   */
  setVar(name: string, value: VimValue): Promise<void>;

  /**
   * Delete buffer variable (b:var).
   *
   * @param name - Variable name
   */
  deleteVar(name: string): Promise<void>;

  /**
   * Get buffer option.
   *
   * @param name - Option name
   * @returns Option value
   */
  getOption(name: string): Promise<VimValue>;

  /**
   * Set buffer option.
   *
   * @param name - Option name
   * @param value - Option value
   */
  setOption(name: string, value: VimValue): Promise<void>;

  /**
   * Compare with another buffer.
   *
   * @param other - Object to compare
   * @returns true if buffers are equal
   */
  equals(other: any): boolean;

  /**
   * Send RPC request to Neovim for this buffer.
   *
   * @param name - API method name
   * @param args - Method arguments
   * @returns Response from Neovim
   */
  request(name: string, args?: any[]): Promise<any>;

  /**
   * Send notification to Neovim for this buffer (non-blocking).
   *
   * @param name - Notification name
   * @param args - Notification arguments
   */
  notify(name: string, args: any[]): void;
}

type VimValue = number | boolean | string | number[] | { [key: string]: any };

Line Operations

Reading and modifying buffer lines.

/**
 * Get lines from buffer within a range.
 *
 * @param options - Range options
 * @param options.start - Start line index (0-based, default: 0)
 * @param options.end - End line index (exclusive, default: -1 for end of buffer)
 * @param options.strictIndexing - Throw error if range is invalid (default: true)
 * @returns Array of line strings
 */
getLines(options?: { start?: number; end?: number; strictIndexing?: boolean }): Promise<string[]>;

/**
 * Set lines in buffer within a range.
 * Replaces lines from start to end (exclusive) with provided lines.
 *
 * @param lines - Lines to set (string or array of strings)
 * @param options - Range options
 * @param options.start - Start line index (default: 0)
 * @param options.end - End line index (exclusive, default: -1)
 * @param options.strictIndexing - Throw error if range is invalid (default: true)
 */
setLines(lines: string | string[], options?: BufferSetLines): Promise<void>;

/**
 * Insert lines at a specific index.
 * Existing lines at and after the index are shifted down.
 *
 * @param lines - Lines to insert
 * @param start - Index to insert at (0-based)
 */
insert(lines: string[] | string, start: number): Promise<void>;

/**
 * Replace lines starting at index.
 * Replaces as many lines as provided in the array.
 *
 * @param lines - Lines to set
 * @param start - Starting index (0-based)
 */
replace(lines: string[] | string, start: number): Promise<void>;

/**
 * Remove lines from buffer.
 *
 * @param start - Start line index (0-based, inclusive)
 * @param end - End line index (exclusive)
 * @param strictIndexing - Throw error if range is invalid
 */
remove(start: number, end: number, strictIndexing?: boolean): Promise<void>;

/**
 * Append lines to end of buffer.
 *
 * @param lines - Lines to append
 */
append(lines: string[] | string): Promise<void>;

interface BufferSetLines {
  start?: number;
  end?: number;
  strictIndexing?: boolean;
}

Usage Examples:

import { attach } from 'neovim';

const nvim = attach({ proc: nvim_proc });
const buf = await nvim.buffer;

// Get all lines
const allLines = await buf.lines;
console.log('Lines:', allLines);

// Get specific range
const firstFiveLines = await buf.getLines({ start: 0, end: 5 });
console.log('First 5:', firstFiveLines);

// Get from line 10 to end
const fromTen = await buf.getLines({ start: 10, end: -1 });

// Set all lines
await buf.setLines(['Line 1', 'Line 2', 'Line 3']);

// Set specific range (replace lines 0-2)
await buf.setLines(['New line 1', 'New line 2'], { start: 0, end: 2 });

// Set single line
await buf.setLines('Single line', { start: 0, end: 1 });

// Insert at beginning
await buf.insert(['Header line'], 0);

// Insert at line 5
await buf.insert(['Inserted line'], 5);

// Replace from line 2
await buf.replace(['Replacement 1', 'Replacement 2'], 2);

// Remove lines 5-10
await buf.remove(5, 10);

// Remove all lines
const lineCount = await buf.length;
await buf.remove(0, lineCount);

// Append to end
await buf.append(['Appended line 1', 'Appended line 2']);

// Append single line
await buf.append('Single appended line');

// Get line count
const count = await buf.length;
console.log('Line count:', count);

// Working with large buffers efficiently
const chunkSize = 1000;
for (let i = 0; i < 10000; i += chunkSize) {
  const lines = await buf.getLines({
    start: i,
    end: Math.min(i + chunkSize, await buf.length)
  });
  // Process chunk
}

Buffer Information

Querying buffer metadata and properties.

/**
 * Get buffer-local commands.
 *
 * @param options - Optional filter options
 * @returns Map of command name to command info
 */
getCommands(options?: object): Promise<Record<string, any>>;

/**
 * Get position of a mark in buffer.
 *
 * @param name - Mark name (single character)
 * @returns [row, col] position (1-indexed row, 0-indexed col)
 */
mark(name: string): Promise<[number, number]>;

/**
 * Get buffer-local key mappings for a mode.
 *
 * @param mode - Mode ('n', 'v', 'i', 'x', etc.)
 * @returns Array of mapping objects
 */
getKeymap(mode: string): Promise<object[]>;

/**
 * Get byte offset of a line.
 *
 * @param index - Line index (0-based)
 * @returns Byte offset from start of buffer
 */
getOffset(index: number): Promise<number>;

Usage Examples:

const buf = await nvim.buffer;

// Get buffer name
const name = await buf.name;
console.log('Buffer name:', name);

// Set buffer name
buf.name = '/path/to/file.txt';

// Check if buffer is valid
const isValid = await buf.valid;
if (!isValid) {
  console.log('Buffer was deleted');
}

// Check if loaded
const isLoaded = await buf.loaded;
console.log('Buffer loaded:', isLoaded);

// Get change tick
const tick = await buf.changedtick;
console.log('Change tick:', tick);

// Get commands
const commands = await buf.commands;
console.log('Buffer commands:', Object.keys(commands));

const allCommands = await buf.getCommands();
console.log('All buffer commands:', allCommands);

// Get mark position
const markPos = await buf.mark('a');
console.log('Mark a at:', markPos); // [row, col]

// Get keymaps
const normalMaps = await buf.getKeymap('n');
console.log('Normal mode buffer mappings:', normalMaps);

// Get byte offset
const offset = await buf.getOffset(10);
console.log('Line 10 starts at byte:', offset);

// Buffer ID
console.log('Buffer number:', buf.id);

Highlight Operations

Adding and managing buffer highlights.

/**
 * Add a highlight to buffer.
 * Returns highlight ID that can be used to clear the highlight.
 *
 * @param options - Highlight options
 * @param options.hlGroup - Highlight group name
 * @param options.line - Line number (0-based)
 * @param options.colStart - Start column (0-based, default: 0)
 * @param options.colEnd - End column (exclusive, default: -1 for end of line)
 * @param options.srcId - Source/namespace ID (default: -1 for new namespace)
 * @returns Highlight ID
 */
addHighlight(options: BufferHighlight): Promise<number>;

/**
 * Clear highlights in namespace.
 *
 * @param args - Clear options
 * @param args.nsId - Namespace ID
 * @param args.lineStart - Start line (default: 0)
 * @param args.lineEnd - End line (exclusive, default: -1 for end)
 */
clearNamespace(args: BufferClearNamespace): void;

/**
 * Clear highlights (deprecated - use clearNamespace instead).
 *
 * @param args - Clear options
 * @param args.srcId - Source ID
 * @param args.lineStart - Start line
 * @param args.lineEnd - End line
 */
clearHighlight(args?: BufferClearHighlight): Promise<void>;

interface BufferHighlight {
  hlGroup: string;
  line: number;
  colStart?: number;
  colEnd?: number;
  srcId?: number;
}

interface BufferClearNamespace {
  nsId: number;
  lineStart?: number;
  lineEnd?: number;
}

interface BufferClearHighlight {
  srcId?: number;
  lineStart?: number;
  lineEnd?: number;
}

Usage Examples:

const buf = await nvim.buffer;

// Create namespace
const ns = await nvim.createNamespace('my-highlights');

// Add highlight to entire line
const hlId1 = await buf.addHighlight({
  hlGroup: 'Error',
  line: 0,
  srcId: ns
});

// Add highlight to specific columns
const hlId2 = await buf.addHighlight({
  hlGroup: 'Search',
  line: 5,
  colStart: 10,
  colEnd: 20,
  srcId: ns
});

// Highlight multiple lines
for (let i = 0; i < 10; i++) {
  await buf.addHighlight({
    hlGroup: 'Comment',
    line: i,
    colStart: 0,
    colEnd: -1, // End of line
    srcId: ns
  });
}

// Clear all highlights in namespace
await buf.clearNamespace({ nsId: ns });

// Clear highlights in line range
await buf.clearNamespace({
  nsId: ns,
  lineStart: 0,
  lineEnd: 10
});

// Highlight search results
const lines = await buf.lines;
const searchTerm = 'TODO';
for (let i = 0; i < lines.length; i++) {
  let index = 0;
  while ((index = lines[i].indexOf(searchTerm, index)) !== -1) {
    await buf.addHighlight({
      hlGroup: 'Todo',
      line: i,
      colStart: index,
      colEnd: index + searchTerm.length,
      srcId: ns
    });
    index += searchTerm.length;
  }
}

// Clear old highlights (deprecated method)
await buf.clearHighlight({ srcId: ns });

Virtual Text Operations

Adding virtual text (text displayed but not in buffer).

/**
 * Set virtual text on a line.
 * Virtual text appears after the line content but is not part of the buffer.
 *
 * @param nsId - Namespace ID
 * @param line - Line number (0-based)
 * @param chunks - Array of [text, highlight] tuples
 * @param opts - Optional configuration
 * @returns ID of the virtual text
 */
setVirtualText(nsId: number, line: number, chunks: VirtualTextChunk[], opts?: object): Promise<number>;

type VirtualTextChunk = [string, string]; // [text, highlight_group]

Usage Examples:

const buf = await nvim.buffer;
const ns = await nvim.createNamespace('my-virtual-text');

// Add simple virtual text
await buf.setVirtualText(ns, 0, [['← This is virtual text', 'Comment']]);

// Add multi-styled virtual text
await buf.setVirtualText(ns, 5, [
  ['Error: ', 'ErrorMsg'],
  ['Line too long', 'WarningMsg']
]);

// Add diagnostic messages
const diagnostics = [
  { line: 10, message: 'Unused variable', severity: 'Warning' },
  { line: 25, message: 'Syntax error', severity: 'Error' }
];

for (const diag of diagnostics) {
  const hlGroup = diag.severity === 'Error' ? 'ErrorMsg' : 'WarningMsg';
  await buf.setVirtualText(ns, diag.line, [
    [`  ${diag.message}`, hlGroup]
  ]);
}

// Clear virtual text (clear entire namespace)
await buf.clearNamespace({ nsId: ns });

// Add inline hints
const lines = await buf.lines;
for (let i = 0; i < lines.length; i++) {
  const line = lines[i];
  // Simple type inference example
  if (line.includes('const')) {
    await buf.setVirtualText(ns, i, [
      [' : string', 'Type']
    ]);
  }
}

Event Handling

Listening to buffer events in real-time.

/**
 * Listen to buffer events.
 * Available events: 'lines', 'changedtick', 'detach', 'reload'
 *
 * @param eventName - Event name
 * @param cb - Callback function
 * @returns Detach function
 */
listen(eventName: string, cb: Function): Function;

/**
 * Stop listening to buffer events.
 *
 * @param eventName - Event name
 * @param cb - Callback function
 */
unlisten(eventName: string, cb: Function): void;

/**
 * Attach to buffer events (Symbol method).
 * Must be called before listen() will work.
 *
 * @param sendBuffer - Whether to send buffer in events
 * @param options - Attachment options
 * @returns true if successfully attached
 */
\[ATTACH\]\(sendBuffer?: boolean, options?: object\): Promise<boolean>;

/**
 * Detach from buffer events (Symbol method).
 */
[DETACH](): void;

// Symbol constants
const ATTACH: unique symbol;
const DETACH: unique symbol;

Usage Examples:

import { attach } from 'neovim';
import { ATTACH, DETACH } from 'neovim/lib/api/Buffer';

const nvim = attach({ proc: nvim_proc });
const buf = await nvim.buffer;

// Attach to buffer events (required before listening)
await buf[ATTACH]();

// Listen to line changes
const detachLines = buf.listen('lines', (
  buffer: Buffer,
  tick: number,
  firstLine: number,
  lastLine: number,
  lineData: string[],
  more: boolean
) => {
  console.log('Lines changed:', {
    tick,
    firstLine,
    lastLine,
    newLines: lineData,
    more
  });
});

// Listen to change tick updates
buf.listen('changedtick', (buffer: Buffer, tick: number) => {
  console.log('Buffer changed, tick:', tick);
});

// Listen to buffer detach
buf.listen('detach', (buffer: Buffer) => {
  console.log('Buffer detached');
});

// Listen to buffer reload
buf.listen('reload', (buffer: Buffer) => {
  console.log('Buffer reloaded');
});

// Stop listening to specific event
buf.unlisten('lines', detachLines);

// Or call the detach function returned by listen()
detachLines();

// Detach from all buffer events
buf[DETACH]();

// Check if attached
console.log('Buffer attached:', buf.isAttached);

// Example: Auto-save on changes
await buf[ATTACH]();
let saveTimer: NodeJS.Timeout;

buf.listen('lines', () => {
  // Debounce save
  clearTimeout(saveTimer);
  saveTimer = setTimeout(async () => {
    await nvim.command('write');
    console.log('Auto-saved');
  }, 1000);
});

// Example: Track changes for undo
const changes: any[] = [];

await buf[ATTACH]();
buf.listen('lines', (buffer, tick, firstLine, lastLine, lineData) => {
  changes.push({
    timestamp: Date.now(),
    tick,
    range: [firstLine, lastLine],
    lines: lineData
  });
});

Buffer Variables and Options

Managing buffer-scoped variables and options.

// Inherited from BaseApi
getVar(name: string): Promise<VimValue>;
setVar(name: string, value: VimValue): Promise<void>;
deleteVar(name: string): Promise<void>;
getOption(name: string): Promise<VimValue>;
setOption(name: string, value: VimValue): Promise<void>;

Usage Examples:

const buf = await nvim.buffer;

// Buffer variables (b:)
await buf.setVar('my_buffer_var', 'hello');
const myVar = await buf.getVar('my_buffer_var');
console.log('Buffer var:', myVar);

await buf.setVar('line_count', await buf.length);
await buf.deleteVar('my_buffer_var');

// Buffer options
await buf.setOption('filetype', 'javascript');
await buf.setOption('expandtab', true);
await buf.setOption('tabstop', 2);

const ft = await buf.getOption('filetype');
console.log('Filetype:', ft);

const modified = await buf.getOption('modified');
console.log('Buffer modified:', modified);

// Common buffer options
await buf.setOption('readonly', false);
await buf.setOption('modifiable', true);
await buf.setOption('buftype', 'nofile');
await buf.setOption('swapfile', false);

// Check various options
const options = ['filetype', 'fileencoding', 'fileformat', 'modified', 'modifiable'];
for (const opt of options) {
  const value = await buf.getOption(opt);
  console.log(`${opt}:`, value);
}

Practical Examples

Syntax Highlighting

const buf = await nvim.buffer;
const ns = await nvim.createNamespace('syntax');

const lines = await buf.lines;

// Simple keyword highlighting
const keywords = ['function', 'const', 'let', 'var', 'if', 'else', 'return'];
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
  const line = lines[lineNum];
  for (const keyword of keywords) {
    let index = 0;
    while ((index = line.indexOf(keyword, index)) !== -1) {
      await buf.addHighlight({
        hlGroup: 'Keyword',
        line: lineNum,
        colStart: index,
        colEnd: index + keyword.length,
        srcId: ns
      });
      index += keyword.length;
    }
  }
}

Live Diagnostics

const buf = await nvim.buffer;
const diagNs = await nvim.createNamespace('diagnostics');
const vtextNs = await nvim.createNamespace('diagnostics-vtext');

async function updateDiagnostics() {
  // Clear old diagnostics
  await buf.clearNamespace({ nsId: diagNs });
  await buf.clearNamespace({ nsId: vtextNs });

  const lines = await buf.lines;

  // Run linter/checker
  const diagnostics = await runLinter(lines);

  for (const diag of diagnostics) {
    // Add highlight
    await buf.addHighlight({
      hlGroup: diag.severity === 'error' ? 'ErrorMsg' : 'WarningMsg',
      line: diag.line,
      colStart: diag.colStart,
      colEnd: diag.colEnd,
      srcId: diagNs
    });

    // Add virtual text
    await buf.setVirtualText(vtextNs, diag.line, [
      [`  ${diag.message}`, diag.severity === 'error' ? 'ErrorMsg' : 'WarningMsg']
    ]);
  }
}

// Update on changes
await buf[ATTACH]();
buf.listen('lines', () => {
  updateDiagnostics();
});

Buffer Comparison

const buf1 = await nvim.createBuffer(true, false);
const buf2 = await nvim.createBuffer(true, false);

await buf1.setLines(['line 1', 'line 2', 'line 3']);
await buf2.setLines(['line 1', 'line 2 modified', 'line 3']);

// Compare buffers
const lines1 = await buf1.lines;
const lines2 = await buf2.lines;

const diffNs = await nvim.createNamespace('diff');

for (let i = 0; i < Math.max(lines1.length, lines2.length); i++) {
  if (lines1[i] !== lines2[i]) {
    // Highlight differences
    await buf1.addHighlight({
      hlGroup: 'DiffDelete',
      line: i,
      srcId: diffNs
    });
    await buf2.addHighlight({
      hlGroup: 'DiffAdd',
      line: i,
      srcId: diffNs
    });
  }
}

Important Notes

  • Line indices are 0-based in the API (but 1-based in Vim/Neovim)
  • Column indices are 0-based
  • Buffer IDs never change for the lifetime of the buffer
  • Use namespaces to organize and clear related highlights/virtual text
  • Buffer events require explicit attachment with buf[ATTACH]()
  • The ATTACH and DETACH symbols must be imported separately
  • All operations return Promises and should be awaited
  • Buffer getters like lines, name, length return Promises
  • Buffer setters like name = "file" trigger async operations
  • Use clearNamespace() instead of deprecated clearHighlight()
  • Virtual text is cleared by clearing the namespace
  • Event callbacks receive buffer handle as first argument
  • Always detach event listeners when done to avoid memory leaks