> ## 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 Development Guide

> Create powerful TypeScript plugins for Fresh with full access to the editor API

Fresh plugins are written in **TypeScript** and run in a sandboxed **QuickJS environment**. This guide covers everything you need to build your own plugins.

## Quick Start

Create your first plugin in under 2 minutes:

<Steps>
  <Step title="Create Plugin File">
    Create a new TypeScript file in the plugins directory:

    ```bash theme={null}
    touch ~/.config/fresh/plugins/my_plugin.ts
    ```
  </Step>

  <Step title="Add Plugin Code">
    ```typescript my_plugin.ts theme={null}
    /// <reference path="../types/fresh.d.ts" />

    // Register a command that inserts text at the cursor
    globalThis.my_plugin_say_hello = function(): void {
      editor.insertAtCursor("Hello from my new plugin!\n");
      editor.setStatus("My plugin says hello!");
    };

    editor.registerCommand(
      "My Plugin: Say Hello",
      "Inserts a greeting from my plugin",
      "my_plugin_say_hello",
      "normal"
    );

    editor.setStatus("My first plugin loaded!");
    ```
  </Step>

  <Step title="Restart Fresh">
    Restart Fresh to load your plugin.
  </Step>

  <Step title="Test Your Plugin">
    Press `Ctrl+P >` and search for "My Plugin: Say Hello".
  </Step>
</Steps>

<Tip>
  All `.ts` files in the `plugins/` directory are automatically loaded when Fresh starts.
</Tip>

## Plugin Architecture

### Runtime Environment

Plugins run in a sandboxed **QuickJS** JavaScript runtime:

<CardGroup cols={2}>
  <Card title="Transpilation" icon="code">
    TypeScript is transpiled to JavaScript using **oxc\_transformer** (a fast, Rust-based compiler).
  </Card>

  <Card title="Sandboxing" icon="shield">
    Each plugin runs in an isolated QuickJS environment, preventing interference between plugins.
  </Card>

  <Card title="Async Support" icon="bolt">
    Full async/await support for non-blocking I/O, process spawning, and LSP requests.
  </Card>

  <Card title="Type Safety" icon="check">
    TypeScript definitions provide autocomplete and type checking in your editor.
  </Card>
</CardGroup>

### The `editor` Global Object

The `editor` object is the main API surface:

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

// The editor global is always available
editor.setStatus("Plugin initialized");
editor.debug("This goes to the debug log");
```

The `editor` object provides access to:

* Buffer operations (read, write, modify)
* Command registration
* Event handling
* Process spawning
* File system operations
* LSP integration
* Visual overlays and decorations

## Core Concepts

### Commands

Commands are actions that appear in the command palette and can be bound to keys.

#### Registering Commands

```typescript theme={null}
// Define the command handler
globalThis.my_action = function(): void {
  editor.setStatus("Command executed!");
};

// Register it with the editor
editor.registerCommand(
  "My Custom Command",        // Name shown in command palette
  "Does something useful",    // Description
  "my_action",                // Global function name to call
  "normal"                    // Context: "normal", "insert", "prompt", etc.
);
```

<Note>
  Command handlers must be attached to `globalThis` because the editor calls them by name.
</Note>

#### Command Contexts

Contexts control when commands are available:

| Context      | Description              |
| ------------ | ------------------------ |
| `"normal"`   | Normal editing mode      |
| `"insert"`   | Insert mode              |
| `"prompt"`   | When a prompt is active  |
| `""` (empty) | All contexts             |
| Custom       | Your custom mode/context |

### Async Operations

Many API calls return `Promise`s. Use `async/await`:

```typescript theme={null}
globalThis.search_files = async function(): Promise<void> {
  editor.setStatus("Searching...");
  
  const result = await editor.spawnProcess("rg", ["TODO", "."]);
  
  if (result.exit_code === 0) {
    const lines = result.stdout.split("\n").filter(l => l.trim());
    editor.setStatus(`Found ${lines.length} TODOs`);
  } else {
    editor.setStatus("Search failed");
  }
};
```

### Event Handlers

Subscribe to editor events with `editor.on()`:

```typescript theme={null}
globalThis.onSave = function(data: { buffer_id: number, path: string }): void {
  editor.debug(`Saved: ${data.path}`);
  editor.setStatus(`File saved: ${data.path}`);
};

