Skip to content

Editor API ​

The Editor API provides comprehensive control over Trae IDE's text editors, allowing you to manipulate documents, handle selections, and customize editing behavior.

Overview ​

The Editor API enables you to:

  • Access and modify text documents
  • Control cursor position and selections
  • Handle text changes and edits
  • Customize editor behavior
  • Implement language-specific features

Core Interfaces ​

TextEditor ​

Represents an active text editor instance.

typescript
interface TextEditor {
  readonly document: TextDocument;
  readonly selection: Selection;
  readonly selections: readonly Selection[];
  readonly visibleRanges: readonly Range[];
  readonly options: TextEditorOptions;
  readonly viewColumn?: ViewColumn;

  edit(callback: (editBuilder: TextEditorEdit) => void): Thenable<boolean>;
  insertSnippet(snippet: SnippetString, location?: Position | Range | readonly Position[] | readonly Range[]): Thenable<boolean>;
  setDecorations(decorationType: TextEditorDecorationType, rangesOrOptions: readonly Range[] | readonly DecorationOptions[]): void;
  revealRange(range: Range, revealType?: TextEditorRevealType): void;
  show(column?: ViewColumn): void;
  hide(): void;
}

TextDocument ​

Represents a text document in the editor.

typescript
interface TextDocument {
  readonly uri: Uri;
  readonly fileName: string;
  readonly isUntitled: boolean;
  readonly languageId: string;
  readonly version: number;
  readonly isDirty: boolean;
  readonly isClosed: boolean;
  readonly save: () => Thenable<boolean>;
  readonly eol: EndOfLine;
  readonly lineCount: number;

  getText(range?: Range): string;
  getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined;
  lineAt(line: number): TextLine;
  lineAt(position: Position): TextLine;
  offsetAt(position: Position): number;
  positionAt(offset: number): Position;
  validateRange(range: Range): Range;
  validatePosition(position: Position): Position;
}

Basic Operations ​

Getting the Active Editor ​

typescript
import { TraeAPI } from '@trae/api';

// Get the currently active editor
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
  console.log('Active file:', editor.document.fileName);
  console.log('Language:', editor.document.languageId);
  console.log('Line count:', editor.document.lineCount);
}

Reading Text Content ​

typescript
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
  const document = editor.document;
  
  // Get all text
  const fullText = document.getText();
  
  // Get selected text
  const selectedText = document.getText(editor.selection);
  
  // Get text from a specific range
  const range = new TraeAPI.Range(0, 0, 5, 0); // First 5 lines
  const rangeText = document.getText(range);
  
  // Get a specific line
  const firstLine = document.lineAt(0);
  console.log('First line text:', firstLine.text);
}

Modifying Text ​

typescript
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
  editor.edit(editBuilder => {
    // Insert text at cursor position
    editBuilder.insert(editor.selection.active, 'Hello, World!');
    
    // Replace selected text
    editBuilder.replace(editor.selection, 'New text');
    
    // Delete a range
    const range = new TraeAPI.Range(0, 0, 1, 0);
    editBuilder.delete(range);
    
    // Insert at the beginning of the document
    editBuilder.insert(new TraeAPI.Position(0, 0), '// Generated code\n');
  });
}

Selections and Cursors ​

Working with Selections ​

typescript
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
  // Get current selection
  const selection = editor.selection;
  console.log('Selection start:', selection.start);
  console.log('Selection end:', selection.end);
  console.log('Is empty:', selection.isEmpty);
  
  // Set a new selection
  const newSelection = new TraeAPI.Selection(0, 0, 5, 10);
  editor.selection = newSelection;
  
  // Work with multiple selections
  const selections = editor.selections;
  console.log('Number of cursors:', selections.length);
  
  // Set multiple selections
  editor.selections = [
    new TraeAPI.Selection(0, 0, 0, 5),
    new TraeAPI.Selection(1, 0, 1, 5),
    new TraeAPI.Selection(2, 0, 2, 5)
  ];
}

Cursor Movement ​

typescript
// Move cursor to a specific position
const position = new TraeAPI.Position(10, 5);
editor.selection = new TraeAPI.Selection(position, position);

// Move to the end of the document
const lastLine = editor.document.lineCount - 1;
const lastChar = editor.document.lineAt(lastLine).text.length;
const endPosition = new TraeAPI.Position(lastLine, lastChar);
editor.selection = new TraeAPI.Selection(endPosition, endPosition);

