Skip to main content

Overview

The IEditor interface provides methods for reading and manipulating editor content. Access the active editor via this.app.workspace.activeEditor() or this.app.editorRegistry.getActive().
const editor = this.app.workspace.activeEditor();
if (editor) {
  const selection = editor.getSelection();
  editor.replaceSelection('**' + selection + '**');
}

Interface Definition

interface IEditor {
  getSelection(): string;
  replaceSelection(replacement: string): void;
  getRange(from: EditorPosition, to: EditorPosition): string;
  replaceRange(replacement: string, from: EditorPosition, to?: EditorPosition): void;
  getCursor(type?: 'from' | 'to' | 'head' | 'anchor'): EditorPosition;
  setCursor(pos: EditorPosition | number): void;
  setSelection(anchor: EditorPosition | number, head?: EditorPosition | number): void;
  getValue(): string;
  setValue(content: string): void;
  getLine(line: number): string;
  lineCount(): number;
  lastLine(): number;
  posToOffset(pos: EditorPosition): number;
  offsetToPos(offset: number): EditorPosition;
  focus(): void;
  hasFocus(): boolean;
}

Position Types

EditorPosition

EditorPosition
object
Represents a position in the editor.
interface EditorPosition {
  line: number;  // Line number (0-indexed)
  ch: number;    // Character offset in line (0-indexed)
}
Example positions:
{ line: 0, ch: 0 }   // Start of document
{ line: 0, ch: 5 }   // 6th character of first line
{ line: 10, ch: 0 }  // Start of line 11

EditorRange

EditorRange
object
Represents a range in the editor.
interface EditorRange {
  from: EditorPosition;
  to: EditorPosition;
}

Getting Content

getValue()

getValue
() => string
Get the entire document content.
const editor = this.app.workspace.activeEditor();
if (editor) {
  const content = editor.getValue();
  console.log('Document length:', content.length);
  console.log('Word count:', content.split(/\s+/).length);
}

getSelection()

getSelection
() => string
Get the currently selected text.
const selection = editor.getSelection();
if (selection) {
  console.log('Selected text:', selection);
} else {
  console.log('No selection');
}

getLine()

getLine
(line: number) => string
Get the text of a specific line.
// Get first line
const firstLine = editor.getLine(0);

// Get current line
const cursor = editor.getCursor();
const currentLine = editor.getLine(cursor.line);

console.log('Current line:', currentLine);

getRange()

getRange
(from: EditorPosition, to: EditorPosition) => string
Get text within a specific range.
// Get text from line 0 ch 0 to line 5 ch 10
const text = editor.getRange(
  { line: 0, ch: 0 },
  { line: 5, ch: 10 }
);

// Get entire first line
const firstLine = editor.getRange(
  { line: 0, ch: 0 },
  { line: 1, ch: 0 }
);

lineCount()

lineCount
() => number
Get the total number of lines in the document.
const total = editor.lineCount();
console.log(`Document has ${total} lines`);

lastLine()

lastLine
() => number
Get the index of the last line (same as lineCount() - 1).
const lastLineIdx = editor.lastLine();
const lastLineText = editor.getLine(lastLineIdx);
console.log('Last line:', lastLineText);

Setting Content

setValue()

setValue
(content: string) => void
Replace the entire document content.
editor.setValue('# New Document\n\nCompletely new content.');
This replaces all content and resets the undo history. Use with caution.

replaceSelection()

replaceSelection
(replacement: string) => void
Replace the selected text.
// Bold the selection
const selection = editor.getSelection();
if (selection) {
  editor.replaceSelection(`**${selection}**`);
}

// Insert text at cursor
if (!editor.getSelection()) {
  editor.replaceSelection('inserted text');
}

replaceRange()

replaceRange
(replacement: string, from: EditorPosition, to?: EditorPosition) => void
Replace text in a specific range.
// Replace text in a range
editor.replaceRange(
  'new text',
  { line: 0, ch: 0 },
  { line: 0, ch: 8 }
);

// Insert at position (no 'to' parameter)
editor.replaceRange(
  'inserted ',
  { line: 0, ch: 0 }
);

// Delete text (empty replacement)
editor.replaceRange(
  '',
  { line: 0, ch: 0 },
  { line: 0, ch: 5 }
);

