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