// Reveal the cursor position
editor.revealRange(editor.selection, TraeAPI.TextEditorRevealType.InCenter);

Text Decorations ​

Creating Decoration Types ​

typescript
// Create a decoration type for highlighting errors
const errorDecorationType = TraeAPI.window.createTextEditorDecorationType({
  backgroundColor: 'rgba(255, 0, 0, 0.2)',
  border: '1px solid red',
  borderRadius: '3px',
  overviewRulerColor: 'red',
  overviewRulerLane: TraeAPI.OverviewRulerLane.Right,
  light: {
    backgroundColor: 'rgba(255, 0, 0, 0.1)'
  },
  dark: {
    backgroundColor: 'rgba(255, 0, 0, 0.3)'
  }
});

// Create a decoration type for warnings
const warningDecorationType = TraeAPI.window.createTextEditorDecorationType({
  backgroundColor: 'rgba(255, 165, 0, 0.2)',
  border: '1px solid orange',
  gutterIconPath: TraeAPI.Uri.file('/path/to/warning-icon.svg'),
  gutterIconSize: 'contain'
});

Applying Decorations ​

typescript
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
  // Simple range decorations
  const errorRanges = [
    new TraeAPI.Range(5, 0, 5, 10),
    new TraeAPI.Range(10, 5, 10, 15)
  ];
  editor.setDecorations(errorDecorationType, errorRanges);
  
  // Decorations with hover messages
  const warningDecorations: TraeAPI.DecorationOptions[] = [
    {
      range: new TraeAPI.Range(3, 0, 3, 20),
      hoverMessage: 'This is a warning message',
      renderOptions: {
        after: {
          contentText: ' ⚠️',
          color: 'orange'
        }
      }
    }
  ];
  editor.setDecorations(warningDecorationType, warningDecorations);
}

Snippets ​

Inserting Snippets ​

typescript
const editor = TraeAPI.window.activeTextEditor;
if (editor) {
  // Simple snippet
  const snippet = new TraeAPI.SnippetString('console.log("${1:message}");');
  editor.insertSnippet(snippet);
  
  // Complex snippet with multiple placeholders
  const functionSnippet = new TraeAPI.SnippetString([
    'function ${1:functionName}(${2:parameters}) {',
    '\t${3:// TODO: Implement}',
    '\treturn ${4:undefined};',
    '}'
  ].join('\n'));
  
  // Insert at specific position
  const position = new TraeAPI.Position(10, 0);
  editor.insertSnippet(functionSnippet, position);
  
  // Insert at multiple positions
  const positions = [
    new TraeAPI.Position(5, 0),
    new TraeAPI.Position(15, 0),
    new TraeAPI.Position(25, 0)
  ];
  editor.insertSnippet(snippet, positions);
}

Dynamic Snippets ​

typescript
// Create a snippet based on current context
function createGetterSetterSnippet(propertyName: string, propertyType: string): TraeAPI.SnippetString {
  return new TraeAPI.SnippetString([
    `private _${propertyName}: ${propertyType};`,
    '',
    `get ${propertyName}(): ${propertyType} {`,
    `\treturn this._${propertyName};`,
    '}',
    '',
    `set ${propertyName}(value: ${propertyType}) {`,
    `\tthis._${propertyName} = value;`,
    '}'
  ].join('\n'));
}

// Usage
const snippet = createGetterSetterSnippet('name', 'string');
editor.insertSnippet(snippet);

Event Handling ​

Document Events ​

typescript
// Listen to document changes
TraeAPI.workspace.onDidChangeTextDocument(event => {
  console.log('Document changed:', event.document.fileName);
  console.log('Changes:', event.contentChanges);
  
  // Process each change
  event.contentChanges.forEach(change => {
    console.log('Range:', change.range);
    console.log('New text:', change.text);
    console.log('Range length:', change.rangeLength);
  });
});

// Listen to document saves
TraeAPI.workspace.onDidSaveTextDocument(document => {
  console.log('Document saved:', document.fileName);
});

// Listen to document opens
TraeAPI.workspace.onDidOpenTextDocument(document => {
  console.log('Document opened:', document.fileName);
});

// Listen to document closes
TraeAPI.workspace.onDidCloseTextDocument(document => {
  console.log('Document closed:', document.fileName);
});

