In everyday work with an IDE, interaction with the code currently being edited is often the focus. VSCode extensions therefore require reliable access to the currently active editor and the document loaded within it. This access forms the basis for numerous extension functionalities, from simple text analysis tools to complex code transformations.
The term “active editor” in VSCode refers to the text editor that
currently has focus and in which the user is working. In contrast to
Eclipse, where you would use
IWorkbenchPage.getActiveEditor() to access the active
editor, VSCode provides the property
vscode.window.activeTextEditor. However, this property can
be undefined if no text editor is currently open or focused
— a situation that must be systematically handled in your
extensions.
Typical use cases for accessing the editor include analyzing the active document to determine metrics such as line count or complexity, inserting code snippets at the current cursor position, reading and processing the current text selection, and formatting or validating code based on project-specific rules. These operations require a sound understanding of the VSCode editor API and its characteristics.
The entry point for all editor operations is the property
vscode.window.activeTextEditor. This property returns
either an instance of TextEditor or undefined
if no text editor is active. Checking for undefined is
therefore mandatory and should be the first step of every editor
operation.
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
const disposable = vscode.commands.registerCommand('example.showEditorInfo', () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showErrorMessage("No active editor found.");
return;
}
const document = editor.document;
const fileName = document.fileName;
const languageId = document.languageId;
const lineCount = document.lineCount;
vscode.window.showInformationMessage(
`File: ${fileName}, Language: ${languageId}, Lines: ${lineCount}`
);
});
context.subscriptions.push(disposable);
}The TextEditor object serves as a bridge between your
extension and the displayed content. The document property
gives you access to the associated TextDocument, which
encapsulates the actual text content and its metadata. This separation
between editor (presentation) and document (content) follows the
model-view concept and allows the same document to be displayed in
multiple editors simultaneously.
The TextDocument provides various methods for reading
its content. The simplest form is getText(), which returns
the entire document content as a string. For larger files, however, you
should use more targeted access to optimize performance.
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const document = editor.document;
const fullText = document.getText();
const wordCount = fullText
.split(/\s+/)
.filter(word => word.length > 0)
.length;
let nonEmptyLines = 0;
for (let i = 0; i < document.lineCount; i++) {
const line = document.lineAt(i);
if (!line.isEmptyOrWhitespace) {
nonEmptyLines++;
}
}
vscode.window.showInformationMessage(
`Words: ${wordCount}, Non-empty lines: ${nonEmptyLines}`
);To access specific text ranges, use getText(range) with
a Range object. This is particularly useful when you want
to analyze or edit only a certain code section. A Range is
defined by start and end Position, where each position
consists of a line and character number.
const startPosition = new vscode.Position(0, 0);
const endPosition = new vscode.Position(Math.min(9, document.lineCount - 1), 0);
const range = new vscode.Range(startPosition, endPosition);
const firstTenLines = document.getText(range);The active editor manages the current cursor position and any text
selections via the selection property. This property is of
type Selection, which inherits from Range and
additionally includes information about the anchor and active position
of the selection.
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const selection = editor.selection;
if (selection.isEmpty) {
const cursorPosition = selection.active;
vscode.window.showInformationMessage(
`Cursor at line ${cursorPosition.line + 1}, column ${cursorPosition.character + 1}`
);
} else {
const selectedText = editor.document.getText(selection);
const lineCount = selection.end.line - selection.start.line + 1;
vscode.window.showInformationMessage(
`Selected: "${selectedText}" (${lineCount} lines)`
);
}The distinction between anchor and active
is important for understanding selection direction. The
anchor marks the starting point of the selection (where the
user began marking), while active represents the current
cursor position. In a backward selection, active precedes
anchor in document order.
For operations supporting multiple text ranges, VSCode also provides
selections (plural), an array of all active selections.
This enables multi-cursor operations, which are increasingly important
in modern editors.
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
const selections = editor.selections;
let totalSelectedChars = 0;
selections.forEach((selection, index) => {
if (!selection.isEmpty) {
const text = editor.document.getText(selection);
totalSelectedChars += text.length;
console.log(`Selection ${index + 1}: "${text}"`);
}
});
if (totalSelectedChars > 0) {
vscode.window.showInformationMessage(
`${selections.length} selections, ${totalSelectedChars} total characters`
);
}Changes to the document content in VSCode are made exclusively via
the edit system, which ensures transactional integrity and
undo capability. The simplest form is editor.edit(), which
takes an edit builder as a parameter and supports common operations like
insert, replace, and delete.
const editor = vscode.window.activeTextEditor;
if (!editor) {
return;
}
editor.edit(editBuilder => {
const position = editor.selection.active;
editBuilder.insert(position, "// TODO: Complete implementation\n");
}).then(success => {
if (success) {
vscode.window.showInformationMessage("Comment inserted");
} else {
vscode.window.showErrorMessage("Insert failed");
}
});For more complex operations involving multiple editors or files, use
WorkspaceEdit. This class allows you to collect changes and
execute them as an atomic operation.
editor.edit(editBuilder => {
if (!editor.selection.isEmpty) {
const selectedText = editor.document.getText(editor.selection);
const upperCaseText = selectedText.toUpperCase();
editBuilder.replace(editor.selection, upperCaseText);
}
});The VSCode edit system is asynchronous and returns a Promise indicating whether the operation succeeded. This allows you to react to errors and provide appropriate feedback to the user.
The most common source of errors when accessing the editor is using
activeTextEditor without checking. Since this property can
be undefined, every editor operation must begin with an
appropriate check. Consider creating a helper function for reusable
checks:
function getActiveEditor(): vscode.TextEditor | undefined {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage("No active text editor found");
return undefined;
}
return editor;
}Another critical point is handling different file types. Not all
operations make sense for all languages. Check the document’s
languageId before performing language-specific
operations:
const editor = getActiveEditor();
if (!editor) {
return;
}
const document = editor.document;
if (document.languageId !== 'typescript') {
vscode.window.showWarningMessage("This command only works with TypeScript files");
return;
}For operations involving text selections, you should consider various scenarios: empty selection (cursor only), single-line selection, multi-line selection, and multi-cursor scenarios. Defensive programming with explicit checks prevents unexpected behavior:
const selection = editor.selection;
if (selection.isEmpty) {
handleCursorOperation(editor);
} else if (selection.isSingleLine) {
handleSingleLineSelection(editor, selection);
} else {
handleMultiLineSelection(editor, selection);
}To deepen your understanding, consider three practical examples demonstrating common extension functionalities.
Example 1: Show file information in the status bar
function updateStatusBar() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
statusBarItem.hide();
return;
}
const document = editor.document;
const selection = editor.selection;
let statusText = `Lines: ${document.lineCount}`;
if (!selection.isEmpty) {
const selectedText = document.getText(selection);
statusText += ` | Selected: ${selectedText.length} characters`;
}
statusBarItem.text = statusText;
statusBarItem.show();
}
vscode.window.onDidChangeActiveTextEditor(updateStatusBar);
vscode.window.onDidChangeTextEditorSelection(updateStatusBar);Example 2: Apply bold Markdown formatting to selected text
function makeBold() {
const editor = getActiveEditor();
if (!editor) {
return;
}
if (editor.document.languageId !== 'markdown') {
vscode.window.showWarningMessage("Formatting only available in Markdown files");
return;
}
editor.edit(editBuilder => {
editor.selections.forEach(selection => {
if (selection.isEmpty) {
editBuilder.insert(selection.active, "**bold text**");
} else {
const selectedText = editor.document.getText(selection);
editBuilder.replace(selection, `**${selectedText}**`);
}
});
});
}Example 3: Insert code snippet at cursor position
function insertLogStatement() {
const editor = getActiveEditor();
if (!editor) {
return;
}
const position = editor.selection.active;
const line = editor.document.lineAt(position.line);
const indentation = line.text.match(/^\s*/)?.[0] || '';
const logStatement = `${indentation}console.log('Debug:', );\n`;
editor.edit(editBuilder => {
editBuilder.insert(position, logStatement);
}).then(() => {
const newPosition = position.with(position.line, position.character + logStatement.length - 3);
editor.selection = new vscode.Selection(newPosition, newPosition);
});
}