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

# Event System

> Subscribe to editor events and respond to user actions

The Events API allows plugins to subscribe to editor events and execute code in response to user actions, buffer changes, and other editor state changes.

## Event Subscription

### on

Subscribe to an editor event.

```typescript theme={null}
on(event_name: string, handler_name: string): boolean
```

<ParamField path="event_name" type="string" required>
  Event to subscribe to (e.g., "buffer\_save", "cursor\_moved", "buffer\_modified")
</ParamField>

<ParamField path="handler_name" type="string" required>
  Name of globalThis function to call with event data
</ParamField>

<ResponseField name="returns" type="boolean">
  `true` if subscription was successful
</ResponseField>

<Warning>
  Handler must be a global function name (not a closure). Multiple handlers can be registered for the same event.
</Warning>

**Example:**

```typescript theme={null}
// Define the handler function
globalThis.onSave = (data) => {
  editor.setStatus(`Saved: ${data.path}`);
};

// Subscribe to the event
editor.on("buffer_save", "onSave");
```

### off

Unregister an event handler.

```typescript theme={null}
off(event_name: string, handler_name: string): boolean
```

<ParamField path="event_name" type="string" required>
  Name of the event
</ParamField>

<ParamField path="handler_name" type="string" required>
  Name of the handler to remove
</ParamField>

<ResponseField name="returns" type="boolean">
  `true` if handler was removed successfully
</ResponseField>

**Example:**

```typescript theme={null}
// Unsubscribe from event
editor.off("buffer_save", "onSave");
```

### getHandlers

Get list of registered handlers for an event.

```typescript theme={null}
getHandlers(event_name: string): string[]
```

<ParamField path="event_name" type="string" required>
  Name of the event
</ParamField>

<ResponseField name="returns" type="string[]">
  Array of registered handler function names
</ResponseField>

**Example:**

```typescript theme={null}
const handlers = editor.getHandlers("buffer_save");
editor.debug(`Registered handlers: ${handlers.join(", ")}`);
```

## Available Events

The editor emits various events that plugins can subscribe to:

<CardGroup cols={2}>
  <Card title="buffer_save" icon="floppy-disk">
    Fired when a buffer is saved to disk
  </Card>

  <Card title="buffer_modified" icon="pen">
    Fired when buffer content is modified
  </Card>

  <Card title="cursor_moved" icon="arrow-pointer">
    Fired when the cursor position changes
  </Card>

  <Card title="buffer_opened" icon="folder-open">
    Fired when a new buffer is opened
  </Card>

  <Card title="buffer_closed" icon="circle-xmark">
    Fired when a buffer is closed
  </Card>

  <Card title="lines_changed" icon="bars">
    Fired when lines are added, removed, or modified (batched)
  </Card>
</CardGroup>

## Event Data

Each event passes data to the handler function. The structure depends on the event type:

### buffer\_save

```typescript theme={null}
interface BufferSaveEvent {
  buffer_id: number;
  path: string;
}
```

**Example:**

```typescript theme={null}
globalThis.onBufferSave = (data) => {
  editor.info(`Buffer ${data.buffer_id} saved to ${data.path}`);
};

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

### buffer\_modified

```typescript theme={null}
interface BufferModifiedEvent {
  buffer_id: number;
}
```

**Example:**

```typescript theme={null}
globalThis.onBufferModified = (data) => {
  const info = editor.getBufferInfo(data.buffer_id);
  if (info?.modified) {
    editor.debug(`Buffer ${data.buffer_id} has unsaved changes`);
  }
};

editor.on("buffer_modified", "onBufferModified");
```

### cursor\_moved

```typescript theme={null}
interface CursorMovedEvent {
  buffer_id: number;
  position: number;
  line: number;
}
```

**Example:**

```typescript theme={null}
globalThis.onCursorMoved = (data) => {
  editor.setStatus(`Line ${data.line}, byte ${data.position}`);
};

