SourceControlResourceGroup 资源组管理
源码管理视图的核心是资源组:它把文件按状态分组展示,是"更改列表"的视觉载体。本文深入资源组的创建、内容更新与装饰。
创建资源组
每个 SCM 实例可以有多个资源组:
typescript
import * as vscode from 'vscode';
const scm = vscode.scm.createSourceControl('myscm', 'MySCM');
// 修改组
const modifiedGroup = scm.createResourceGroup('modified', '更改');
// 新增组
const untrackedGroup = scm.createResourceGroup('untracked', '未跟踪');
// 已暂存组
const stagedGroup = scm.createResourceGroup('staged', '已暂存');| 方法 | 说明 |
|---|---|
createResourceGroup(id, label) | 创建资源组并返回 |
dispose() | 销毁资源组 |
resourceStates | 设置组内资源状态列表 |
hideWhenEmpty
typescript
// 组为空时自动隐藏(Git 的"更改"组默认行为)
stagedGroup.hideWhenEmpty = true;
// 设为 false 则始终显示,即使没有内容
modifiedGroup.hideWhenEmpty = false;文件状态建模
SourceControlResourceState 描述单个文件的版本控制状态:
typescript
// 状态工具函数
function makeResource(
uri: vscode.Uri,
opts: {
modified?: boolean;
added?: boolean;
deleted?: boolean;
} = {}
): vscode.SourceControlResourceState {
// 装饰决定资源管理器中的表现
const decorations: vscode.SourceControlResourceDecorations = {};
if (opts.modified) {
decorations.letter = 'M'; // 资源图标角标字母
decorations.tooltip = '已修改'; // 悬停提示
decorations.strikeThrough = false; // 划线(删除时 true)
decorations.color = new vscode.ThemeColor('gitDecoration.modifiedResourceForeground');
decorations.dark = { iconPath: ... }; // 自定义图标(可选)
}
if (opts.added) {
decorations.letter = 'A';
decorations.tooltip = '已新增';
decorations.color = new vscode.ThemeColor('gitDecoration.addedResourceForeground');
}
if (opts.deleted) {
decorations.letter = 'D';
decorations.tooltip = '已删除';
decorations.strikeThrough = true; // 删除的文件显示删除线
decorations.color = new vscode.ThemeColor('gitDecoration.deletedResourceForeground');
}
return {
resourceUri: uri,
contextValue: opts.deleted ? 'myscm.deleted' : 'myscm.changed',
tooltip: opts.modified ? '文件已被修改' : '文件已新增',
decorations
};
}三种状态对比
| 状态 | letter | strikeThrough | 颜色 |
|---|---|---|---|
| 修改 | M | false | 黄色(modified 前景色) |
| 新增 | A | false | 绿色(added 前景色) |
| 删除 | D | true | 红色(deleted 前景色) |
更新资源组内容
typescript
// 扫描工作区并更新
async function refreshGroups() {
const statuses = await scanFileStatuses();
// 过滤分组
modifiedGroup.resourceStates = statuses
.filter((s) => s.status === 'modified')
.map((s) => makeResource(s.uri, { modified: true }));
untrackedGroup.resourceStates = statuses
.filter((s) => s.status === 'untracked')
.map((s) => makeResource(s.uri, { added: true }));
stagedGroup.resourceStates = statuses
.filter((s) => s.status === 'staged')
.map((s) => makeResource(s.uri, { modified: true }));
// 更新徽章计数
scm.count = statuses.length;
}resourceStates 赋值后视图自动刷新,无需手动触发事件。
装饰详解
SourceControlResourceDecorations 提供丰富的表现能力:
内置装饰字段
| 字段 | 作用 |
|---|---|
letter | 资源图标右下角的字母徽标 |
tooltip | 悬停提示文字 |
color | 文件名颜色(ThemeColor) |
strikeThrough | 是否加删除线 |
dark / light | 明暗主题各自的图标路径 |
iconPath | 自定义图标 |
source | 额外徽章来源(如"已忽略") |
propagated | 是否传播到子项 |
主题颜色变量
VS Code 内置了一组 Git 装饰颜色,可直接复用:
typescript
import * as vscode from 'vscode';
function gitColor(kind: string): vscode.ThemeColor {
// 常见内置颜色键
const colors = {
added: 'gitDecoration.addedResourceForeground',
modified: 'gitDecoration.modifiedResourceForeground',
deleted: 'gitDecoration.deletedResourceForeground',
untracked: 'gitDecoration.untrackedResourceForeground',
ignored: 'gitDecoration.ignoredResourceForeground',
conflicting: 'gitDecoration.conflictingResourceForeground'
};
return new vscode.ThemeColor(colors[kind]);
}自定义图标
typescript
// 为特定状态提供独立图标(相对扩展目录路径)
const state: vscode.SourceControlResourceState = {
resourceUri: uri,
decorations: {
// 轻量方式:用 codicon
iconPath: new vscode.ThemeIcon('lock'),
// 或自定义 SVG(需要为 dark/light 分别提供)
dark: {
iconPath: vscode.Uri.joinPath(context.extensionUri, 'media', 'lock-dark.svg')
},
light: {
iconPath: vscode.Uri.joinPath(context.extensionUri, 'media', 'lock-light.svg')
}
}
};资源点击命令
点击资源列表中的文件时执行 command。最常见的动作是打开差异视图:
typescript
import * as path from 'path';
function makeDiffCommand(
current: vscode.Uri,
original: vscode.Uri | undefined
): vscode.Command {
const fileName = path.basename(current.fsPath);
return {
// 内置命令:打开差异编辑器
command: 'vscode.diff',
title: '比较更改',
arguments: [
original, // 左:原始版本
current, // 右:当前版本
`${fileName} (原始) ↔ ${fileName} (工作区)`,
// 第四个参数:在 diff 编辑器中显示左侧原始内容
{ preview: true }
]
};
}
const state: vscode.SourceControlResourceState = {
resourceUri: currentFileUri,
command: makeDiffCommand(currentFileUri, snapshotUri)
};多状态文件
一个文件同时有多个状态(如"新增且暂存"),用 resourceStates 嵌套:
typescript
// 分组展开显示不同状态子项
const fileWithMultipleStates: vscode.SourceControlResourceState = {
resourceUri: vscode.Uri.file('/repo/config.json'),
contextValue: 'myscm.file',
// 子状态:文件本身 + 其子资源
resourceStates: [
{
resourceUri: vscode.Uri.file('/repo/config.json'),
contextValue: 'myscm.staged',
command: makeDiffCommand(...)
},
{
resourceUri: vscode.Uri.file('/repo/config.json'),
contextValue: 'myscm.modified',
command: makeDiffCommand(...)
}
]
};右键菜单与上下文值
contextValue 结合菜单 when 条件实现差异化菜单:
json
{
"contributes": {
"menus": {
"scm/resourceState/context": [
{
"command": "myscm.stage",
"when": "scmProvider == myscm && resourceState.contextValue == myscm.changed",
"group": "1_modification",
"title": "暂存更改"
},
{
"command": "myscm.discard",
"when": "scmProvider == myscm && resourceState.contextValue == myscm.changed",
"group": "2_actions",
"title": "放弃更改"
},
{
"command": "myscm.restore",
"when": "scmProvider == myscm && resourceState.contextValue == myscm.deleted",
"group": "1_modification",
"title": "恢复文件"
}
]
}
}
}typescript
// 菜单命令实现
vscode.commands.registerCommand('myscm.stage', (state: vscode.SourceControlResourceState) => {
const uri = state.resourceUri;
stageFile(uri);
refreshGroups(); // 更新后视图自动刷新
});文件状态变更监听
资源组内容需要随文件系统变化刷新,监听文件事件:
typescript
import * as vscode from 'vscode';
// 监听文件保存与删除
const watcher = vscode.workspace.createFileSystemWatcher('**/*');
watcher.onDidChange((uri) => {
if (uri.fsPath.includes('/.snapshots/')) return; // 忽略自身数据
refreshGroups();
});
watcher.onDidCreate((uri) => {
refreshGroups();
});
watcher.onDidDelete((uri) => {
refreshGroups();
});
// 或监听文档保存事件(更精确)
vscode.workspace.onDidSaveTextDocument((doc) => {
if (isUnderRepo(doc.uri)) {
refreshGroups();
}
});刷新时注意防抖,避免高频事件反复扫描:
typescript
let refreshTimer: NodeJS.Timeout | undefined;
function scheduleRefresh() {
if (refreshTimer) clearTimeout(refreshTimer);
refreshTimer = setTimeout(() => refreshGroups(), 200);
}资源组把文件状态可视化地组织起来,配合装饰与命令,构成了 SCM 视图的主体交互。下一步接入提交输入框,让提交流程完整。