editor.on("buffer_save", "onSave");
```

**Available Events:**

* `buffer_save` - After a buffer is saved to disk
* `buffer_closed` - When a buffer is closed
* `cursor_moved` - When cursor position changes
* `render_start` - Before screen renders (for overlays)
* `lines_changed` - When visible lines change
* `prompt_confirmed` - When user confirms a prompt
* `prompt_cancelled` - When user cancels a prompt

### Buffers

Buffers hold text content. Each buffer has a unique numeric ID.

#### Querying Buffers

```typescript theme={null}
const bufferId = editor.getActiveBufferId();
const path = editor.getBufferPath(bufferId);
const length = editor.getBufferLength(bufferId);
const modified = editor.isBufferModified(bufferId);
const cursorPos = editor.getCursorPosition();
```

#### Reading Buffer Content

```typescript theme={null}
const bufferId = editor.getActiveBufferId();
const text = await editor.getBufferText(bufferId, 0, 100);
editor.debug(`First 100 bytes: ${text}`);
```

#### Modifying Buffers

```typescript theme={null}
const bufferId = editor.getActiveBufferId();
const pos = editor.getCursorPosition();

// Insert text at a position
editor.insertText(bufferId, pos, "Hello, world!\n");

// Delete a range
editor.deleteRange(bufferId, 0, 10);

// Insert at cursor (convenience method)
editor.insertAtCursor("Quick insert\n");
```

### Virtual Buffers

Create special buffers for displaying structured data like search results, diagnostics, or logs.

```typescript theme={null}
editor.defineMode(
  "my-results",  // Mode name
  null,          // No parent mode
  [
    ["Return", "my_goto_result"],
    ["q", "close_buffer"]
  ],
  true  // Read-only
);

await editor.createVirtualBufferInSplit({
  name: "*Search Results*",
  mode: "my-results",
  readOnly: true,
  entries: [
    {
      text: "src/main.rs:42: found match\n",
      properties: { file: "src/main.rs", line: 42, column: 10 }
    },
    {
      text: "src/lib.rs:100: another match\n",
      properties: { file: "src/lib.rs", line: 100, column: 5 }
    }
  ],
  ratio: 0.3,  // Takes 30% of height
  panelId: "my-search-results"  // Reuse panel if exists
});
```

#### Accessing 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, props[0].column || 0);
  }
};
```

### Overlays

Add visual decorations without modifying buffer content:

```typescript theme={null}
const bufferId = editor.getActiveBufferId();
const start = 100;
const end = 150;

// Highlight with yellow background
editor.addOverlay(
  bufferId,
  "my_highlight",  // Namespace (for batch removal)
  start,
  end,
  {
    fg: [255, 255, 0],  // Yellow foreground
    underline: true
  }
);

// Later, clear all overlays in namespace
editor.clearNamespace(bufferId, "my_highlight");
```

### Process Spawning

Run external commands and tools:

```typescript theme={null}
globalThis.run_tests = async function(): Promise<void> {
  editor.setStatus("Running tests...");
  
  const result = await editor.spawnProcess(
    "cargo",           // Command
    ["test"],          // Arguments
    null               // Working directory (null = editor's cwd)
  );
  
  if (result.exit_code === 0) {
    editor.setStatus("Tests passed!");
  } else {
    const firstError = result.stderr.split('\n')[0];
    editor.setStatus(`Tests failed: ${firstError}`);
  }
};
```

### LSP Integration

Invoke language server requests:

```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",
    "textDocument/switchSourceHeader",
    { textDocument: { uri } }
  );
  
  if (result && typeof result === "string") {
    editor.openFile(result.replace("file://", ""), 0, 0);
  }
};
```

### File System Operations

Read and write files:

```typescript theme={null}
globalThis.process_file = async function(): Promise<void> {
  const path = "/path/to/file.txt";
  
  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("File processed!");
  }
};
```

## Common Patterns

### Highlighting Text

```typescript theme={null}
globalThis.highlight_todos = function(): void {
  const bufferId = editor.getActiveBufferId();
  const length = editor.getBufferLength(bufferId);
  
  // Read entire buffer
  editor.getBufferText(bufferId, 0, length).then(text => {
    const regex = /TODO|FIXME|HACK/g;
    let match;
    
    while ((match = regex.exec(text)) !== null) {
      editor.addOverlay(
        bufferId,
        "todo_highlight",
        match.index,
        match.index + match[0].length,
        { fg: [255, 165, 0], underline: true }
      );
    }
  });
};
```

### Interactive Prompts

