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

# Common Plugin Patterns

> Learn common patterns and best practices for building Fresh plugins with real-world examples

This guide covers common patterns used in Fresh plugins, with examples from the plugin library.

## Text Highlighting with Overlays

Overlays allow you to visually highlight text without modifying buffer content. They're perfect for search results, diagnostics, or temporary annotations.

<CodeGroup>
  ```typescript Basic Highlighting theme={null}
  globalThis.highlight_word = function(): void {
    const bufferId = editor.getActiveBufferId();
    const cursor = editor.getCursorPosition();

    // Highlight 5 bytes starting at cursor with yellow background
    editor.addOverlay(
      bufferId,
      "my_highlight:1",  // Unique ID (use prefix for batch removal)
      cursor,
      cursor + 5,
      255, 255, 0,       // RGB color
      false              // underline
    );
  };

  // Later, remove all highlights with the prefix
  editor.removeOverlaysByPrefix(bufferId, "my_highlight:");
  ```

  ```typescript Advanced Overlays theme={null}
  // From bookmarks.ts - Using namespace API
  globalThis.bookmark_add = function(): void {
    const bufferId = editor.getActiveBufferId();
    const cursorPos = editor.getCursorPosition();

    // Add visual indicator with bookmark namespace
    editor.addOverlay(bufferId, "bookmark", position, position + 1, {
      fg: [0, 128, 255],  // Teal color
      underline: true,
    });
  };

  // Clear all bookmarks at once using namespace
  globalThis.bookmark_clear = function(): void {
    const bufferId = editor.getActiveBufferId();
    editor.clearNamespace(bufferId, "bookmark");
  };
  ```
</CodeGroup>

<Tip>
  Use namespace prefixes for overlays to enable batch removal. This is essential for plugins that create many temporary highlights.
</Tip>

## Creating Results Panels

Virtual buffers are ideal for displaying search results, diagnostics, or any structured data that users can navigate.

<Steps>
  <Step title="Define a Custom Mode">
    Create keybindings specific to your results panel:

    ```typescript theme={null}
    editor.defineMode(
      "my-results",      // mode name
      "special",         // parent mode (or null)
      [
        ["Return", "my_goto_result"],
        ["q", "close_buffer"]
      ],
      true              // read-only
    );
    ```
  </Step>

  <Step title="Create the Virtual Buffer">
    Build entries with embedded metadata:

    ```typescript theme={null}
    globalThis.show_results = async function(): Promise<void> {
      await editor.createVirtualBufferInSplit({
        name: "*Results*",
        mode: "my-results",
        read_only: true,
        entries: [
          {
            text: "src/main.rs:42: found match\n",
            properties: { file: "src/main.rs", line: 42 }
          },
          {
            text: "src/lib.rs:100: another match\n",
            properties: { file: "src/lib.rs", line: 100 }
          }
        ],
        ratio: 0.3,           // Panel takes 30% of height
        panel_id: "my-results" // Reuse panel if it exists
      });
    };
    ```
  </Step>

  <Step title="Handle Navigation">
    Implement the "go to" action using embedded properties:

    ```typescript theme={null}
    globalThis.my_goto_result = function(): void {
      const bufferId = editor.getActiveBufferId();
      const props = editor.getTextPropertiesAtCursor(bufferId);

      if (props.length > 0 && props[0].file) {
        editor.openFile(props[0].file, props[0].line, 0);
      }
    };

    editor.registerCommand(
      "my_goto_result",
      "Go to result",
      "my_goto_result",
      "my-results"
    );
    ```
  </Step>
</Steps>

### Real Example: Diagnostics Panel

From `diagnostics_panel.ts` - a production virtual buffer implementation:

```typescript theme={null}
interface DiagnosticItem {
  uri: string;
  file: string;
  line: number;
  column: number;
  message: string;
  severity: number; // 1=error, 2=warning, 3=info, 4=hint
}

const entries = diagnostics.map(diag => ({
  text: `[ERROR] ${diag.file}:${diag.line}:${diag.column} - ${diag.message}\n`,
  properties: {
    severity: "error",
    location: { 
      file: diag.file, 
      line: diag.line, 
      column: diag.column 
    },
    message: diag.message,
  }
}));

await editor.createVirtualBufferInSplit({
  name: "*Diagnostics*",
  mode: "diagnostics-list",
  readOnly: true,
  entries: entries,
  ratio: 0.3,
  panelId: "diagnostics",
  showLineNumbers: false,
  showCursors: true,
});
```

<Note>
  Virtual buffers automatically persist when reopened with the same `panel_id`. This provides a seamless UX for results panels.
</Note>

## Running External Commands

Use `spawnProcess` to integrate with external tools. All process operations are async.

