Virtual buffers are special buffers created by plugins to display structured data like search results, diagnostics, git logs, or file explorers. Unlike regular buffers, they’re typically read-only and contain metadata (text properties) that plugins can query.
Creating Virtual Buffers
createVirtualBufferInSplit
Create a virtual buffer in a new split below the current pane.
createvirtualBufferInSplit(options: CreateVirtualBufferOptions): Promise<CreateVirtualBufferResult>
options
CreateVirtualBufferOptions
required
Buffer configuration object
returns
Promise<CreateVirtualBufferResult>
Result containing buffer_id and optional split_id
CreateVirtualBufferOptions:
interface CreateVirtualBufferOptions {
name: string; // Buffer name (convention: "*Name*")
mode: string; // Mode for keybindings
read_only: boolean; // Prevent text modifications
entries: TextPropertyEntry[]; // Content with embedded metadata
ratio: number; // Split ratio (0.3 = 30% of space)
direction?: string | null; // "horizontal" or "vertical"
panel_id?: string | null; // For idempotent updates
show_line_numbers?: boolean | null; // Show line numbers
show_cursors?: boolean | null; // Show cursor
editing_disabled?: boolean | null; // Disable editing
line_wrap?: boolean | null; // Enable line wrapping
}
TextPropertyEntry:
interface TextPropertyEntry {
text: string; // Text to display (include \n for separate lines)
properties: Record<string, unknown>; // Arbitrary metadata
}
The panel_id enables idempotent updates: if a panel with that ID exists, its content is replaced instead of creating a new split. Define the mode with defineMode first.
Example:
// First define the mode with keybindings
editor.defineMode("search-results", "special", [
["Return", "search_goto"],
["q", "close_buffer"]
], true);
// Then create the buffer
const result = await editor.createVirtualBufferInSplit({
name: "*Search*",
mode: "search-results",
read_only: true,
entries: [
{
text: "src/main.rs:42: match found\n",
properties: { file: "src/main.rs", line: 42 }
},
{
text: "src/lib.rs:15: another match\n",
properties: { file: "src/lib.rs", line: 15 }
}
],
ratio: 0.3,
panel_id: "search"
});
editor.debug(`Created buffer ${result.buffer_id}`);
createVirtualBufferInExistingSplit
Create a virtual buffer in an existing split.
createVirtualBufferInExistingSplit(options: CreateVirtualBufferInExistingSplitOptions): Promise<number>
options
CreateVirtualBufferInExistingSplitOptions
required
Configuration for the virtual buffer
CreateVirtualBufferInExistingSplitOptions:
interface CreateVirtualBufferInExistingSplitOptions {
name: string;
mode: string;
read_only: boolean;
entries: TextPropertyEntry[];
split_id: number; // Target split ID
show_line_numbers?: boolean | null;
show_cursors?: boolean | null;
editing_disabled?: boolean | null;
line_wrap?: boolean | null;
}
Example:
const splitId = editor.getActiveSplitId();
const bufferId = await editor.createVirtualBufferInExistingSplit({
name: "*Output*",
mode: "output-mode",
read_only: true,
entries: [
{ text: "Build output:\n", properties: {} },
{ text: "Success!\n", properties: { status: "success" } }
],
split_id: splitId
});
createVirtualBuffer
Create a virtual buffer in the current split as a new tab.
createVirtualBuffer(options: CreateVirtualBufferInCurrentSplitOptions): Promise<number>
options
CreateVirtualBufferInCurrentSplitOptions
required
Configuration for the virtual buffer
CreateVirtualBufferInCurrentSplitOptions:
interface CreateVirtualBufferInCurrentSplitOptions {
name: string;
mode: string;
read_only: boolean;
entries: TextPropertyEntry[];
show_line_numbers?: boolean | null;
show_cursors?: boolean | null;
editing_disabled?: boolean | null;
hidden_from_tabs?: boolean | null; // Hide from tabs
}
Example:
// Create help buffer in current split
const bufferId = await editor.createVirtualBuffer({
name: "*Help*",
mode: "help-mode",
read_only: true,
entries: [
{ text: "# Help\n", properties: {} },
{ text: "Press q to close\n", properties: {} }
],
show_line_numbers: false
});
Managing Virtual Buffers
setVirtualBufferContent
Set the content of a virtual buffer with text properties.
setVirtualBufferContent(buffer_id: number, entries: TextPropertyEntry[]): boolean
entries
TextPropertyEntry[]
required
Array of text entries with properties
true if content was set successfully
Example:
// Update search results
editor.setVirtualBufferContent(bufferId, [
{ text: "Updated results:\n", properties: {} },
{ text: "src/new.rs:10: found\n", properties: { file: "src/new.rs", line: 10 } }
]);
getTextPropertiesAtCursor
Get text properties at the cursor position in a buffer.
getTextPropertiesAtCursor(buffer_id: number): Record<string, unknown>[]
ID of the buffer to query
returns
Record<string, unknown>[]
Array of property objects at cursor position
Example:
// Implement "go to definition" from search results
globalThis.searchGoto = () => {
const bufferId = editor.getActiveBufferId();
const props = editor.getTextPropertiesAtCursor(bufferId);
if (props.length > 0 && props[0].file && props[0].line) {
editor.openFile(props[0].file as string, props[0].line as number, 0);
}
};
Modes and Keybindings
defineMode
Define a buffer mode with keybindings.
defineMode(
name: string,
parent: string,
bindings: [string, string][],
read_only: boolean
): boolean
Mode name (e.g., “diagnostics-list”)
Parent mode name for inheritance (e.g., “special”), or null
bindings
[string, string][]
required
Array of [key_string, command_name] pairs
Whether buffers in this mode are read-only
true if mode was defined successfully
Example:
editor.defineMode("diagnostics-list", "special", [
["Return", "diagnostics_goto"],
["n", "diagnostics_next"],
["p", "diagnostics_prev"],
["q", "close_buffer"]
], true);
Split Management
getActiveSplitId
Get the ID of the focused split pane.
getActiveSplitId(): number
focusSplit
Focus a specific split.
focusSplit(split_id: number): boolean
setSplitBuffer
Set the buffer displayed in a specific split.
setSplitBuffer(split_id: number, buffer_id: number): boolean
ID of the buffer to display
closeSplit
Close a split (if not the last one).
closeSplit(split_id: number): boolean
setSplitRatio
Set the ratio of a split container.
setSplitRatio(split_id: number, ratio: number): boolean
Ratio between 0.0 and 1.0 (0.5 = equal split)
distributeSplitsEvenly
Distribute all visible splits evenly.
distributeSplitsEvenly(): boolean
This adjusts the ratios of all container splits so each leaf split gets equal space.
Complete Example: Search Results Panel
// Define the mode
editor.defineMode("search-results", "special", [
["Return", "search_goto"],
["n", "search_next"],
["p", "search_prev"],
["q", "close_buffer"]
], true);
// Search function
async function performSearch(query: string) {
// Run search command
const result = await editor.spawnProcess("rg", [
"--line-number",
"--no-heading",
query
]);
if (result.exit_code !== 0) {
editor.setStatus("No matches found");
return;
}
// Parse results
const entries: TextPropertyEntry[] = [];
for (const line of result.stdout.split("\n")) {
if (!line) continue;
const match = line.match(/^([^:]+):(\d+):(.*)$/);
if (match) {
const [, file, lineNum, text] = match;
entries.push({
text: `${file}:${lineNum}: ${text}\n`,
properties: {
file: file,
line: parseInt(lineNum)
}
});
}
}
// Create or update panel
const panel = await editor.createVirtualBufferInSplit({
name: "*Search*",
mode: "search-results",
read_only: true,
entries: entries,
ratio: 0.3,
panel_id: "search" // Reuse existing panel
});
editor.setStatus(`Found ${entries.length} matches`);
}
// Go to selected result
globalThis.search_goto = () => {
const bufferId = editor.getActiveBufferId();
const props = editor.getTextPropertiesAtCursor(bufferId);
if (props.length > 0 && props[0].file && props[0].line) {
editor.openFile(props[0].file as string, props[0].line as number, 0);
}
};
// Register command
editor.registerCommand(
"Search: Grep",
"Search for text in files",
"search_grep",
"normal",
"plugin"
);
globalThis.search_grep = () => {
editor.startPrompt("Search: ", "search-input");
};
// Handle prompt submission
globalThis.handleSearchPrompt = async (data) => {
if (data.prompt_type === "search-input" && data.confirmed) {
await performSearch(data.value);
}
};
editor.on("prompt_submit", "handleSearchPrompt");