Skip to content

Virtual Buffers & Panels ​

Buffers whose content a plugin writes, such as result lists and side panels, and groups of them shown as one tab.

Virtual Buffers ​

setLineTargets ​

Make lines of a buffer clickable: a click or Enter on a listed line opens what it points at.

js
editor.setLineTargets(bufferId, [
  { line: 0, path: "src/main.rs", target: 41, into: "code" },
  { line: 1, path: "src/lib.rs",  target: 12, into: "code" },
]);

line is the row in this buffer; target is the line to land on in the file, both 0-indexed. into names a pane by its setSplitLabel label; when it names no live pane the target opens beside this one, so an index never replaces itself with what you clicked.

The editor owns the behaviour, which is the point: a script that builds an index — a search result list, an error list, a review map — exits immediately, and a mouse_click handler would die with it. These targets keep working.

Replaces any previous targets for the buffer; pass [] to clear.

typescript
setLineTargets(bufferId: number, targets: LineTarget[]): boolean;

createVirtualBuffer ​

Create a virtual buffer in current split (async, returns buffer and split IDs)

Without splitId, the buffer opens as a new tab in the current split. This suits help panels, documentation and similar views that should open alongside other buffers rather than in a separate split.

typescript
createVirtualBuffer(opts: CreateVirtualBufferOptions): Promise<VirtualBufferResult>;
ParameterDescription
optsConfiguration for the virtual buffer

createVirtualBufferInSplit ​

Create a virtual buffer in a new split (async, returns buffer and split IDs)

By default the new split is stacked below the current pane. Use it for results panels, diagnostics, logs and similar. panelId makes updates idempotent: if a panel with that ID already exists, its content is replaced instead of creating a new split. Define the mode with defineMode first.

ratio is the share of the first pane, which is the existing content unless before is set.

ts
// First define the mode with keybindings
editor.defineMode("search-results", [
  ["Return", "search_goto"],
  ["q", "close_buffer"],
], true);

// Then create the buffer
const { bufferId, splitId } = await editor.createVirtualBufferInSplit({
  name: "*Search*",
  mode: "search-results",
  readOnly: true,
  entries: [
    { text: "src/main.rs:42: match\n", properties: { file: "src/main.rs", line: 42 } },
  ],
  ratio: 0.7, // existing pane keeps 70%, the panel gets 30%
  panelId: "search",
});
typescript
createVirtualBufferInSplit(opts: CreateVirtualBufferInSplitOptions): Promise<VirtualBufferResult>;

createVirtualBufferInExistingSplit ​

Create a virtual buffer in an existing split (async, returns buffer and split IDs)

typescript
createVirtualBufferInExistingSplit(opts: CreateVirtualBufferInExistingSplitOptions): Promise<VirtualBufferResult>;

setBufferMode ​

Switch a virtual buffer's mode — the keybinding set that applies while it is focused.

A panel that grows a text field (an in-panel filter) needs the single-key commands of its normal mode to stop firing while the user types; giving the panel a text-input mode for the duration does that without the plugin re-registering bindings. Modes are declared with defineMode; a mode name with no definition falls back to the global bindings.

typescript
setBufferMode(bufferId: number, mode: string): boolean;

setVirtualBufferContent ​

Replace a virtual buffer's content with a list of styled spans.

Spans are concatenated verbatim: they are runs of text, not lines, and nothing inserts separators for you. [{text:"a"},{text:"b"}] is the single line ab; for two lines, write [{text:"a\n"},{text:"b\n"}]. A buffer built without those newlines reports a plausible length and a lineCount of 1 — read it back from getBufferInfo if it matters, since otherwise the mistake is only visible on screen.

If you are an agent putting text in front of a human, prefer writing a file and opening it (splitWindow({ file }) / openFileInSplit(splitId, path)). A file buffer gives you syntax highlighting, search, save, and renders ANSI escape codes as colour — so command output can go straight in. Virtual buffers exist for plugin-owned panels: ephemeral, styled per span, driven by a mode's keybindings.

typescript
setVirtualBufferContent(bufferId: number, entriesArr: Record<string, unknown>[]): boolean;

getTextPropertiesAtCursor ​

Get text properties at cursor position (returns JS array)

Returns the properties object of every entry whose text covers the cursor, or an empty array when none does.

ts
const props = editor.getTextPropertiesAtCursor(bufferId);
if (props.length > 0 && typeof props[0].file === "string") {
  editor.openFile(props[0].file, props[0].line as number, 0);
}
typescript
getTextPropertiesAtCursor(bufferId: number): TextPropertiesAtCursor;
ParameterDescription
bufferIdID of the buffer to query

Buffer Groups ​

setPanelContent ​

Set the content of a panel within a buffer group

typescript
setPanelContent(groupId: number, panelName: string, entriesArr: Record<string, unknown>[]): boolean;

closeBufferGroup ​

Close a buffer group

typescript
closeBufferGroup(groupId: number): boolean;

setBufferGroupPanelVisible ​

Show or hide one panel of a buffer group, without tearing the group down.

The panel's buffer, its content and its scroll position all survive being hidden — only the group's split tree changes, so the remaining panels take over the freed space and a re-shown panel comes back where it was. Use it for optional sidebars a mode wants to toggle (a file list, a comments rail) instead of closing and recreating the group.

