窗口与标签管理
VSCode 的编辑器区域由若干标签组构成,每个标签组是一排标签页(Tab)。插件通过 window.tabGroups 与 window.tabs 可以枚举、监听甚至移动这些标签,实现"自动打开文件""按类型整理标签"等窗口管理能力。
tabGroups 标签组
window.tabGroups 管理当前窗口的全部标签组。一个组通常对应一个编辑器分栏:
import * as vscode from 'vscode';
// 遍历所有标签组
for (const group of vscode.window.tabGroups.all) {
console.log(group.label); // 组的显示名(可自定义)
console.log(group.viewColumn); // 所在列:ViewColumn.One / Two ...
console.log(group.isActive); // 是否为当前活动组
for (const tab of group.tabs) {
console.log(tab.label, tab.input);
}
}
// 当前活动标签组与活动标签
const activeGroup = vscode.window.tabGroups.activeTabGroup;
const activeTab = activeGroup.activeTab;TabGroup 关键属性:
| 属性 | 含义 |
|---|---|
label | 标签组标题(用户可重命名) |
viewColumn | 组所在列,ViewColumn 枚举 |
isActive | 是否当前聚焦的组 |
tabs | 组内全部标签页 |
activeTab | 组内当前激活的标签页 |
TabInput 标签类型判断
每个 Tab 的 input 属性描述标签内容,具体类型由 TabInput 的子类承载。判断类型后才能拿到文件路径、Webview 视图类型等关键信息:
for (const tab of vscode.window.tabGroups.activeTabGroup.tabs) {
if (tab.input instanceof vscode.TabInputText) {
// 普通文本文件标签:拿到 uri
const uri = tab.input.uri;
console.log('文本文件:', uri.fsPath);
} else if (tab.input instanceof vscode.TabInputCustomEditor) {
// 自定义编辑器标签
console.log('自定义编辑器:', tab.input.viewType, tab.input.uri.fsPath);
} else if (tab.input instanceof vscode.TabInputWebview) {
// Webview 标签
console.log('Webview:', tab.input.viewType);
} else if (tab.input instanceof vscode.TabInputNotebook) {
// Notebook 标签
console.log('Notebook:', tab.input.notebookType, tab.input.uri.fsPath);
} else if (tab.input instanceof vscode.TabInputTextDiff) {
// Diff 编辑器标签:original / modified
console.log('Diff:', tab.input.original.uri.fsPath, 'vs', tab.input.modified.uri.fsPath);
}
}标签通用属性
const tab: vscode.Tab = /* ... */;
tab.isActive; // 是否当前激活标签
tab.isPinned; // 是否被固定(显示为 📌)
tab.isPreview; // 是否为预览标签(单击打开、未持久化的标签)
tab.group; // 所属标签组
tab.label; // 显示的标题
tab.input; // 内容描述(TabInput 子类)
tab.isDirty; // 是否有未保存修改isPreview 值得注意:单击从资源管理器打开的文件默认是预览标签,双击或编辑后才固定。插件批量打开文件时可通过 preview 选项控制这一行为。
标签变更监听
onDidChangeTabs
window.tabGroups.onDidChangeTabs 监听标签的增删与状态变化:
context.subscriptions.push(
vscode.window.tabGroups.onDidChangeTabs((event) => {
// event.changed / event.closed / event.opened
for (const tab of event.opened) {
console.log('新打开标签:', tab.label);
}
for (const tab of event.closed) {
console.log('关闭标签:', tab.label);
}
for (const tab of event.changed) {
console.log('标签变化:', tab.label,
'激活:', tab.isActive, '预览:', tab.isPreview);
}
})
);事件对象包含 opened、closed、changed 三个标签数组,对应新增、关闭与属性变化(激活切换、预览固定、脏状态变化都会进 changed)。
onDidChangeTabGroups
标签组的开合、激活与列位置变化由 onDidChangeTabGroups 上报:
context.subscriptions.push(
vscode.window.tabGroups.onDidChangeTabGroups((event) => {
for (const group of event.changed) {
console.log(`组 ${group.label} 变化,位于列 ${group.viewColumn}`);
}
for (const group of event.closed) {
console.log(`组 ${group.label} 已关闭`);
}
})
);showTextDocument 打开与定位文件
showTextDocument 是编程式打开文件的核心 API,通过 ViewColumn 控制打开位置:
// 1. 在活动列打开(默认行为)
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file('/workspace/a.ts'));
const editor = await vscode.window.showTextDocument(doc);
// 2. 指定列打开
await vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.Two, // 右侧一列
preserveFocus: false // 打开后获得焦点
});
// 3. 以预览标签打开
await vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.Beside, // 当前列旁边(自动新建列)
preview: true
});打开并选中代码
selection 与 reveal 组合实现"打开文件并跳到指定行":
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, {
viewColumn: vscode.ViewColumn.One,
selection: new vscode.Range(
new vscode.Position(10, 0),
new vscode.Position(10, 0)
), // 光标定位到第 10 行
reveal: vscode.TextEditorRevealType.InCenter // 让该行居中
});ViewColumn 相关值:
| 值 | 含义 |
|---|---|
One / Two / Three ... | 指定列 |
Beside | 当前编辑器的旁边(自动开新列) |
Active | 当前激活的列 |
-1 负数值 | 表示标签组(showTextDocument 也接受 TabGroup 对象) |
标签组分割与移动
从文件打开创建新组
ViewColumn.Beside 或传入 TabGroup 对象即可把文件开进新组:
// 把文件移动到标签组 B
const groupB = vscode.window.tabGroups.all[1];
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { viewColumn: groupB });命令式移动
通过 VSCode 内置命令移动标签,无需手动关闭再打开:
// 在当前组内切换到下一个标签
await vscode.commands.executeCommand('workbench.action.nextEditor');
// 把活动标签移动到下一组
await vscode.commands.executeCommand('workbench.action.moveEditorToNextGroup');
// 在当前组左右分割出一个新组
await vscode.commands.executeCommand('workbench.action.splitEditor');
// 关闭当前组的全部标签
await vscode.commands.executeCommand('workbench.action.closeEditorsInGroup');常用标签组命令一览:
| 命令 | 作用 |
|---|---|
workbench.action.splitEditor | 当前编辑器左右分栏 |
workbench.action.splitEditorOrthogonal | 当前编辑器上下分栏 |
workbench.action.moveEditorToNextGroup | 移到下一组 |
workbench.action.moveEditorToPreviousGroup | 移到上一组 |
workbench.action.focusNextGroup | 聚焦下一组 |
workbench.action.closeEditorsInGroup | 关闭组内全部标签 |
workbench.action.revertAndCloseActiveEditor | 关闭并丢弃未保存修改 |
实战:按目录整理标签
综合运用标签组 API 与命令,实现"把指定目录的文件都打开到新的右侧组":
import * as vscode from 'vscode';
async function openFolderInNewGroup(folder: string) {
// 1. 在当前编辑器右侧分割出一个新标签组
await vscode.commands.executeCommand('workbench.action.splitEditor');
const groups = vscode.window.tabGroups.all;
const target = groups[groups.length - 1]; // 新组在最后
// 2. 递归收集目录下的文件
const uris = await collectFiles(vscode.Uri.file(folder));
// 3. 依次在新组中打开(preview 让首个文件保持预览态)
for (let i = 0; i < uris.length; i++) {
const doc = await vscode.workspace.openTextDocument(uris[i]);
await vscode.window.showTextDocument(doc, {
viewColumn: target,
preview: i === 0 // 只有第一个是预览标签
});
}
vscode.window.showInformationMessage(
`已在右侧标签组打开 ${uris.length} 个文件`
);
}
async function collectFiles(dir: vscode.Uri): Promise<vscode.Uri[]> {
const entries = await vscode.workspace.fs.readDirectory(dir);
const result: vscode.Uri[] = [];
for (const [name, type] of entries) {
const uri = vscode.Uri.joinPath(dir, name);
if (type === vscode.FileType.Directory) {
result.push(...await collectFiles(uri));
} else {
result.push(uri);
}
}
return result;
}标签变更驱动的 UI 同步
监听标签变化可以驱动状态栏、Webview 等 UI 的实时更新:
const item = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right
);
item.text = '$(files) 0 个标签';
function updateCount() {
const total = vscode.window.tabGroups.all.reduce(
(sum, g) => sum + g.tabs.length, 0
);
item.text = `$(files) ${total} 个标签`;
}
// 任意标签变化都刷新计数
vscode.window.tabGroups.onDidChangeTabs(updateCount);
vscode.window.tabGroups.onDidChangeTabGroups(updateCount);
updateCount();
item.show();标签组 API 让插件的窗口编排能力从"只打开文件"升级为"编排整个编辑器布局":按组遍历、按类型过滤、按事件联动,配合内置命令即可实现复杂的多列布局管理。