Cursor and Selection

getCursor()

getCursor
(type?: 'from' | 'to' | 'head' | 'anchor') => EditorPosition
Get the cursor position.
// Get main cursor position
const cursor = editor.getCursor();
console.log(`Line ${cursor.line}, Column ${cursor.ch}`);

// Get selection start
const from = editor.getCursor('from');

// Get selection end
const to = editor.getCursor('to');

// Check if there's a selection
const hasSelection = from.line !== to.line || from.ch !== to.ch;

setCursor()

setCursor
(pos: EditorPosition | number) => void
Set the cursor position.
// Set cursor to position
editor.setCursor({ line: 5, ch: 10 });

// Set cursor to offset
editor.setCursor(100); // 100th character

// Move to start of document
editor.setCursor({ line: 0, ch: 0 });

// Move to end of document
const lastLine = editor.lastLine();
const lastLineText = editor.getLine(lastLine);
editor.setCursor({ line: lastLine, ch: lastLineText.length });

setSelection()

setSelection
(anchor: EditorPosition | number, head?: EditorPosition | number) => void
Set the selection range.
// Select text from position to position
editor.setSelection(
  { line: 0, ch: 0 },
  { line: 0, ch: 10 }
);

// Select using offsets
editor.setSelection(0, 10);

// Select entire first line
editor.setSelection(
  { line: 0, ch: 0 },
  { line: 1, ch: 0 }
);

// Select entire document
const lastLine = editor.lastLine();
const lastLineText = editor.getLine(lastLine);
editor.setSelection(
  { line: 0, ch: 0 },
  { line: lastLine, ch: lastLineText.length }
);

Position Conversion

posToOffset()

posToOffset
(pos: EditorPosition) => number
Convert a position to a character offset.
const pos = { line: 5, ch: 10 };
const offset = editor.posToOffset(pos);
console.log('Character offset:', offset);

offsetToPos()

offsetToPos
(offset: number) => EditorPosition
Convert a character offset to a position.
const offset = 100;
const pos = editor.offsetToPos(offset);
console.log(`Position: line ${pos.line}, ch ${pos.ch}`);

Focus

focus()

focus
() => void
Focus the editor.
editor.focus();

hasFocus()

hasFocus
() => boolean
Check if the editor has focus.
if (editor.hasFocus()) {
  console.log('Editor is focused');
}

Common Patterns

Insert Text at Cursor

function insertText(editor: IEditor, text: string) {
  editor.replaceSelection(text);
}

// Usage
this.addCommand({
  id: 'insert-date',
  name: 'Insert Current Date',
  editorCallback: (editor) => {
    const date = new Date().toISOString().split('T')[0];
    insertText(editor, date);
  },
});

Wrap Selection

function wrapSelection(
  editor: IEditor,
  before: string,
  after: string = before
) {
  const selection = editor.getSelection();
  if (selection) {
    editor.replaceSelection(before + selection + after);
  } else {
    // No selection - insert markers and place cursor between them
    const cursor = editor.getCursor();
    editor.replaceSelection(before + after);
    const newPos = editor.offsetToPos(
      editor.posToOffset(cursor) + before.length
    );
    editor.setCursor(newPos);
  }
}

// Usage
this.addCommand({
  id: 'bold',
  name: 'Bold',
  hotkey: ['Mod', 'b'],
  editorCallback: (editor) => {
    wrapSelection(editor, '**');
  },
});

Transform Selection

function transformSelection(
  editor: IEditor,
  transform: (text: string) => string
) {
  const selection = editor.getSelection();
  if (selection) {
    const transformed = transform(selection);
    editor.replaceSelection(transformed);
  }
}

// Usage
this.addCommand({
  id: 'uppercase',
  name: 'Uppercase Selection',
  editorCallback: (editor) => {
    transformSelection(editor, text => text.toUpperCase());
  },
});

this.addCommand({
  id: 'titlecase',
  name: 'Title Case',
  editorCallback: (editor) => {
    transformSelection(editor, text => 
      text.replace(/\w\S*/g, txt => 
        txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
      )
    );
  },
});

Modify Current Line

