Skip to main content
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.
on(event_name: string, handler_name: string): boolean
event_name
string
required
Event to subscribe to (e.g., “buffer_save”, “cursor_moved”, “buffer_modified”)
handler_name
string
required
Name of globalThis function to call with event data
returns
boolean
true if subscription was successful
Handler must be a global function name (not a closure). Multiple handlers can be registered for the same event.
Example:
// 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.
off(event_name: string, handler_name: string): boolean
event_name
string
required
Name of the event
handler_name
string
required
Name of the handler to remove
returns
boolean
true if handler was removed successfully
Example:
// Unsubscribe from event
editor.off("buffer_save", "onSave");

getHandlers

Get list of registered handlers for an event.
getHandlers(event_name: string): string[]
event_name
string
required
Name of the event
returns
string[]
Array of registered handler function names
Example:
const handlers = editor.getHandlers("buffer_save");
editor.debug(`Registered handlers: ${handlers.join(", ")}`);

Available Events

The editor emits various events that plugins can subscribe to:

buffer_save

Fired when a buffer is saved to disk

buffer_modified

Fired when buffer content is modified

cursor_moved

Fired when the cursor position changes

buffer_opened

Fired when a new buffer is opened

buffer_closed

Fired when a buffer is closed

lines_changed

Fired when lines are added, removed, or modified (batched)

Event Data

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

buffer_save

interface BufferSaveEvent {
  buffer_id: number;
  path: string;
}
Example:
globalThis.onBufferSave = (data) => {
  editor.info(`Buffer ${data.buffer_id} saved to ${data.path}`);
};

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

buffer_modified

interface BufferModifiedEvent {
  buffer_id: number;
}
Example:
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

interface CursorMovedEvent {
  buffer_id: number;
  position: number;
  line: number;
}
Example:
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.
interface LinesChangedEvent {
  buffer_id: number;
  start_line: number;
  end_line: number;
}
Example:
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

Prefer lines_changed over buffer_modified or cursor_moved for performance-sensitive operations like syntax highlighting.
Unsubscribe from events when your plugin is deactivated or no longer needs them to avoid memory leaks.
Wrap event handlers in try/catch blocks to prevent one plugin from breaking others.
globalThis.onSave = (data) => {
  try {
    // Your event handling code
  } catch (error) {
    editor.error(`Error in onSave: ${error}`);
  }
};
For events like cursor_moved, consider debouncing to avoid excessive processing.
let timeout: number | null = null;

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

Examples

Auto-save on buffer modified

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

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

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");

Build docs developers (and LLMs) love