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.