<CodeGroup>
  ```typescript Basic Command theme={null}
  globalThis.run_tests = async function(): Promise<void> {
    editor.setStatus("Running tests...");

    const result = await editor.spawnProcess("cargo", ["test"], null);

    if (result.exit_code === 0) {
      editor.setStatus("Tests passed!");
    } else {
      editor.setStatus(`Tests failed: ${result.stderr.split('\n')[0]}`);
    }
  };
  ```

  ```typescript With Working Directory theme={null}
  // From async_demo.ts
  globalThis.async_with_cwd = async function(): Promise<void> {
    const result = await editor.spawnProcess(
      "pwd", 
      [], 
      "/tmp"  // custom working directory
    );
    const dir = result.stdout.trim();
    editor.setStatus(`Working dir was: ${dir}`);
  };
  ```

  ```typescript Git Integration theme={null}
  // From git_grep.ts
  async function searchWithGitGrep(query: string): Promise<GrepMatch[]> {
    const cwd = editor.getCwd();
    const result = await editor.spawnProcess(
      "git",
      ["grep", "-n", "--column", "-I", "--", query],
      cwd
    );

    if (result.exit_code === 0) {
      return parseGrepOutput(result.stdout, 100);
    }
    return [];
  }
  ```

  ```typescript Error Handling theme={null}
  globalThis.safe_command = async function(): Promise<void> {
    try {
      const result = await editor.spawnProcess("my-command", ["--arg"]);
      
      if (result.exit_code !== 0) {
        editor.setStatus(`Command failed: ${result.stderr}`);
        return;
      }
      
      // Process successful output
      const output = result.stdout.trim();
      editor.setStatus(`Success: ${output}`);
      
    } catch (e) {
      editor.setStatus(`Error: ${e}`);
    }
  };
  ```
</CodeGroup>

<Tip>
  Always handle both `exit_code` and exceptions. Non-zero exit codes don't throw errors - check them explicitly.
</Tip>

## LSP Requests

Plugins can invoke custom LSP methods for language-specific features like type hierarchy, switch header, or clangd extensions.

```typescript theme={null}
globalThis.switch_header = async function(): Promise<void> {
  const bufferId = editor.getActiveBufferId();
  const path = editor.getBufferPath(bufferId);
  const uri = `file://${path}`;
  
  const result = await editor.sendLspRequest(
    "cpp",                          // target language ID
    "textDocument/switchSourceHeader", // LSP method
    { textDocument: { uri } }       // method parameters
  );
  
  if (result && typeof result === "string") {
    editor.openFile(result, 0, 0);
  }
};
```

<Note>
  The method name should be the full LSP method (e.g., `textDocument/typeHierarchy`). Response handling is your responsibility.
</Note>

## File System Operations

Fresh provides async file I/O APIs for reading, writing, and checking files.

```typescript theme={null}
globalThis.process_file = async function(): Promise<void> {
  const path = editor.getBufferPath(editor.getActiveBufferId());

  if (editor.fileExists(path)) {
    const content = await editor.readFile(path);
    const modified = content.replace(/TODO/g, "DONE");
    await editor.writeFile(path + ".processed", modified);
    
    editor.setStatus(`Processed file saved to ${path}.processed`);
  } else {
    editor.setStatus("File does not exist");
  }
};
```

<Warning>
  `writeFile` will overwrite existing files without confirmation. Always check file existence first if needed.
</Warning>

## Event Handling

Plugins can react to editor events using the `editor.on()` API.

<CodeGroup>
  ```typescript Diagnostics Updated theme={null}
  // From diagnostics_panel.ts
  globalThis.on_diagnostics_updated = function(data: {
    uri: string;
    count: number;
  }): void {
    if (isOpen) {
      provider.notify(); // Refresh the panel
    }
  };

  editor.on("diagnostics_updated", "on_diagnostics_updated");
  ```

  ```typescript Buffer Activated theme={null}
  globalThis.on_buffer_activated = function(data: {
    buffer_id: number;
  }): void {
    const path = editor.getBufferPath(data.buffer_id);
    if (!path) return; // Skip virtual buffers

    // Update context when switching files
    sourceBufferId = data.buffer_id;
    
    if (!showAllFiles) {
      provider.notify();
    }
  };

  editor.on("buffer_activated", "on_buffer_activated");
  ```

  ```typescript Prompt Events theme={null}
  // From bookmarks.ts
  globalThis.onBookmarkSelectConfirmed = function(args: {
    prompt_type: string;
    selected_index: number | null;
    input: string;
  }): boolean {
    if (args.prompt_type !== "bookmark-select") {
      return true; // Not our prompt
    }

    if (args.selected_index !== null) {
      const bookmark = bookmarks.get(bookmarkIds[args.selected_index]);
      editor.openFile(bookmark.path, bookmark.line, bookmark.column);
    }

    return true; // Event handled
  };

  editor.on("prompt_confirmed", "onBookmarkSelectConfirmed");
  editor.on("prompt_cancelled", "onBookmarkSelectCancelled");
  ```
</CodeGroup>

<Note>
  Event handlers should return `true` if they handled the event, or `false` to let it propagate.
</Note>

## Interactive Prompts

Create rich selection interfaces with suggestions and fuzzy matching.

```typescript theme={null}
// Start a prompt session
globalThis.bookmark_select = function(): void {
  const suggestions: PromptSuggestion[] = bookmarks.map(bm => ({
    text: `${bm.name}: ${bm.path}:${bm.line}:${bm.column}`,
    description: `${filename} at line ${bm.line}`,
    value: String(bm.id),
    disabled: false,
  }));

  editor.startPrompt("Select bookmark: ", "bookmark-select");
  editor.setPromptSuggestions(suggestions);
};
```

See [Events API](/api/events) for complete prompt event handling.

## Command Registration

Make your plugin functions discoverable through the command palette.

<CodeGroup>
  ```typescript Basic Registration theme={null}
  editor.registerCommand(
    "Add Bookmark",              // Display name
    "Add a bookmark at cursor",  // Description
    "bookmark_add",              // Function name
    "normal"                     // Mode filter (null = all modes)
  );
  ```

  ```typescript Mode-Specific Command theme={null}
  editor.registerCommand(
    "Go to Result",
    "Jump to the selected result",
    "my_goto_result",
    "my-results"  // Only available in my-results mode
  );
  ```

  ```typescript Global Command theme={null}
  editor.registerCommand(
    "Show Diagnostics",
    "Open the diagnostics panel",
    "show_diagnostics_panel",
    null  // Available in all modes
  );
  ```
</CodeGroup>

<Tip>
  Use mode filters to prevent command clutter. Mode-specific commands only appear when that mode is active.
</Tip>

## State Management

Plugins maintain state using standard JavaScript variables and data structures.

```typescript theme={null}
// Module-level state
interface Bookmark {
  id: number;
  name: string;
  path: string;
  line: number;
  column: number;
}