```typescript theme={null}
globalThis.git_grep = function(): void {
  editor.startPrompt("Git grep: ", "git-grep");
};

globalThis.onGitGrepConfirm = async function(args: {
  prompt_type: string;
  input: string;
}): Promise<boolean> {
  if (args.prompt_type !== "git-grep") return true;
  
  const result = await editor.spawnProcess(
    "git",
    ["grep", "-n", args.input]
  );
  
  if (result.exit_code === 0) {
    // Parse results and create virtual buffer
    const lines = result.stdout.split("\n").filter(l => l.trim());
    editor.setStatus(`Found ${lines.length} matches`);
  }
  
  return true;
};

editor.on("prompt_confirmed", "onGitGrepConfirm");
```

### Custom Modes

```typescript theme={null}
editor.defineMode(
  "my-mode",
  "normal",  // Parent mode
  [
    ["j", "move_line_down"],
    ["k", "move_line_up"],
    ["Return", "my_custom_action"],
    ["q", "close_buffer"]
  ],
  false  // Not read-only
);
```

## Internationalization

Plugins can support multiple languages using i18n:

```typescript theme={null}
// Use plugin translations
const message = editor.pluginTranslate(
  "my_plugin",
  "welcome_message",
  { name: "World" }
);
editor.setStatus(message);
```

Create a `.i18n.json` file next to your plugin:

```json my_plugin.i18n.json theme={null}
{
  "en": {
    "welcome_message": "Hello, {name}!"
  },
  "es": {
    "welcome_message": "¡Hola, {name}!"
  }
}
```

## Debugging

### Debug Logging

```typescript theme={null}
editor.debug("Debug message");
editor.info("Info message");
editor.warn("Warning message");
editor.error("Error message");
```

Run Fresh with debug logging:

```bash theme={null}
RUST_LOG=debug fresh
```

### Status Messages

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

Status messages appear in the status bar.

## Best Practices

<AccordionGroup>
  <Accordion title="Use TypeScript Type Definitions">
    Always include the type reference at the top of your plugin:

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

  <Accordion title="Handle Errors Gracefully">
    Always wrap async operations in try/catch:

    ```typescript theme={null}
    try {
      const result = await editor.spawnProcess("cmd", []);
    } catch (e) {
      editor.error(`Failed: ${e}`);
    }
    ```
  </Accordion>

  <Accordion title="Use Namespaces for Overlays">
    Use consistent namespaces to enable batch removal:

    ```typescript theme={null}
    editor.addOverlay(bufferId, "plugin:feature", start, end, {});
    // Later:
    editor.clearNamespace(bufferId, "plugin:feature");
    ```
  </Accordion>

  <Accordion title="Debounce Expensive Operations">
    Use `editor.delay()` to debounce rapid events:

    ```typescript theme={null}
    await editor.delay(300);  // Wait 300ms
    ```
  </Accordion>

  <Accordion title="Clean Up on Buffer Close">
    Subscribe to `buffer_closed` to clean up resources:

    ```typescript theme={null}
    globalThis.onBufferClose = function(data: { buffer_id: number }) {
      editor.clearNamespace(data.buffer_id, "my_plugin");
    };
    editor.on("buffer_closed", "onBufferClose");
    ```
  </Accordion>
</AccordionGroup>

## Publishing Your Plugin

To share your plugin with others:

<Steps>
  <Step title="Create Git Repository">
    Initialize a git repository for your plugin:

    ```bash theme={null}
    git init
    git add .
    git commit -m "Initial plugin version"
    ```
  </Step>

  <Step title="Add package.json">
    Create a `package.json` with metadata:

    ```json theme={null}
    {
      "name": "my-awesome-plugin",
      "version": "1.0.0",
      "description": "Does something awesome",
      "fresh": {
        "type": "plugin",
        "entry": "my_plugin.ts"
      }
    }
    ```
  </Step>

  <Step title="Push to GitHub">
    Push your repository to GitHub or any git hosting service.
  </Step>

  <Step title="Share the URL">
    Users can install your plugin with:

    ```
    pkg: Install from URL
    https://github.com/yourusername/my-awesome-plugin
    ```
  </Step>
</Steps>

## Next Steps

<CardGroup cols={2}>
  <Card title="Plugin Examples" icon="lightbulb" href="/plugins/examples">
    Explore real plugin examples with complete code
  </Card>

  <Card title="API Reference" icon="book" href="/api">
    Complete API documentation with all methods
  </Card>

  <Card title="Plugin Overview" icon="info" href="/plugins/overview">
    Learn about the plugin system architecture
  </Card>

  <Card title="Getting Started" icon="rocket" href="/plugins/getting-started">
    Install and manage plugins
  </Card>
</CardGroup>
