> ## 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.

# Plugin Utilities

> Reusable utilities for common plugin patterns

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:

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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.

```typescript theme={null}
await panel.open(entries: TextPropertyEntry[]): Promise<void>
```

**Example:**

```typescript theme={null}
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).

```typescript theme={null}
await panel.update(entries: TextPropertyEntry[]): Promise<void>
```

**Example:**

```typescript theme={null}
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).

```typescript theme={null}
await panel.toggle(entries: TextPropertyEntry[]): Promise<void>
```

#### close

Close the panel.

```typescript theme={null}
panel.close(): void
```

#### isOpen

Check if panel is currently open.

```typescript theme={null}
panel.isOpen(): boolean
```

### Complete Example

```typescript theme={null}
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

```typescript theme={null}
import { NavigationController } from "@plugins/lib";

const nav = new NavigationController({
  bufferId: myBufferId,
  highlightPrefix: "search",
  color: { r: 100, g: 100, b: 255 }
});
```

### NavigationOptions

```typescript theme={null}
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.

```typescript theme={null}
nav.moveUp(): void
```

#### moveDown

Move selection down one line.

```typescript theme={null}
nav.moveDown(): void
```

#### moveToTop

Move selection to first line.

```typescript theme={null}
nav.moveToTop(): void
```

#### moveToBottom

Move selection to last line.

```typescript theme={null}
nav.moveToBottom(): void
```

#### getSelectedIndex

Get current selection index (0-based).

```typescript theme={null}
nav.getSelectedIndex(): number
```

#### getSelectedLocation

Get text properties at current selection.

```typescript theme={null}
nav.getSelectedLocation(): Record<string, unknown> | null
```

#### clearHighlights

Clear all navigation highlights.

```typescript theme={null}
nav.clearHighlights(): void
```

### Complete Example

```typescript theme={null}
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

```typescript theme={null}
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

```typescript theme={null}
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:

```typescript theme={null}
import type {
  RGB,
  Location,
  PanelOptions,
  NavigationOptions,
  VirtualBufferOptions
} from "@plugins/lib";
```

### RGB

```typescript theme={null}
interface RGB {
  r: number;  // Red component (0-255)
  g: number;  // Green component (0-255)
  b: number;  // Blue component (0-255)
}
```

### Location

```typescript theme={null}
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:

```typescript theme={null}
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

<AccordionGroup>
  <Accordion title="Use PanelManager for persistent panels">
    PanelManager handles panel lifecycle automatically, including reusing panels with the same `panelId`.
  </Accordion>

  <Accordion title="Combine with NavigationController">
    For navigable result lists, use PanelManager to manage the panel and NavigationController for selection highlighting.
  </Accordion>

  <Accordion title="Consistent color schemes">
    Use theme-appropriate colors for highlights. Consider using theme colors from the editor config.
  </Accordion>

  <Accordion title="Clean up resources">
    Call `clearHighlights()` when closing panels to remove visual decorations.
  </Accordion>
</AccordionGroup>

## 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`
