Programmatic manipulation of text content is the core of most VSCode extensions. Extensions can analyze, transform, or automatically generate content. Typical use cases include analyzing existing code sections, selectively replacing text ranges, inserting templates or boilerplate code, and performing automated corrections.
The VSCode API distinguishes between two basic operation types: read
operations are synchronous via the getText() method, while
write operations are asynchronous via edit() or
WorkspaceEdit.apply() calls. This distinction stems from
VSCode’s architecture, where write operations must be coordinated
through the change management system.
The TextEditor provides a TextDocument via
its document property, which allows synchronous access to
the text content. The getText() method without parameters
returns the entire document content, while the overloaded version
getText(range) returns only the content of a specified
range.
The editor’s selection property represents the currently
selected range and can be used directly as a Range
parameter:
import * as vscode from 'vscode';
export function showSelectedText(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('No active editor available');
return;
}
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
const displayText = selectedText.length > 0
? selectedText
: 'No selection made';
vscode.window.showInformationMessage(`Selected text: ${displayText}`);
}To access specific text ranges, construct Range objects
with start and end positions. Each Position is defined by
line and column indices, both zero-based:
export function readSpecificRange(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const range = new vscode.Range(
new vscode.Position(0, 0),
new vscode.Position(2, 0)
);
const text = editor.document.getText(range);
console.log('First three lines:', text);
}To access files outside the active editor, use
vscode.workspace.openTextDocument(). This method opens
files as TextDocument without necessarily displaying them
in the editor:
export async function readFileFromWorkspace(relativePath: string): Promise<string> {
try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
throw new Error('No workspace open');
}
const fileUri = vscode.Uri.joinPath(workspaceFolder.uri, relativePath);
const document = await vscode.workspace.openTextDocument(fileUri);
return document.getText();
} catch (error) {
vscode.window.showErrorMessage(`Could not read file: ${error}`);
return '';
}
}The openTextDocument() method resolves the returned
Promise with a TextDocument, which can then be treated like
a document from the active editor. VSCode automatically caches opened
documents, so repeated calls are performant.
Write operations in the active editor are done via the
edit() method, which provides a TextEditorEdit
builder as a callback parameter. This builder collects all desired
changes and applies them atomically:
export function insertTextAtCursor(text: string): void {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
editor.edit(editBuilder => {
editBuilder.insert(editor.selection.active, text);
});
}The selection.active property represents the cursor
position. In case of a text selection, this corresponds to the end of
the selection. To insert at the beginning of a selection, use
selection.anchor.
Replacing text is done using the replace() method, which
takes a range and the new text:
export function wrapSelectionInTags(tag: string): void {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
editor.edit(editBuilder => {
const wrappedText = `<${tag}>${selectedText}</${tag}>`;
editBuilder.replace(selection, wrappedText);
});
}To delete text, use the delete() method with a
range:
export function deleteCurrentLine(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const position = editor.selection.active;
const line = editor.document.lineAt(position.line);
editor.edit(editBuilder => {
editBuilder.delete(line.rangeIncludingLineBreak);
});
}Complex edit operations involving multiple changes can be performed
within a single edit() call. This ensures that all changes
are treated as a single undo operation:
export function formatSelectionAsComment(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const selection = editor.selection;
const selectedText = editor.document.getText(selection);
editor.edit(editBuilder => {
editBuilder.insert(selection.start, '/* ');
editBuilder.insert(selection.end, ' */');
});
}To modify files outside the active editor or for cross-file
refactorings, use WorkspaceEdit. This class allows
coordination of changes across multiple files:
export async function replaceInFile(
filePath: string,
searchText: string,
replaceText: string
): Promise<void> {
try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) return;
const fileUri = vscode.Uri.joinPath(workspaceFolder.uri, filePath);
const document = await vscode.workspace.openTextDocument(fileUri);
const fullText = document.getText();
const index = fullText.indexOf(searchText);
if (index === -1) {
vscode.window.showInformationMessage(`Text "${searchText}" not found`);
return;
}
const startPosition = document.positionAt(index);
const endPosition = document.positionAt(index + searchText.length);
const range = new vscode.Range(startPosition, endPosition);
const edit = new vscode.WorkspaceEdit();
edit.replace(fileUri, range, replaceText);
const success = await vscode.workspace.applyEdit(edit);
if (success) {
vscode.window.showInformationMessage('Text successfully replaced');
}
} catch (error) {
vscode.window.showErrorMessage(`Error during replacement: ${error}`);
}
}To replace the entire contents of a file, construct a range that spans the entire document:
export async function replaceFileContent(
filePath: string,
newContent: string
): Promise<void> {
try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) return;
const fileUri = vscode.Uri.joinPath(workspaceFolder.uri, filePath);
const document = await vscode.workspace.openTextDocument(fileUri);
const fullRange = new vscode.Range(
new vscode.Position(0, 0),
new vscode.Position(document.lineCount, 0)
);
const edit = new vscode.WorkspaceEdit();
edit.replace(fileUri, fullRange, newContent);
await vscode.workspace.applyEdit(edit);
} catch (error) {
vscode.window.showErrorMessage(`Error writing file: ${error}`);
}
}To create completely new files, use the
vscode.workspace.fs API. This interface works with
Uint8Array data, so text content must be converted using
TextEncoder:
export async function createNewFile(
fileName: string,
content: string
): Promise<void> {
try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
vscode.window.showWarningMessage('No workspace open');
return;
}
const fileUri = vscode.Uri.joinPath(workspaceFolder.uri, fileName);
try {
await vscode.workspace.fs.stat(fileUri);
vscode.window.showWarningMessage(`File ${fileName} already exists`);
return;
} catch {}
const encoder = new TextEncoder();
const data = encoder.encode(content);
await vscode.workspace.fs.writeFile(fileUri, data);
const document = await vscode.workspace.openTextDocument(fileUri);
await vscode.window.showTextDocument(document);
vscode.window.showInformationMessage(`File ${fileName} created`);
} catch (error) {
vscode.window.showErrorMessage(`Error creating file: ${error}`);
}
}To create files from predefined templates, combine file creation with text replacement:
export async function createFromTemplate(
fileName: string,
templateName: string,
replacements: { [key: string]: string }
): Promise<void> {
try {
const templatePath = `templates/${templateName}.template`;
let templateContent = await readFileFromWorkspace(templatePath);
for (const [placeholder, value] of Object.entries(replacements)) {
const pattern = new RegExp(`\\$\\{${placeholder}\\}`, 'g');
templateContent = templateContent.replace(pattern, value);
}
await createNewFile(fileName, templateContent);
} catch (error) {
vscode.window.showErrorMessage(`Template error: ${error}`);
}
}All operations involving the active editor must check whether it is available. VSCode can start without any files open, and extensions must account for this state:
export function safeEditorOperation(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage(
'This function requires an open editor'
);
return;
}
if (editor.document.isUntitled) {
vscode.window.showWarningMessage(
'This function cannot be used on unsaved files'
);
return;
}
editor.edit(editBuilder => {
editBuilder.insert(editor.selection.active, 'Safe text');
});
}All write operations and file access are asynchronous and must be handled accordingly:
export async function robustFileOperation(): Promise<void> {
try {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const editSuccess = await editor.edit(editBuilder => {
editBuilder.insert(editor.selection.active, 'Test');
});
if (!editSuccess) {
vscode.window.showErrorMessage('Edit operation failed');
return;
}
const saveSuccess = await editor.document.save();
if (!saveSuccess) {
vscode.window.showErrorMessage('Save failed');
}
} catch (error) {
vscode.window.showErrorMessage(`Unexpected error: ${error}`);
}
}For frequent or extensive text operations, prefer
WorkspaceEdit over multiple individual edit()
calls:
export async function efficientMultipleEdits(
changes: Array<{ range: vscode.Range; text: string }>
): Promise<void> {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const workspaceEdit = new vscode.WorkspaceEdit();
for (const change of changes) {
workspaceEdit.replace(editor.document.uri, change.range, change.text);
}
await vscode.workspace.applyEdit(workspaceEdit);
}This function analyzes function signatures and generates JSDoc comments:
export function generateJSDocComment(): void {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const position = editor.selection.active;
const line = editor.document.lineAt(position.line);
const functionMatch = line.text.match(/function\s+(\w+)\s*\(([^)]*)\)/);
if (!functionMatch) {
vscode.window.showWarningMessage('No function at current position');
return;
}
const [, functionName, parameters] = functionMatch;
const paramList = parameters
.split(',')
.map(p => p.trim())
.filter(p => p.length > 0);
let comment = '/**\n';
comment += ` * Description for ${functionName}\n`;
for (const param of paramList) {
const paramName = param.split(':')[0].trim();
comment += ` * @param ${paramName} Description for ${paramName}\n`;
}
comment += ' * @returns Return value\n';
comment += ' */\n';
editor.edit(editBuilder => {
editBuilder.insert(new vscode.Position(position.line, 0), comment);
});
}This function applies a transformation to all open documents:
export async function addHeaderToAllFiles(header: string): Promise<void> {
const openDocuments = vscode.workspace.textDocuments;
const workspaceEdit = new vscode.WorkspaceEdit();
for (const document of openDocuments) {
if (document.isUntitled || document.uri.scheme !== 'file') {
continue;
}
const insertPosition = new vscode.Position(0, 0);
workspaceEdit.insert(document.uri, insertPosition, header + '\n\n');
}
if (workspaceEdit.size > 0) {
const success = await vscode.workspace.applyEdit(workspaceEdit);
const message = success
? `Header added to ${workspaceEdit.size} files`
: 'Error adding headers';
vscode.window.showInformationMessage(message);
} else {
vscode.window.showInformationMessage('No suitable files found');
}
}