文档 API
文档 API 提供了管理和操作 VS Code 中文档的功能,包括文档内容访问、编辑、保存、以及文档生命周期管理。
概述
在 VS Code 中,文档(Document)代表编辑器中打开的文件内容。文档 API 允许扩展程序:
- 访问和修改文档内容
- 监听文档变化事件
- 管理文档状态
- 执行文档操作
- 处理文档保存和关闭
主要接口
TextDocument
typescript
interface TextDocument {
/**
* 文档的 URI
*/
readonly uri: Uri;
/**
* 文档的文件名
*/
readonly fileName: string;
/**
* 文档是否未保存
*/
readonly isUntitled: boolean;
/**
* 文档的语言标识符
*/
readonly languageId: string;
/**
* 文档版本号
*/
readonly version: number;
/**
* 文档是否已修改
*/
readonly isDirty: boolean;
/**
* 文档是否已关闭
*/
readonly isClosed: boolean;
/**
* 保存文档
*/
save(): Thenable<boolean>;
/**
* 获取文档的行数
*/
readonly lineCount: number;
/**
* 获取指定行的内容
*/
lineAt(line: number): TextLine;
lineAt(position: Position): TextLine;
/**
* 获取指定范围的文本
*/
getText(range?: Range): string;
/**
* 获取指定位置的单词范围
*/
getWordRangeAtPosition(position: Position, regex?: RegExp): Range | undefined;
/**
* 验证位置是否有效
*/
validatePosition(position: Position): Position;
/**
* 验证范围是否有效
*/
validateRange(range: Range): Range;
/**
* 获取位置的偏移量
*/
offsetAt(position: Position): number;
/**
* 根据偏移量获取位置
*/
positionAt(offset: number): Position;
}TextLine
typescript
interface TextLine {
/**
* 行号(从0开始)
*/
readonly lineNumber: number;
/**
* 行的完整文本
*/
readonly text: string;
/**
* 行的范围
*/
readonly range: Range;
/**
* 行的范围(包含行结束符)
*/
readonly rangeIncludingLineBreak: Range;
/**
* 第一个非空白字符的索引
*/
readonly firstNonWhitespaceCharacterIndex: number;
/**
* 行是否为空或只包含空白字符
*/
readonly isEmptyOrWhitespace: boolean;
}文档访问
获取文档
typescript
import * as vscode from 'vscode';
// 获取当前活动文档
const activeDocument = vscode.window.activeTextEditor?.document;
// 获取所有打开的文档
const allDocuments = vscode.workspace.textDocuments;
// 根据 URI 获取文档
const uri = vscode.Uri.file('/path/to/file.ts');
const document = await vscode.workspace.openTextDocument(uri);
// 创建新的未命名文档
const newDocument = await vscode.workspace.openTextDocument({
language: 'typescript',
content: '// 新文档内容'
});文档内容读取
typescript
function analyzeDocument(document: vscode.TextDocument) {
// 获取文档基本信息
console.log(`文件名: ${document.fileName}`);
console.log(`语言: ${document.languageId}`);
console.log(`行数: ${document.lineCount}`);
console.log(`是否已修改: ${document.isDirty}`);
// 获取全部文本
const fullText = document.getText();
// 获取指定范围的文本
const range = new vscode.Range(0, 0, 10, 0); // 前10行
const partialText = document.getText(range);
// 逐行读取
for (let i = 0; i < document.lineCount; i++) {
const line = document.lineAt(i);
console.log(`第${i + 1}行: ${line.text}`);
}
}文档监听
文档变化事件
typescript
// 监听文档打开
vscode.workspace.onDidOpenTextDocument(document => {
console.log(`文档已打开: ${document.fileName}`);
});
// 监听文档关闭
vscode.workspace.onDidCloseTextDocument(document => {
console.log(`文档已关闭: ${document.fileName}`);
});
// 监听文档内容变化
vscode.workspace.onDidChangeTextDocument(event => {
const document = event.document;
console.log(`文档已修改: ${document.fileName}`);
// 处理具体的变化
event.contentChanges.forEach(change => {
console.log(`变化范围: ${change.range}`);
console.log(`新文本: ${change.text}`);
});
});
// 监听文档保存
vscode.workspace.onDidSaveTextDocument(document => {
console.log(`文档已保存: ${document.fileName}`);
});文档状态监听
typescript
// 监听文档状态变化
vscode.workspace.onDidChangeTextDocument(event => {
const document = event.document;
if (document.isDirty) {
console.log('文档有未保存的更改');
}
// 检查特定类型的文档
if (document.languageId === 'typescript') {
// 处理 TypeScript 文档变化
handleTypeScriptDocumentChange(event);
}
});
function handleTypeScriptDocumentChange(event: vscode.TextDocumentChangeEvent) {
// TypeScript 特定的处理逻辑
const document = event.document;
// 检查是否有语法错误
checkSyntaxErrors(document);
// 更新类型信息
updateTypeInformation(document);
}文档操作
文档编辑
typescript
async function editDocument(document: vscode.TextDocument) {
const editor = await vscode.window.showTextDocument(document);
// 创建编辑操作
const edit = new vscode.WorkspaceEdit();
// 在文档开头插入文本
const insertPosition = new vscode.Position(0, 0);
edit.insert(document.uri, insertPosition, '// 自动生成的注释\n');
// 替换指定范围的文本
const replaceRange = new vscode.Range(1, 0, 1, 10);
edit.replace(document.uri, replaceRange, 'new content');
// 删除指定范围的文本
const deleteRange = new vscode.Range(2, 0, 3, 0);
edit.delete(document.uri, deleteRange);
// 应用编辑
await vscode.workspace.applyEdit(edit);
}文档格式化
typescript
async function formatDocument(document: vscode.TextDocument) {
// 获取格式化编辑
const formatEdits = await vscode.commands.executeCommand<vscode.TextEdit[]>(
'vscode.executeFormatDocumentProvider',
document.uri
);
if (formatEdits) {
const edit = new vscode.WorkspaceEdit();
edit.set(document.uri, formatEdits);
await vscode.workspace.applyEdit(edit);
}
}
async function formatRange(document: vscode.TextDocument, range: vscode.Range) {
// 格式化指定范围
const formatEdits = await vscode.commands.executeCommand<vscode.TextEdit[]>(
'vscode.executeFormatRangeProvider',
document.uri,
range
);
if (formatEdits) {
const edit = new vscode.WorkspaceEdit();
edit.set(document.uri, formatEdits);
await vscode.workspace.applyEdit(edit);
}
}实用示例
文档分析器
typescript
class DocumentAnalyzer {
analyzeDocument(document: vscode.TextDocument): DocumentAnalysis {
const analysis: DocumentAnalysis = {
fileName: document.fileName,
languageId: document.languageId,
lineCount: document.lineCount,
characterCount: document.getText().length,
wordCount: this.countWords(document.getText()),
emptyLines: 0,
commentLines: 0,
codeLines: 0
};
// 逐行分析
for (let i = 0; i < document.lineCount; i++) {
const line = document.lineAt(i);
if (line.isEmptyOrWhitespace) {
analysis.emptyLines++;
} else if (this.isCommentLine(line.text, document.languageId)) {
analysis.commentLines++;
} else {
analysis.codeLines++;
}
}
return analysis;
}
private countWords(text: string): number {
return text.split(/\s+/).filter(word => word.length > 0).length;
}
private isCommentLine(text: string, languageId: string): boolean {
const trimmed = text.trim();
switch (languageId) {
case 'typescript':
case 'javascript':
return trimmed.startsWith('//') || trimmed.startsWith('/*');
case 'python':
return trimmed.startsWith('#');
case 'html':
return trimmed.startsWith('<!--');
default:
return false;
}
}
}
interface DocumentAnalysis {
fileName: string;
languageId: string;
lineCount: number;
characterCount: number;
wordCount: number;
emptyLines: number;
commentLines: number;
codeLines: number;
}文档同步器
typescript
class DocumentSynchronizer {
private disposables: vscode.Disposable[] = [];
private documentStates = new Map<string, DocumentState>();
constructor() {
this.setupEventListeners();
}
private setupEventListeners() {
// 监听文档变化
this.disposables.push(
vscode.workspace.onDidChangeTextDocument(this.onDocumentChanged.bind(this))
);
// 监听文档保存
this.disposables.push(
vscode.workspace.onDidSaveTextDocument(this.onDocumentSaved.bind(this))
);
// 监听文档关闭
this.disposables.push(
vscode.workspace.onDidCloseTextDocument(this.onDocumentClosed.bind(this))
);
}
private onDocumentChanged(event: vscode.TextDocumentChangeEvent) {
const document = event.document;
const uri = document.uri.toString();
// 更新文档状态
const state = this.documentStates.get(uri) || {
version: 0,
lastModified: Date.now(),
changeCount: 0
};
state.version = document.version;
state.lastModified = Date.now();
state.changeCount++;
this.documentStates.set(uri, state);
// 触发同步逻辑
this.syncDocument(document, event.contentChanges);
}
private onDocumentSaved(document: vscode.TextDocument) {
const uri = document.uri.toString();
const state = this.documentStates.get(uri);
if (state) {
state.lastSaved = Date.now();
this.documentStates.set(uri, state);
}
console.log(`文档已保存: ${document.fileName}`);
}
private onDocumentClosed(document: vscode.TextDocument) {
const uri = document.uri.toString();
this.documentStates.delete(uri);
console.log(`文档已关闭: ${document.fileName}`);
}
private syncDocument(document: vscode.TextDocument, changes: readonly vscode.TextDocumentContentChangeEvent[]) {
// 实现文档同步逻辑
console.log(`同步文档: ${document.fileName}, 变化数量: ${changes.length}`);
}
dispose() {
this.disposables.forEach(d => d.dispose());
this.documentStates.clear();
}
}
interface DocumentState {
version: number;
lastModified: number;
lastSaved?: number;
changeCount: number;
}文档备份管理器
typescript
class DocumentBackupManager {
private backupInterval = 30000; // 30秒
private backupTimer?: NodeJS.Timeout;
private disposables: vscode.Disposable[] = [];
constructor(private backupPath: string) {
this.setupAutoBackup();
}
private setupAutoBackup() {
// 定期备份
this.backupTimer = setInterval(() => {
this.backupAllDocuments();
}, this.backupInterval);
// 监听文档保存,创建备份
this.disposables.push(
vscode.workspace.onDidSaveTextDocument(this.createBackup.bind(this))
);
}
private async backupAllDocuments() {
const documents = vscode.workspace.textDocuments;
for (const document of documents) {
if (document.isDirty && !document.isUntitled) {
await this.createBackup(document);
}
}
}
private async createBackup(document: vscode.TextDocument) {
try {
const backupFileName = this.generateBackupFileName(document);
const backupUri = vscode.Uri.file(path.join(this.backupPath, backupFileName));
const content = document.getText();
await vscode.workspace.fs.writeFile(backupUri, Buffer.from(content, 'utf8'));
console.log(`备份已创建: ${backupFileName}`);
} catch (error) {
console.error('创建备份失败:', error);
}
}
private generateBackupFileName(document: vscode.TextDocument): string {
const fileName = path.basename(document.fileName);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
return `${fileName}.${timestamp}.backup`;
}
dispose() {
if (this.backupTimer) {
clearInterval(this.backupTimer);
}
this.disposables.forEach(d => d.dispose());
}
}最佳实践
1. 性能优化
typescript
// 避免频繁的文档访问
let cachedContent: string | undefined;
let cachedVersion: number = -1;
function getDocumentContent(document: vscode.TextDocument): string {
if (cachedVersion !== document.version) {
cachedContent = document.getText();
cachedVersion = document.version;
}
return cachedContent!;
}2. 错误处理
typescript
async function safeDocumentOperation(document: vscode.TextDocument) {
try {
if (document.isClosed) {
throw new Error('文档已关闭');
}
// 执行文档操作
await performDocumentOperation(document);
} catch (error) {
vscode.window.showErrorMessage(`文档操作失败: ${error.message}`);
}
}3. 资源管理
typescript
// 正确管理事件监听器
const disposables: vscode.Disposable[] = [];
disposables.push(
vscode.workspace.onDidChangeTextDocument(handler)
);
// 在适当时机清理
export function deactivate() {
disposables.forEach(d => d.dispose());
}