const bookmarks: Map<number, Bookmark> = new Map();
let nextBookmarkId = 1;
let isOpen = false;

// State persists across function calls
globalThis.bookmark_add = function(): void {
  const id = nextBookmarkId++;
  bookmarks.set(id, { id, name: `Bookmark ${id}`, ... });
};
```

<Warning>
  State is not persisted between editor sessions. For persistent state, use file system APIs to save/load configuration.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Use TypeScript for type safety">
    Always include the Fresh types reference:

    ```typescript theme={null}
    /// <reference path="../../types/fresh.d.ts" />
    ```

    This enables autocomplete and catches errors at development time.
  </Accordion>

  <Accordion title="Provide user feedback">
    Always update status after operations:

    ```typescript theme={null}
    editor.setStatus("Operation complete");
    ```

    Use `editor.debug()` for development logging:

    ```typescript theme={null}
    editor.debug(`Processing ${count} items`);
    ```
  </Accordion>

  <Accordion title="Clean up resources">
    Remove overlays, close panels, and clear state when done:

    ```typescript theme={null}
    globalThis.cleanup = function(): void {
      editor.clearNamespace(bufferId, "my-plugin");
      bookmarks.clear();
      isOpen = false;
    };
    ```
  </Accordion>

  <Accordion title="Handle errors gracefully">
    Wrap async operations in try-catch:

    ```typescript theme={null}
    try {
      const result = await editor.spawnProcess("git", ["status"]);
      // Process result
    } catch (e) {
      editor.setStatus(`Error: ${e}`);
    }
    ```
  </Accordion>

  <Accordion title="Use meaningful names">
    Prefix overlays and virtual buffers with your plugin name:

    ```typescript theme={null}
    editor.addOverlay(bufferId, "my-plugin:highlight:1", ...);

    await editor.createVirtualBufferInSplit({
      name: "*My Plugin Results*",
      panel_id: "my-plugin-results",
      ...
    });
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Buffer API" icon="file-lines" href="/api/buffer">
    Learn about buffer manipulation and text operations
  </Card>

  <Card title="Events API" icon="bolt" href="/api/events">
    React to editor events and user actions
  </Card>

  <Card title="Overlays API" icon="highlighter" href="/api/overlays">
    Master visual highlighting and annotations
  </Card>

  <Card title="Virtual Buffers API" icon="window-restore" href="/api/virtual-buffers">
    Create powerful results panels and UI
  </Card>
</CardGroup>