function modifyCurrentLine(
  editor: IEditor,
  modify: (line: string) => string
) {
  const cursor = editor.getCursor();
  const lineText = editor.getLine(cursor.line);
  const modified = modify(lineText);
  
  editor.replaceRange(
    modified,
    { line: cursor.line, ch: 0 },
    { line: cursor.line, ch: lineText.length }
  );
}

// Usage
this.addCommand({
  id: 'toggle-todo',
  name: 'Toggle Todo',
  editorCallback: (editor) => {
    modifyCurrentLine(editor, line => {
      if (line.includes('- [ ]')) {
        return line.replace('- [ ]', '- [x]');
      } else if (line.includes('- [x]')) {
        return line.replace('- [x]', '- [ ]');
      } else {
        return '- [ ] ' + line;
      }
    });
  },
});

Insert Template

function insertTemplate(editor: IEditor, template: string) {
  const cursor = editor.getCursor();
  editor.replaceSelection(template);
  
  // Find {{cursor}} placeholder and move cursor there
  const placeholderPos = template.indexOf('{{cursor}}');
  if (placeholderPos !== -1) {
    const newOffset = editor.posToOffset(cursor) + placeholderPos;
    const newPos = editor.offsetToPos(newOffset);
    
    // Remove placeholder
    editor.replaceRange(
      '',
      newPos,
      editor.offsetToPos(newOffset + '{{cursor}}'.length)
    );
    
    editor.setCursor(newPos);
  }
}

// Usage
this.addCommand({
  id: 'insert-code-block',
  name: 'Insert Code Block',
  editorCallback: (editor) => {
    insertTemplate(editor, '```{{cursor}}\n\n```');
  },
});

Get Word at Cursor

function getWordAtCursor(editor: IEditor): string {
  const cursor = editor.getCursor();
  const line = editor.getLine(cursor.line);
  
  // Find word boundaries
  let start = cursor.ch;
  let end = cursor.ch;
  
  while (start > 0 && /\w/.test(line[start - 1])) {
    start--;
  }
  
  while (end < line.length && /\w/.test(line[end])) {
    end++;
  }
  
  return line.substring(start, end);
}

Select Word at Cursor

function selectWordAtCursor(editor: IEditor) {
  const cursor = editor.getCursor();
  const line = editor.getLine(cursor.line);
  
  let start = cursor.ch;
  let end = cursor.ch;
  
  while (start > 0 && /\w/.test(line[start - 1])) {
    start--;
  }
  
  while (end < line.length && /\w/.test(line[end])) {
    end++;
  }
  
  editor.setSelection(
    { line: cursor.line, ch: start },
    { line: cursor.line, ch: end }
  );
}

Using with Commands

Use the editorCallback property to create commands that operate on the active editor:
this.addCommand({
  id: 'insert-timestamp',
  name: 'Insert Timestamp',
  editorCallback: (editor) => {
    const timestamp = new Date().toISOString();
    editor.replaceSelection(timestamp);
  },
});

this.addCommand({
  id: 'count-words',
  name: 'Count Words in Selection',
  editorCallback: (editor) => {
    const selection = editor.getSelection();
    const wordCount = selection.split(/\s+/).filter(w => w).length;
    this.showNotice(`Word count: ${wordCount}`);
  },
});

Best Practices

Check for Active Editor

// ✅ Good
const editor = this.app.workspace.activeEditor();
if (editor) {
  const content = editor.getValue();
}

// ❌ Bad - may be null
const editor = this.app.workspace.activeEditor()!;
const content = editor.getValue();

Preserve Selection

function preserveSelection(editor: IEditor, callback: () => void) {
  const from = editor.getCursor('from');
  const to = editor.getCursor('to');
  
  callback();
  
  editor.setSelection(from, to);
}

Use editorCallback

// ✅ Good - uses editorCallback
this.addCommand({
  id: 'my-command',
  name: 'My Command',
  editorCallback: (editor) => {
    // Editor is guaranteed to exist
    editor.replaceSelection('text');
  },
});

// ❌ Bad - manual check needed
this.addCommand({
  id: 'my-command',
  name: 'My Command',
  callback: () => {
    const editor = this.app.workspace.activeEditor();
    if (editor) {
      editor.replaceSelection('text');
    }
  },
});