Hiding a panel that holds focus moves focus to a panel that is still rendered; focusing a hidden panel is a no-op. Returns false if the group or panel is unknown, or if the call would hide the group's last visible panel.

Queued, like every layout mutation: the returned bool only reports that the command was sent.

typescript
setBufferGroupPanelVisible(groupId: number, panelName: string, visible: boolean): boolean;

focusBufferGroupPanel ​

Focus a specific panel within a buffer group

typescript
focusBufferGroupPanel(groupId: number, panelName: string): boolean;

setBufferGroupPanelBuffer ​

Re-point a buffer-group's panel at a different buffer id.

Streaming plugins (e.g. git-log) allocate one file-backed buffer per item and call this on navigation to swap which buffer the panel displays — instead of mutating a single shared buffer's contents. Resolves with true on success.

typescript
setBufferGroupPanelBuffer(groupId: number, panelName: string, bufferId: number): Promise<boolean>;

createBufferGroup ​

Create a buffer group: multiple panels appearing as one tab.

typescript
createBufferGroup(name: string, mode: string, layout: unknown): Promise<BufferGroupResult>;

Composite Buffers ​

getCompositeCursorInfo ​

Cursor info for the active composite (side-by-side diff) buffer.

Resolves with null when the active buffer is not a composite buffer, otherwise an object describing the focused pane and the 0-indexed source line shown in each pane on the cursor's aligned row (null where a pane has no content on that row). Lets a plugin map a side-by-side cursor back to a concrete file version + line.

typescript
getCompositeCursorInfo(): Promise<{
  focusedPane: number;
  paneCount: number;
  lines: Array<number | null>;
} | null>;

createCompositeBuffer ​

Create a composite buffer (async)

A composite buffer displays several source buffers in a single tab/view area with a custom layout (side-by-side, stacked or unified). This is useful for diff views, merge conflict resolution, etc.

The options are checked when the call is made; options that don't match the type (for example a misspelled field name) make the call throw.

typescript
createCompositeBuffer(opts: TsCreateCompositeBufferOptions): Promise<number>;
ParameterDescription
optsConfiguration for the composite buffer

updateCompositeAlignment ​

Update alignment hunks for a composite buffer

The hunks are checked when the call is made; a hunk that doesn't match the type (for example one with a misspelled field name) makes the call throw.

typescript
updateCompositeAlignment(bufferId: number, hunks: TsCompositeHunk[]): boolean;
ParameterDescription
bufferIdThe composite buffer ID
hunksNew diff hunks for alignment

closeCompositeBuffer ​

Close a composite buffer

typescript
closeCompositeBuffer(bufferId: number): boolean;

flushLayout ​

Force-materialize render-dependent state (like layoutIfNeeded in UIKit). After calling this, commands that depend on view state created during rendering (e.g., compositeNextHunk) will work correctly.

typescript
flushLayout(): boolean;

setCompositeCursorLine ​

Put a composite buffer's cursor on the row showing line (0-indexed) of pane pane — 0 is the left/OLD pane — and scroll it into view.

initialFocusHunk on createCompositeBuffer lands the view on a hunk; this lands it on a line, which is what a plugin holding a concrete file position wants (following a review comment, or keeping the reader's place when a diff view flips between its unified and side-by-side layouts). No-op if that pane has no such line.

Queued, like every layout mutation: the returned bool only reports that the command was sent.

typescript
setCompositeCursorLine(bufferId: number, pane: number, line: number): boolean;

compositeNextHunk ​

Navigate to the next hunk in a composite buffer

typescript
compositeNextHunk(bufferId: number): boolean;

compositePrevHunk ​

Navigate to the previous hunk in a composite buffer

typescript
compositePrevHunk(bufferId: number): boolean;

Scroll Sync ​

createScrollSyncGroup ​

Create a scroll sync group for anchor-based synchronized scrolling

Used for side-by-side diff views where two panes need to scroll together. The plugin provides the group ID, which must be unique per plugin. Groups are removed automatically when the plugin unloads.

typescript
createScrollSyncGroup(groupId: number, leftSplit: number, rightSplit: number): boolean;
ParameterDescription
groupIdPlugin-assigned group ID
leftSplitThe left (primary) split; scroll is tracked in its lines
rightSplitThe right (secondary) split; follows via the anchors

setScrollSyncAnchors ​

Set sync anchors for a scroll sync group

Anchors map corresponding line numbers between the left and right buffers. Each anchor is a [leftLine, rightLine] pair. Entries with fewer than two numbers are ignored.

typescript
setScrollSyncAnchors(groupId: number, anchors: number[][]): boolean;
ParameterDescription
groupIdThe group ID passed to createScrollSyncGroup
anchors[leftLine, rightLine] pairs marking matching positions

removeScrollSyncGroup ​

Remove a scroll sync group

typescript
removeScrollSyncGroup(groupId: number): boolean;

View State ​

setViewState ​

Set plugin-managed per-buffer view state, as seen in the active split. getViewState returns the new value straight away; null or undefined deletes the key. For a buffer backed by a file, the state is saved with the workspace and comes back when it is restored.

typescript
setViewState(bufferId: number, key: string, value: unknown): boolean;

getViewState ​

Get plugin-managed per-buffer view state, as set by setViewState. undefined if missing.

typescript
getViewState(bufferId: number, key: string): unknown;

Released under the Apache 2.0 License