Buffers & Editing
Read and change buffer text, move cursors, open, save and close files, and search.
Buffer Queries
getActiveBufferId
Get the active buffer ID (0 if none) This is the buffer in the focused editor pane. Use the ID with other buffer operations such as insertText.
getActiveBufferId(): number;listBuffers
List all open buffers - returns array of BufferInfo objects
listBuffers(): BufferInfo[];getBufferPath
Get file path for a buffer Returns an empty string for unsaved buffers, virtual buffers, or an unknown buffer ID. Use the path to determine file type, construct related paths, or display to the user.
getBufferPath(bufferId: number): string;getBufferLength
Get buffer length in bytes Returns 0 if the buffer doesn't exist.
getBufferLength(bufferId: number): number;isBufferModified
Check if buffer has unsaved changes Returns false if the buffer doesn't exist. Setting a virtual buffer's content does not mark it modified.
isBufferModified(bufferId: number): boolean;getBufferInfo
Get buffer info by ID Returns null if the buffer doesn't exist.
getBufferInfo(bufferId: number): BufferInfo | null;getLineStartPosition
Get the byte offset of the start of a line (0-indexed line number) Returns null if the line number is out of range
getLineStartPosition(line: number): Promise<number | null>;getLineEndPosition
Get the byte offset of the end of a line (0-indexed line number) Returns the position after the last character of the line (before newline) Returns null if the line number is out of range
getLineEndPosition(line: number): Promise<number | null>;getBufferLineCount
Get the total number of lines in the active buffer Returns null if buffer not found
getBufferLineCount(): Promise<number | null>;findBufferByPath
Find buffer by file path, returns buffer ID or 0 if not found
findBufferByPath(path: string): number;getBufferSavedDiff
Get diff between buffer content and last saved version
getBufferSavedDiff(bufferId: number): BufferSavedDiff | null;getBufferText
Read buffer text.
getBufferText(id) returns the whole buffer — the common case, and the one that used to require reading length from getBufferInfo and passing it back. getBufferText(id, start, end) still reads a byte range, so existing callers are unaffected.
Byte offsets, not character or line offsets.
getBufferText(bufferId: number, start?: number, end?: number): Promise<string>;Cursors & Viewport
getCursorPosition
Get cursor position in active buffer The position is a byte offset, not a character index. Returns 0 if there is no cursor. For multiple cursors, use getAllCursors.
getCursorPosition(): number;getPrimaryCursor
Get primary cursor info for active buffer The result includes the cursor's selection, if any. Returns null if there is no active cursor.
getPrimaryCursor(): CursorInfo | null;getAllCursors
Get all cursors for active buffer
getAllCursors(): CursorInfo[];getAllCursorPositions
Get all cursor positions as byte offsets
Returns an empty array if there are no cursors. For selection info use getAllCursors instead.
getAllCursorPositions(): number[];getViewport
Get viewport info for active buffer
getViewport(): ViewportInfo | null;getCursorLine
Get the line number (0-indexed) of the primary cursor.
@deprecated Use getPrimaryCursor()?.line instead. This accessor cannot represent "line index unavailable" (huge files before their line scan) — it returns 0 in that case, indistinguishable from a real first line. getPrimaryCursor().line is number | null and also covers every cursor via getAllCursors().
getCursorLine(): number;scrollToLineCenter
Scroll a split to center a specific line in the viewport Line is 0-indexed (0 = first line)
scrollToLineCenter(splitId: number, bufferId: number, line: number): boolean;scrollBufferToLine
Scroll any split/panel showing buffer_id so line is visible. Unlike scrollToLineCenter, this does not require a split id — it updates every split's viewport whose active buffer is the given buffer, including inner leaves of a buffer group. Use this from a panel plugin to keep the user's "selected" row in view after arrow-key navigation (the plugin's own selection state isn't automatically reflected in the buffer cursor, so the core-driven viewport would otherwise stay put).
scrollBufferToLine(bufferId: number, line: number): boolean;setBufferCursor
Set cursor position in a buffer
The cursor moves in every split showing the buffer, and each viewport scrolls to keep it visible.
setBufferCursor(bufferId: number, position: number): boolean;| Parameter | Description |
|---|---|
position | Byte offset position for the cursor |
setBufferShowCursors
Toggle whether the editor draws a native caret in this buffer.
Buffer-group panel buffers default to show_cursors = false, which also blocks all native movement actions in action_to_events. Plugins that want native cursor motion in a panel (e.g. magit-style row navigation) call this with true after createBufferGroup returns.
setBufferShowCursors(bufferId: number, show: boolean): boolean;Text Editing
insertText
Insert text at a byte position in a buffer.
The text is inserted before the byte at position, and all text after it shifts. The operation is asynchronous: the return value is true if the command was sent, not that the edit was applied.
insertText(bufferId: number, position: number, text: string): boolean;| Parameter | Description |
|---|---|
position | Byte offset where text will be inserted (0 to buffer length, at a UTF-8 char boundary) |
text | UTF-8 text to insert |
deleteRange
Delete a byte range from a buffer.
Both positions must be at valid UTF-8 char boundaries. The operation is asynchronous: the return value is true if the command was sent, not that the edit was applied.
deleteRange(bufferId: number, start: number, end: number): boolean;| Parameter | Description |
|---|---|
start | Start byte offset (inclusive) |
end | End byte offset (exclusive) |
insertAtCursor
Insert text at cursor position in active buffer
insertAtCursor(text: string): boolean;Opening, Saving & Closing
saveBufferToPath
Save a buffer to a specific file path Used by :w filename to save unnamed buffers or save-as
saveBufferToPath(bufferId: number, path: string): boolean;openFile
Open a file, optionally at a specific line/column.
editor.openFile(path) is the whole request most of the time.
openFile(path: string, line?: number | null, column?: number | null): boolean;| Parameter | Description |
|---|---|
path | File path to open |
line | 1-based line number to jump to; omit or pass null for no jump |
column | 1-based column (a byte offset within the line) to jump to; omit or pass null for no jump |
openFileInBackground
Open a file in the background — no focus change, no active-split mutation. windowId defaults to the active session. Setting it to an inactive session id loads the file's buffer and adds it as a tab in that session's stashed split tree, ready to be revealed on next dive. Orchestrator uses this to populate worktree sessions with preselected files.
Pairs with createTerminal's windowId for setting up an inactive session's contents without diving.
openFileInBackground(path: string, windowId?: number): boolean;openFileInSplit
Open a file in a specific split
openFileInSplit(splitId: number, path: string, line?: number, column?: number): boolean;| Parameter | Description |
|---|---|
splitId | The split ID to open the file in |
path | File path to open |
line | 1-based line number to jump to; defaults to the first line |
column | 1-based column (a byte offset within the line) to jump to; defaults to the first column |
previewFileInSplit
Preview a file in a specific split, as the editor's single preview (ephemeral) tab — what the File Explorer does on a single click, pointed at a split you name.
Use this instead of openFileInSplit while the user is browsing a list of locations — search results, references, diagnostics — and call it again as the selection moves. The previous preview is replaced rather than piling up as tabs, a file the user already had open is switched to and never demoted to a preview, and the buffer becomes a permanent tab as soon as they commit to it (open it, edit it, or move focus to another split). Focus does not move, so the panel or prompt driving the browse keeps the keys.
line / column are 1-indexed and optional. Returns false only when the editor can no longer take commands (for example while it shuts down); a file that cannot be previewed (unreadable, or large enough that loading it would have to ask the user about its encoding) is skipped quietly on the editor side — a browse never raises a dialog. Pair with dismissPreview when the browse ends without a choice.
previewFileInSplit(splitId: number, path: string, line?: number, column?: number): boolean;dismissPreview
Drop the preview tab opened by previewFileInSplit, if it is still the preview — the browse ended without a choice (the user cancelled the prompt), so the split goes back to what it was showing.
A preview the user edited is kept and promoted to a permanent tab: their typing was the commitment. Safe to call when there is no preview.
dismissPreview(): boolean;openFileStreaming
Open path as a regular buffer in forced large-file (file-backed) mode. The file is created (empty) if missing — designed for buffers that will be filled by a concurrent spawnProcess with stdoutTo. Resolves with the new buffer's id, or null on failure.
Pair with refreshBufferFromDisk to grow the buffer as the streaming write advances.
openFileStreaming(path: string): Promise<number | null>;refreshBufferFromDisk
Re-stat the file backing bufferId and extend the buffer if the file has grown. Resolves with the new total byte length, or null if the buffer has no file path or doesn't exist.
Used to drive a streaming display: while a spawnProcess writes to a temp file, the plugin polls this on a timer so the buffer length tracks the file length.
refreshBufferFromDisk(bufferId: number): Promise<number | null>;showBuffer
Show a buffer in the current split
showBuffer(bufferId: number): boolean;closeBuffer
Close a buffer. Pass force: true to discard unsaved changes.
A closed buffer is removed from all splits that show it.
A modified buffer is not closed unless force is set — the user's unsaved edits are not a plugin's to throw away. A scratch buffer the plugin created and filled itself counts as modified, so disposing of one needs closeBuffer(id, true).
The returned boolean is "the request was delivered", not "the buffer closed": this call is fire-and-forget, and the editor decides afterwards. A refusal is logged editor-side but is invisible here, so confirm with listBuffers() (after await editor.flush()) when it matters. Without force the sequence that used to be required was delete-the-contents, saveBufferToPath, then close — three round-trips, the first two of which returned true while achieving nothing.
closeBuffer(bufferId: number, force?: boolean | null): boolean;closeOtherBuffersInSplit
Close other buffers in split
closeOtherBuffersInSplit(bufferId: number, splitId: number): boolean;closeAllBuffersInSplit
Close all buffers in split
closeAllBuffersInSplit(splitId: number): boolean;closeBuffersToRightInSplit
Close buffers to right in split
closeBuffersToRightInSplit(bufferId: number, splitId: number): boolean;closeBuffersToLeftInSplit
Close buffers to left in split
closeBuffersToLeftInSplit(bufferId: number, splitId: number): boolean;moveTabToLeft
Move the active tab to the left in the active split
moveTabToLeft(): boolean;moveTabToRight
Move the active tab to the right in the active split
moveTabToRight(): boolean;markFileReadOnly
Mark the buffer backing path read-only. Race-free right after openFile because both are FIFO commands.
markFileReadOnly(path: string): boolean;Clipboard
copyToClipboard
copyToClipboard(text: string): void;setClipboard
Copy text to the clipboard. Copies the text to both the internal and the system clipboard. The system copy uses OSC 52 and arboard, as enabled in the clipboard settings.
setClipboard(text: string): void;Search & Replace
hasActiveSearch
Returns true when search highlights are currently active in the buffer. Becomes true after a search is confirmed; false once cleared.
hasActiveSearch(): boolean;grepProject
Project-wide grep search (async) Searches all files in the project, respecting .gitignore. Open buffers with dirty edits are searched in-memory.
grepProject(pattern: string, fixedString: boolean | null, caseSensitive: boolean | null, maxResults: number | null, wholeWords: boolean | null): Promise<GrepMatch[]>;beginSearch
Begin a streaming project-wide search and return a SearchHandle. The producer (host) writes matches at full speed into shared state; the consumer drains via handle.take() at its own cadence. Call handle.cancel() to abort.
beginSearch(pattern: string, opts?: {
fixedString?: boolean;
caseSensitive?: boolean;
maxResults?: number;
wholeWords?: boolean;
sourceBufferId?: number;
fileGlob?: string;
}): SearchHandle;replaceInFile
Replace matches in a file's buffer (async) Opens the file if not already in a buffer, applies edits via the buffer model, and saves. All edits are grouped as a single undo action.
Pass regex — the search the matches came from — to treat replacement as a template: $1, ${name} and \n are expanded per match. Without it, replacement is written as is.
replaceInFile(filePath: string, matches: number[][], replacement: string, bufferId?: number, regex?: {
pattern: string;
caseSensitive?: boolean;
wholeWords?: boolean;
}): Promise<ReplaceResult>;Diff Baselines
registerDiffBaseline
Register a diff baseline for a buffer (async). kind is one of "saved" | "disk" | "gitRef" | "gitIndex"; gitRef carries the ref for kind "gitRef". Resolves with the baseline id once the reference content is loaded host-side — no file content ever crosses the plugin bridge. Baselines are dropped automatically when their buffer closes, or explicitly via releaseDiffBaseline.
registerDiffBaseline(bufferId: number, kind: string, gitRef: string | null): Promise<number>;diffAgainstBaseline
Diff a buffer's live content against a registered baseline (async). Resolves with a DiffBaselineResult; check its revision against the buffer's current version before anchoring decorations on the hunks.
diffAgainstBaseline(bufferId: number, baselineId: number): Promise<DiffBaselineResult>;diffBaselinePair
Diff two registered baselines against each other (async) — e.g. disk vs HEAD, the git-gutter comparison. Resolves with a DiffBaselineResult whose revision is 0.
diffBaselinePair(oldBaselineId: number, newBaselineId: number): Promise<DiffBaselineResult>;getBaselineLines
Fetch baseline lines for (startLine, count) ranges in one batched call (async). Lines come back without trailing newlines, grouped per requested range — fetch only the old-side lines a diff view actually renders.
getBaselineLines(baselineId: number, ranges: number[][]): Promise<string[][]>;refreshDiffBaseline
Reload a baseline's reference content (async; call after a HEAD move or an external write). Resolves once the fresh content is serving.
refreshDiffBaseline(baselineId: number): Promise<void>;releaseDiffBaseline
Drop a registered diff baseline.
releaseDiffBaseline(baselineId: number): void;