多窗口与 Workspace
VSCode 的"窗口"概念有两层:编辑器窗格(Editor)与整个工作区(Workspace)。window 命名空间管理前者,workspace 命名空间管理后者。插件需要精确感知用户当前在看什么、工作区挂载了什么,才能做出恰到好处的响应。
activeTextEditor 与编辑器状态
window.activeTextEditor 指向当前获得焦点的编辑器,是最常用的入口:
const editor = vscode.window.activeTextEditor;
if (editor) {
console.log('当前文件:', editor.document.uri.fsPath);
console.log('语言:', editor.document.languageId);
console.log('光标位置:', editor.selection.active);
}注意两个易错点:没有打开编辑器时它可能是 undefined,必须判空;在多窗格布局下它只指向焦点所在的窗格,其余窗格属于 visibleTextEditors。
窗口切换监听
onDidChangeActiveTextEditor
焦点在编辑器之间切换时触发,参数可能为 undefined(焦点离开编辑器区域):
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (!editor) {
// 焦点移出编辑器(如聚焦到侧边栏)
console.log('焦点离开编辑器');
return;
}
console.log('切到:', editor.document.uri.fsPath);
})
);visibleTextEditors 与可见编辑器
visibleTextEditors 返回当前所有可见窗格中的编辑器实例(包括被分割窗格中的),并随布局变化实时刷新:
// 遍历所有可见编辑器
for (const editor of vscode.window.visibleTextEditors) {
const col = editor.viewColumn; // 所在列
const uri = editor.document.uri;
console.log(`列 ${col} 中显示: ${uri.fsPath}`);
}
// 可见编辑器集合变化(打开/关闭/分割/切换)时触发
context.subscriptions.push(
vscode.window.onDidChangeVisibleTextEditors((editors) => {
console.log(`当前可见 ${editors.length} 个编辑器`);
})
);activeTextEditor 与多窗格的关系
| 场景 | activeTextEditor | visibleTextEditors |
|---|---|---|
| 单窗格打开 2 个文件 | 当前显示的那个 | 仅当前显示的 1 个 |
| 左右分栏各显示 1 个文件 | 焦点所在侧的文件 | 2 个(两侧各一) |
| 焦点在侧边栏 | undefined 或保持上次值 | 不受影响 |
核心区别:active 是焦点语义,visible 是布局语义。批量同步 UI 时应遍历 visibleTextEditors,只关心"用户正在看哪个"时用 activeTextEditor。
组合示例:多窗格同步标记
// 对每个可见编辑器执行操作(如同步设置诊断可见性)
function syncAllVisibleEditors() {
for (const editor of vscode.window.visibleTextEditors) {
const config = vscode.workspace.getConfiguration(
'myext', editor.document.uri
);
editor.options = {
...editor.options,
renderWhitespace: config.get('renderWhitespace', 'none')
};
}
}
vscode.window.onDidChangeVisibleTextEditors(syncAllVisibleEditors);workspace.workspaceFile 工作区文件
多根工作区(multi-root workspace)的信息存于 .code-workspace 文件中,workspace.workspaceFile 返回其 URI:
const wsFile = vscode.workspace.workspaceFile;
if (wsFile) {
console.log('工作区文件:', wsFile.fsPath);
// 读取多根配置
const config = vscode.workspace.getConfiguration('folders', wsFile);
} else {
// 单文件夹模式(未打开 .code-workspace)时为 undefined
console.log('当前是单文件夹模式');
}workspace.workspaceFolders 返回挂载的全部根目录,处理多根场景时的标准写法:
// 遍历所有工作区根目录
for (const folder of vscode.workspace.workspaceFolders ?? []) {
console.log('根目录:', folder.uri.fsPath, '索引:', folder.index);
}
// 判断一个文件属于哪个根
function whichFolder(uri: vscode.Uri): vscode.WorkspaceFolder | undefined {
return vscode.workspace.getWorkspaceFolder(uri);
}workspace.fs 文件系统操作
workspace.fs 是 VSCode 提供的跨平台文件系统 API,与 Node.js 的 fs 模块定位不同:
| 对比维度 | workspace.fs | Node.js fs |
|---|---|---|
| 参数形式 | 一律使用 vscode.Uri | 使用字符串路径 |
| 异步模型 | 全部返回 Promise,统一异步 | 回调/同步/Promise 三种混用 |
| 扩展性 | 支持虚拟文件系统 provider | 只访问本地磁盘 |
| 远程工作区 | 原生支持(SSH/容器/WSL) | 需要自己处理映射 |
| 权限 | 受 VSCode 安全策略约束 | 完整系统权限 |
| 写文件 | 整体覆盖 | 支持流式追加 |
基础操作示例
import * as vscode from 'vscode';
async function fileSystemDemo() {
const uri = vscode.Uri.file('/workspace/data.json');
// 1. 读文件(返回 Uint8Array,需解码)
const bytes = await vscode.workspace.fs.readFile(uri);
const text = Buffer.from(bytes).toString('utf8');
// 2. 写文件(整体覆盖)
await vscode.workspace.fs.writeFile(
uri,
Buffer.from('{"ok": true}', 'utf8')
);
// 3. 创建目录(递归)
await vscode.workspace.fs.createDirectory(
vscode.Uri.joinPath(vscode.Uri.file('/workspace'), 'build', 'dist')
);
// 4. 遍历目录
const entries = await vscode.workspace.fs.readDirectory(
vscode.Uri.file('/workspace')
);
for (const [name, type] of entries) {
console.log(name, type === vscode.FileType.Directory ? '[目录]' : '[文件]');
}
// 5. 删除(recursive 删除目录树)
await vscode.workspace.fs.delete(
vscode.Uri.file('/workspace/build'),
{ recursive: true, useTrash: false }
);
// 6. 重命名/移动
await vscode.workspace.fs.rename(
vscode.Uri.file('/workspace/old.ts'),
vscode.Uri.file('/workspace/new.ts'),
{ overwrite: true }
);
}流式处理大文件
readFile 一次性读入内存,处理大文件时应改用 openReadStream:
// 逐块读取,避免内存暴涨
async function tailLines(uri: vscode.Uri, count: number) {
const stream = await vscode.workspace.fs.openReadStream(uri);
const chunks: Uint8Array[] = [];
// 监听数据块
stream.on('data', (chunk: Uint8Array) => chunks.push(chunk));
await new Promise((resolve, reject) => {
stream.once('end', resolve);
stream.once('error', reject);
});
const full = Buffer.concat(chunks.map(c => Buffer.from(c))).toString('utf8');
return full.split('\n').slice(-count).join('\n');
}何时仍需要 Node.js fs
workspace.fs 无法覆盖所有场景,以下情况使用 Node.js fs 更合适:
import * as fs from 'fs';
import * as fsp from 'fs/promises';
// 1. 流式写大文件(workspace.fs 只能整体覆盖)
const ws = fs.createWriteStream('/workspace/log.txt', { flags: 'a' });
ws.write('append line\n');
// 2. 文件存在性快速判断
if (fs.existsSync('/workspace/node_modules')) { /* ... */ }
// 3. 复杂权限/统计信息
const stat = fs.statSync('/workspace/a.ts');
console.log(stat.mtime, stat.size);选择建议:读写普通文件、遍历目录、跨平台移动一律用 workspace.fs(自动适配远程环境);需要流式追加、文件统计、文件监听时退回 Node.js fs。监听文件用 fs.watch 与 fs.watchFile 时注意内存与平台差异,VSCode 场景优先考虑 workspace.createFileSystemWatcher。
实战:多窗格目录浏览器
综合窗口与文件系统 API,做一个把目录内容同步到所有可见编辑器的简单同步器:
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
// 监听:任何可见编辑器变化时,同步状态栏信息
const item = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Left, 100
);
function refreshStatus() {
const active = vscode.window.activeTextEditor;
const visible = vscode.window.visibleTextEditors.length;
item.text = active
? `$(eye) ${active.document.fileName} | 可见 ${visible} 个窗格`
: `$(eye) 无活动编辑器 | 可见 ${visible} 个窗格`;
}
context.subscriptions.push(
vscode.window.onDidChangeActiveTextEditor(refreshStatus),
vscode.window.onDidChangeVisibleTextEditors(refreshStatus)
);
// 命令:把当前文件复制到 workspace 根目录
context.subscriptions.push(
vscode.commands.registerCommand('demo.copyToRoot', async () => {
const editor = vscode.window.activeTextEditor;
const root = vscode.workspace.workspaceFolders?.[0];
if (!editor || !root) {
vscode.window.showWarningMessage('没有活动编辑器或工作区');
return;
}
const source = editor.document.uri;
const target = vscode.Uri.joinPath(
root.uri, 'backup-' + source.path.split('/').pop()
);
await vscode.workspace.fs.copy(source, target, { overwrite: true });
vscode.window.showInformationMessage(`已复制到 ${target.fsPath}`);
})
);
refreshStatus();
item.show();
}窗口的焦点语义与可见语义、工作区的单根与多根形态、文件系统的 URI 抽象,三者共同决定了插件感知环境的方式。选对 API,插件在远程开发、多窗格布局下也能保持一致行为。