Documentation Index Fetch the complete documentation index at: https://mintlify.com/sinelaw/fresh/llms.txt
Use this file to discover all available pages before exploring further.
The Fresh editor provides a plugin utilities library (plugins/lib/) with reusable components that abstract common plugin patterns. These utilities help you build plugins faster with less boilerplate.
Installation
Import utilities from the @plugins/lib module:
import { PanelManager , NavigationController , VirtualBufferFactory } from "@plugins/lib" ;
import type { RGB , Location , PanelOptions } from "@plugins/lib" ;
PanelManager
Manages the lifecycle of result panels (open, close, update, toggle). Simplifies creating persistent panels that can be shown, hidden, and updated.
Creating a Panel
import { PanelManager } from "@plugins/lib" ;
const panel = new PanelManager ({
name: "*Search Results*" ,
mode: "search-results" ,
panelId: "search" ,
ratio: 0.3 ,
keybindings: [
[ "Return" , "search_goto" ],
[ "q" , "close_buffer" ]
]
});
PanelOptions
interface PanelOptions {
name : string ; // Panel name (e.g., "*Search*")
mode : string ; // Mode for keybindings
panelId : string ; // Unique panel ID
ratio : number ; // Split ratio (0.0-1.0)
keybindings : [ string , string ][]; // Key bindings
direction ?: string ; // "horizontal" or "vertical"
}
Methods
open
Open the panel with initial content.
await panel . open ( entries : TextPropertyEntry []): Promise < void >
Example:
await panel . open ([
{ text: "Result 1 \n " , properties: { id: 1 } },
{ text: "Result 2 \n " , properties: { id: 2 } }
]);
update
Update panel content (panel must already be open).
await panel . update ( entries : TextPropertyEntry []): Promise < void >
Example:
await panel . update ([
{ text: "Updated result 1 \n " , properties: { id: 1 } },
{ text: "New result 3 \n " , properties: { id: 3 } }
]);
toggle
Toggle panel visibility (open if closed, close if open).
await panel . toggle ( entries : TextPropertyEntry []): Promise < void >
close
Close the panel.
isOpen
Check if panel is currently open.
Complete Example
import { PanelManager } from "@plugins/lib" ;
// Create panel manager
const searchPanel = new PanelManager ({
name: "*Search Results*" ,
mode: "search-results" ,
panelId: "search" ,
ratio: 0.3 ,
keybindings: [
[ "Return" , "search_goto" ],
[ "n" , "search_next" ],
[ "q" , "close_buffer" ]
]
});
// Perform search and show results
async function search ( query : string ) {
const results = await performSearch ( query );
const entries = results . map ( r => ({
text: ` ${ r . file } : ${ r . line } : ${ r . text } \n ` ,
properties: { file: r . file , line: r . line }
}));
if ( searchPanel . isOpen ()) {
await searchPanel . update ( entries );
} else {
await searchPanel . open ( entries );
}
}
NavigationController
Handles list navigation with selection tracking and visual highlighting. Perfect for implementing navigable result lists.
Creating a Navigator
import { NavigationController } from "@plugins/lib" ;
const nav = new NavigationController ({
bufferId: myBufferId ,
highlightPrefix: "search" ,
color: { r: 100 , g: 100 , b: 255 }
});
NavigationOptions
interface NavigationOptions {
bufferId : number ; // Buffer to navigate in
highlightPrefix : string ; // Prefix for highlight namespace
color : RGB ; // Highlight color
}
interface RGB {
r : number ; // Red (0-255)
g : number ; // Green (0-255)
b : number ; // Blue (0-255)
}
Methods
moveUp
Move selection up one line.
moveDown
Move selection down one line.
moveToTop
Move selection to first line.
moveToBottom
Move selection to last line.
getSelectedIndex
Get current selection index (0-based).
nav . getSelectedIndex (): number
getSelectedLocation
Get text properties at current selection.
nav . getSelectedLocation (): Record < string , unknown > | null
clearHighlights
Clear all navigation highlights.
nav . clearHighlights (): void
Complete Example
import { NavigationController } from "@plugins/lib" ;
// Create navigator
const nav = new NavigationController ({
bufferId: resultsBufferId ,
highlightPrefix: "search" ,
color: { r: 100 , g: 150 , b: 255 }
});
// Register navigation commands
globalThis . search_next = () => {
nav . moveDown ();
};
globalThis . search_prev = () => {
nav . moveUp ();
};
globalThis . search_goto = () => {
const location = nav . getSelectedLocation ();
if ( location ?. file && location ?. line ) {
editor . openFile ( location . file as string , location . line as number , 0 );
}
};
VirtualBufferFactory
Simplified creation of virtual buffers with less boilerplate.
Creating a Virtual Buffer
import { VirtualBufferFactory } from "@plugins/lib" ;
const bufferId = await VirtualBufferFactory . create ({
name: "*Output*" ,
mode: "output-mode" ,
entries: [
{ text: "Line 1 \n " , properties: { id: 1 } },
{ text: "Line 2 \n " , properties: { id: 2 } }
],
readOnly: true ,
ratio: 0.25 ,
panelId: "output"
});
VirtualBufferOptions
interface VirtualBufferOptions {
name : string ; // Buffer name
mode : string ; // Mode for keybindings
entries : TextPropertyEntry []; // Buffer content
readOnly ?: boolean ; // Read-only flag
ratio ?: number ; // Split ratio
panelId ?: string ; // Panel ID for updates
direction ?: string ; // Split direction
}
Type Definitions
The library exports common types for use in your plugins:
import type {
RGB ,
Location ,
PanelOptions ,
NavigationOptions ,
VirtualBufferOptions
} from "@plugins/lib" ;
RGB
interface RGB {
r : number ; // Red component (0-255)
g : number ; // Green component (0-255)
b : number ; // Blue component (0-255)
}
Location
interface Location {
file : string ; // File path
line : number ; // Line number
column ?: number ; // Optional column number
}
Complete Plugin Example
Here’s a complete plugin using all three utilities:
import {
PanelManager ,
NavigationController ,
VirtualBufferFactory
} from "@plugins/lib" ;
import type { RGB } from "@plugins/lib" ;
// Define mode
editor . defineMode ( "file-search" , "special" , [
[ "Return" , "file_search_open" ],
[ "j" , "file_search_next" ],
[ "k" , "file_search_prev" ],
[ "q" , "close_buffer" ]
], true );
// Create panel manager
const panel = new PanelManager ({
name: "*File Search*" ,
mode: "file-search" ,
panelId: "file-search" ,
ratio: 0.3 ,
keybindings: [
[ "Return" , "file_search_open" ],
[ "j" , "file_search_next" ],
[ "k" , "file_search_prev" ],
[ "q" , "close_buffer" ]
]
});
let navigator : NavigationController | null = null ;
// Search for files
async function searchFiles ( pattern : string ) {
const result = await editor . spawnProcess ( "find" , [
"." ,
"-name" ,
`* ${ pattern } *` ,
"-type" ,
"f"
]);
if ( result . exit_code !== 0 ) {
editor . setStatus ( "No files found" );
return ;
}
const files = result . stdout
. split ( " \n " )
. filter ( f => f . length > 0 );
const entries = files . map ( file => ({
text: ` ${ file } \n ` ,
properties: { file: file , line: 1 }
}));
await panel . open ( entries );
// Create navigator
const bufferId = editor . getActiveBufferId ();
navigator = new NavigationController ({
bufferId: bufferId ,
highlightPrefix: "file-search" ,
color: { r: 100 , g: 150 , b: 255 }
});
editor . setStatus ( `Found ${ files . length } files` );
}
// Navigation commands
globalThis . file_search_next = () => {
navigator ?. moveDown ();
};
globalThis . file_search_prev = () => {
navigator ?. moveUp ();
};
globalThis . file_search_open = () => {
const location = navigator ?. getSelectedLocation ();
if ( location ?. file ) {
editor . openFile ( location . file as string , 1 , 0 );
}
};
// Register command
editor . registerCommand (
"File: Search" ,
"Search for files by name" ,
"file_search" ,
"normal" ,
"plugin"
);
globalThis . file_search = () => {
editor . startPrompt ( "File search: " , "file-search-input" );
};
// Handle prompt
globalThis . handleFileSearchPrompt = async ( data ) => {
if ( data . prompt_type === "file-search-input" && data . confirmed ) {
await searchFiles ( data . value );
}
};
editor . on ( "prompt_submit" , "handleFileSearchPrompt" );
Best Practices
Use PanelManager for persistent panels
PanelManager handles panel lifecycle automatically, including reusing panels with the same panelId.
Combine with NavigationController
For navigable result lists, use PanelManager to manage the panel and NavigationController for selection highlighting.
Use theme-appropriate colors for highlights. Consider using theme colors from the editor config.
Call clearHighlights() when closing panels to remove visual decorations.
Source Files
For full API details, see the source files in plugins/lib/:
plugins/lib/panel-manager.ts
plugins/lib/navigation-controller.ts
plugins/lib/virtual-buffer-factory.ts
plugins/lib/types.ts