Editor Events ​

typescript
// Listen to active editor changes
TraeAPI.window.onDidChangeActiveTextEditor(editor => {
  if (editor) {
    console.log('Active editor changed to:', editor.document.fileName);
  } else {
    console.log('No active editor');
  }
});

// Listen to selection changes
TraeAPI.window.onDidChangeTextEditorSelection(event => {
  console.log('Selection changed in:', event.textEditor.document.fileName);
  console.log('New selections:', event.selections);
});

// Listen to visible range changes
TraeAPI.window.onDidChangeTextEditorVisibleRanges(event => {
  console.log('Visible ranges changed:', event.visibleRanges);
});

// Listen to editor options changes
TraeAPI.window.onDidChangeTextEditorOptions(event => {
  console.log('Editor options changed:', event.options);
});

Advanced Features ​

Custom Language Support ​

typescript
// Register a language
TraeAPI.languages.registerDocumentFormattingEditProvider('mylang', {
  provideDocumentFormattingEdits(document: TraeAPI.TextDocument): TraeAPI.TextEdit[] {
    // Implement formatting logic
    const edits: TraeAPI.TextEdit[] = [];
    
    for (let i = 0; i < document.lineCount; i++) {
      const line = document.lineAt(i);
      if (line.text.startsWith('  ')) {
        // Convert 2 spaces to 4 spaces
        const newText = line.text.replace(/^  /, '    ');
        edits.push(TraeAPI.TextEdit.replace(line.range, newText));
      }
    }
    
    return edits;
  }
});

// Register hover provider
TraeAPI.languages.registerHoverProvider('mylang', {
  provideHover(document: TraeAPI.TextDocument, position: TraeAPI.Position): TraeAPI.Hover | undefined {
    const range = document.getWordRangeAtPosition(position);
    if (range) {
      const word = document.getText(range);
      return new TraeAPI.Hover(`Information about: ${word}`);
    }
  }
});

Code Actions ​

typescript
// Register code action provider
TraeAPI.languages.registerCodeActionsProvider('typescript', {
  provideCodeActions(
    document: TraeAPI.TextDocument,
    range: TraeAPI.Range,
    context: TraeAPI.CodeActionContext
  ): TraeAPI.CodeAction[] {
    const actions: TraeAPI.CodeAction[] = [];
    
    // Add a quick fix for console.log statements
    const text = document.getText(range);
    if (text.includes('console.log')) {
      const action = new TraeAPI.CodeAction('Remove console.log', TraeAPI.CodeActionKind.QuickFix);
      action.edit = new TraeAPI.WorkspaceEdit();
      action.edit.delete(document.uri, range);
      actions.push(action);
    }
    
    // Add a refactor action
    const refactorAction = new TraeAPI.CodeAction('Extract to function', TraeAPI.CodeActionKind.Refactor);
    refactorAction.command = {
      title: 'Extract to function',
      command: 'myExtension.extractFunction',
      arguments: [document.uri, range]
    };
    actions.push(refactorAction);
    
    return actions;
  }
});

Folding Ranges ​

typescript
// Register folding range provider
TraeAPI.languages.registerFoldingRangeProvider('mylang', {
  provideFoldingRanges(document: TraeAPI.TextDocument): TraeAPI.FoldingRange[] {
    const ranges: TraeAPI.FoldingRange[] = [];
    
    for (let i = 0; i < document.lineCount; i++) {
      const line = document.lineAt(i);
      
      // Fold function blocks
      if (line.text.includes('function')) {
        let endLine = i;
        let braceCount = 0;
        
        for (let j = i; j < document.lineCount; j++) {
          const currentLine = document.lineAt(j);
          braceCount += (currentLine.text.match(/{/g) || []).length;
          braceCount -= (currentLine.text.match(/}/g) || []).length;
          
          if (braceCount === 0 && j > i) {
            endLine = j;
            break;
          }
        }
        
        if (endLine > i) {
          ranges.push(new TraeAPI.FoldingRange(i, endLine, TraeAPI.FoldingRangeKind.Region));
        }
      }
    }
    
    return ranges;
  }
});

Utilities ​

Text Manipulation Helpers ​

typescript
class TextUtils {
  static getIndentation(line: string): string {
    const match = line.match(/^\s*/);
    return match ? match[0] : '';
  }
  