editor.on("cursor_moved", "onCursorMoved");
```

### lines\_changed

Fired when lines are modified in a buffer. This is a batched event that's more efficient than listening to every keystroke.

```typescript theme={null}
interface LinesChangedEvent {
  buffer_id: number;
  start_line: number;
  end_line: number;
}
```

**Example:**

```typescript theme={null}
globalThis.onLinesChanged = (data) => {
  // Re-highlight the changed lines
  const start = data.start_line;
  const end = data.end_line;
  editor.debug(`Lines ${start}-${end} changed in buffer ${data.buffer_id}`);
};

editor.on("lines_changed", "onLinesChanged");
```

## Best Practices

<AccordionGroup>
  <Accordion title="Use batched events when possible">
    Prefer `lines_changed` over `buffer_modified` or `cursor_moved` for performance-sensitive operations like syntax highlighting.
  </Accordion>

  <Accordion title="Clean up handlers">
    Unsubscribe from events when your plugin is deactivated or no longer needs them to avoid memory leaks.
  </Accordion>

  <Accordion title="Handle errors gracefully">
    Wrap event handlers in try/catch blocks to prevent one plugin from breaking others.

    ```typescript theme={null}
    globalThis.onSave = (data) => {
      try {
        // Your event handling code
      } catch (error) {
        editor.error(`Error in onSave: ${error}`);
      }
    };
    ```
  </Accordion>

  <Accordion title="Debounce high-frequency events">
    For events like `cursor_moved`, consider debouncing to avoid excessive processing.

    ```typescript theme={null}
    let timeout: number | null = null;

    globalThis.onCursorMoved = (data) => {
      if (timeout) clearTimeout(timeout);
      timeout = setTimeout(() => {
        // Process cursor move after 100ms of inactivity
      }, 100);
    };
    ```
  </Accordion>
</AccordionGroup>

## Examples

### Auto-save on buffer modified

```typescript theme={null}
let autoSaveTimeout: number | null = null;

globalThis.handleBufferModified = (data) => {
  // Clear existing timeout
  if (autoSaveTimeout) {
    clearTimeout(autoSaveTimeout);
  }
  
  // Save after 2 seconds of inactivity
  autoSaveTimeout = setTimeout(() => {
    const info = editor.getBufferInfo(data.buffer_id);
    if (info?.modified && info.path) {
      editor.executeAction("save_buffer");
      editor.setStatus("Auto-saved");
    }
  }, 2000);
};

editor.on("buffer_modified", "handleBufferModified");
```

### Track cursor position

```typescript theme={null}
const cursorHistory: number[] = [];

globalThis.trackCursor = (data) => {
  cursorHistory.push(data.position);
  
  // Keep only last 100 positions
  if (cursorHistory.length > 100) {
    cursorHistory.shift();
  }
  
  editor.debug(`Cursor at ${data.position}, history: ${cursorHistory.length}`);
};

editor.on("cursor_moved", "trackCursor");
```

### Highlight TODOs on line change

```typescript theme={null}
globalThis.highlightTodos = async (data) => {
  const bufferId = data.buffer_id;
  const start = data.start_line;
  const end = data.end_line;
  
  // Clear existing highlights in the changed range
  editor.clearNamespace(bufferId, "todo-highlight");
  
  // Re-highlight TODOs in the changed lines
  for (let line = start; line <= end; line++) {
    const lineStart = /* calculate byte offset for line start */;
    const lineEnd = /* calculate byte offset for line end */;
    const text = await editor.getBufferText(bufferId, lineStart, lineEnd);
    
    const todoMatch = text.match(/TODO:/i);
    if (todoMatch) {
      const todoStart = lineStart + todoMatch.index!;
      const todoEnd = todoStart + todoMatch[0].length;
      editor.addOverlay(
        bufferId,
        "todo-highlight",
        todoStart,
        todoEnd,
        255, 200, 0,  // Orange color
        -1, -1, -1,   // No background
        false, true, false, false
      );
    }
  }
};

editor.on("lines_changed", "highlightTodos");
```