  static getWordAt(document: TraeAPI.TextDocument, position: TraeAPI.Position): string {
    const range = document.getWordRangeAtPosition(position);
    return range ? document.getText(range) : '';
  }
  
  static findAllOccurrences(document: TraeAPI.TextDocument, searchText: string): TraeAPI.Range[] {
    const ranges: TraeAPI.Range[] = [];
    const text = document.getText();
    let index = 0;
    
    while ((index = text.indexOf(searchText, index)) !== -1) {
      const startPos = document.positionAt(index);
      const endPos = document.positionAt(index + searchText.length);
      ranges.push(new TraeAPI.Range(startPos, endPos));
      index += searchText.length;
    }
    
    return ranges;
  }
  
  static insertAtLineEnd(editor: TraeAPI.TextEditor, lineNumber: number, text: string): void {
    const line = editor.document.lineAt(lineNumber);
    const position = new TraeAPI.Position(lineNumber, line.text.length);
    
    editor.edit(editBuilder => {
      editBuilder.insert(position, text);
    });
  }
}

Editor State Management ​

typescript
class EditorStateManager {
  private savedStates = new Map<string, EditorState>();
  
  saveState(editor: TraeAPI.TextEditor): void {
    const state: EditorState = {
      selections: [...editor.selections],
      visibleRanges: [...editor.visibleRanges],
      options: { ...editor.options }
    };
    
    this.savedStates.set(editor.document.uri.toString(), state);
  }
  
  restoreState(editor: TraeAPI.TextEditor): void {
    const state = this.savedStates.get(editor.document.uri.toString());
    if (state) {
      editor.selections = state.selections;
      if (state.visibleRanges.length > 0) {
        editor.revealRange(state.visibleRanges[0]);
      }
    }
  }
}

interface EditorState {
  selections: TraeAPI.Selection[];
  visibleRanges: TraeAPI.Range[];
  options: TraeAPI.TextEditorOptions;
}

Best Practices ​

Performance ​

  • Batch multiple edits into a single edit() call
  • Use document.getText(range) instead of getting all text when possible
  • Dispose of decorations when no longer needed
  • Avoid frequent selection changes

User Experience ​

  • Preserve user selections when possible
  • Provide undo/redo support for custom operations
  • Show progress for long-running operations
  • Use appropriate decoration types for different purposes

Error Handling ​

typescript
try {
  await editor.edit(editBuilder => {
    // Your edit operations
  });
} catch (error) {
  TraeAPI.window.showErrorMessage(`Edit failed: ${error.message}`);
}

Examples ​

Auto-formatter Extension ​

typescript
export function activate(context: TraeAPI.ExtensionContext) {
  const disposable = TraeAPI.commands.registerCommand('myExtension.formatDocument', async () => {
    const editor = TraeAPI.window.activeTextEditor;
    if (!editor) return;
    
    await editor.edit(editBuilder => {
      for (let i = 0; i < editor.document.lineCount; i++) {
        const line = editor.document.lineAt(i);
        const trimmed = line.text.trim();
        
        if (trimmed !== line.text) {
          editBuilder.replace(line.range, trimmed);
        }
      }
    });
    
    TraeAPI.window.showInformationMessage('Document formatted!');
  });
  
  context.subscriptions.push(disposable);
}

Word Counter Extension ​

typescript
class WordCounter {
  private statusBarItem: TraeAPI.StatusBarItem;
  
  constructor() {
    this.statusBarItem = TraeAPI.window.createStatusBarItem(TraeAPI.StatusBarAlignment.Left);
    this.updateWordCount();
    
    // Update on document changes
    TraeAPI.workspace.onDidChangeTextDocument(() => this.updateWordCount());
    TraeAPI.window.onDidChangeActiveTextEditor(() => this.updateWordCount());
  }
  
  private updateWordCount(): void {
    const editor = TraeAPI.window.activeTextEditor;
    if (!editor) {
      this.statusBarItem.hide();
      return;
    }
    
    const text = editor.document.getText();
    const wordCount = text.split(/\s+/).filter(word => word.length > 0).length;
    
    this.statusBarItem.text = `Words: ${wordCount}`;
    this.statusBarItem.show();
  }
  
  dispose(): void {
    this.statusBarItem.dispose();
  }
}

For more examples and advanced usage patterns, see the Extension Samples repository.

Your Ultimate AI-Powered IDE Learning Guide