vscode C/C++ 中关于函数内的静态变量着色问题
·
一直以来用的是微软的C/C++ 插件,但是它相比较clangd 的少了一些语义,其中就包含不能区别静态变量与普通变量。


工作中大部分场景编写的是嵌入式的代码,写代码过程中大部分模式是单例的,有时不想过多的抽象,就会在函数内创建较多的静态变量。众所周知 静态变量与普通变量是不同的,要重点区别对待,所以如果能突出显示函数内的静态变量,则会有助于阅读代码。
这个需求前几年到现在,但是一直没有找到合适的方法,还期待微软能在某个更新补全这个功能,但是一直也没有。
就在今天, 借助AI的力量,成功的实现了改功能,虽然不能完全匹配到所有的语义场景,但是普通场景下完全可用。 原理就是额外给C/C++源文件增加一套自定义的装饰器,并把优先级高于微软的插件,则实现自定义着色的功能。


核心代码
import * as vscode from 'vscode';
// C 关键字(不包括类型修饰符)
const C_KEYWORDS = new Set([
'auto', 'break', 'case', 'continue', 'default', 'do',
'double', 'else', 'enum', 'extern', 'float', 'for', 'goto',
'if', 'inline', 'int', 'long', 'register', 'return',
'short', 'signed', 'sizeof', 'static', 'struct', 'switch',
'typedef', 'union', 'unsigned', 'void', 'while',
'_Bool', 'bool', 'char',
'int8_t', 'uint8_t', 'int16_t', 'uint16_t',
'int32_t', 'uint32_t', 'int64_t', 'uint64_t',
'size_t', 'ssize_t', 'ptrdiff_t', 'uintptr_t', 'intptr_t',
]);
// C 类型修饰符(static 后面可能出现,不是类型名本身)
const C_TYPE_QUALIFIERS = new Set(['const', 'volatile', 'restrict', '__attribute__', '__restrict', '__volatile']);
const STATIC_LEN = 'static'.length;
const STATIC_KEYWORD = 'static';
// 字符码检测(比正则快一个数量级)
function isIdentChar(ch: string): boolean {
const c = ch.charCodeAt(0);
return (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95;
}
function isIdentStart(ch: string): boolean {
const c = ch.charCodeAt(0);
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95;
}
function isWhitespace(ch: string): boolean {
const c = ch.charCodeAt(0);
return c === 32 || c === 9 || c === 10 || c === 13 || c === 11 || c === 12;
}
function extractToken(line: string, startCol: number): { token: string; start: number; end: number } | null {
let i = startCol;
while (i < line.length && isWhitespace(line[i])) { i++; }
if (i >= line.length || !isIdentStart(line[i])) { return null; }
const start = i;
while (i < line.length && isIdentChar(line[i])) { i++; }
return { token: line.substring(start, i), start, end: i };
}
function scanStaticLocalVars(document: vscode.TextDocument): { decls: vscode.Range[]; usages: vscode.Range[] } {
const decls: vscode.Range[] = [];
const usages: vscode.Range[] = [];
// 跟踪每个 static 变量的 {名称, 声明深度, 声明行},离开作用域时清理
const tracked: Array<{ name: string; depth: number; declLine: number }> = [];
let braceDepth = 0;
let inBlockComment = false;
for (let lineIdx = 0; lineIdx < document.lineCount; lineIdx++) {
const rawLine = document.lineAt(lineIdx).text;
if (inBlockComment) {
const endIdx = rawLine.indexOf('*/');
if (endIdx !== -1) { inBlockComment = false; }
else { continue; }
}
// 统计大括号
let lineOpen = 0, lineClose = 0;
let lineCommentStart = rawLine.length; // 行注释起点之后的列不可用
const slIdx = rawLine.indexOf('//');
if (slIdx !== -1) { lineCommentStart = slIdx; }
const mlIdx = rawLine.indexOf('/*');
let mlEnd = -1;
if (mlIdx !== -1) {
mlEnd = rawLine.indexOf('*/', mlIdx + 2);
if (mlEnd !== -1) { lineCommentStart = Math.min(lineCommentStart, mlIdx); }
else { lineCommentStart = Math.min(lineCommentStart, mlIdx); inBlockComment = true; }
}
for (let i = 0; i < lineCommentStart; i++) {
const ch = rawLine.charCodeAt(i);
if (ch === 123) { lineOpen++; } // {
else if (ch === 125) { lineClose++; } // }
}
const insideFunction = braceDepth >= 1;
// —— 扫描 static 声明 ——
if (insideFunction) {
let col = 0;
while (col < rawLine.length) {
while (col < rawLine.length && isWhitespace(rawLine[col])) { col++; }
if (col >= rawLine.length) { break; }
if (col + STATIC_LEN <= rawLine.length &&
rawLine.startsWith(STATIC_KEYWORD, col) &&
(col === 0 || !isIdentChar(rawLine[col - 1])) &&
(col + STATIC_LEN >= rawLine.length || !isIdentChar(rawLine[col + STATIC_LEN]))) {
let scanCol = col + STATIC_LEN;
// 快速检测是否是函数定义(在遇到 = 或 ; 之前先遇到 ()
let isFuncDef = false;
for (let p = scanCol; p < rawLine.length; p++) {
const pc = rawLine[p];
if (pc === '=' || pc === ';') { break; }
if (pc === '/') { break; } // 注释开始
if (pc === '(') { isFuncDef = true; break; }
}
if (isFuncDef) { col = col + STATIC_LEN; continue; }
let typeConsumed = false;
while (scanCol < rawLine.length) {
while (scanCol < rawLine.length && isWhitespace(rawLine[scanCol])) { scanCol++; }
if (scanCol >= rawLine.length) { break; }
const ch = rawLine[scanCol];
if (ch === '/' && scanCol + 1 < rawLine.length && rawLine[scanCol + 1] === '/') { break; }
if (ch === '=' || ch === ';') { break; }
if (ch === ',') { scanCol++; typeConsumed = true; continue; }
if (!isIdentStart(ch)) { scanCol++; continue; }
const tokenResult = extractToken(rawLine, scanCol);
if (!tokenResult) { break; }
scanCol = tokenResult.end;
if (C_KEYWORDS.has(tokenResult.token)) {
if (tokenResult.token === STATIC_KEYWORD) { break; }
typeConsumed = true;
continue;
}
// 类型修饰符 const/volatile/restrict 不是类型名,不消费
if (C_TYPE_QUALIFIERS.has(tokenResult.token)) {
continue;
}
if (!typeConsumed) { typeConsumed = true; continue; }
// 声明位置
decls.push(new vscode.Range(lineIdx, tokenResult.start, lineIdx, tokenResult.end));
// 记录变量用于后续使用搜索
tracked.push({ name: tokenResult.token, depth: braceDepth, declLine: lineIdx });
}
col = scanCol;
} else {
col++;
}
}
}
// —— 在函数体内搜索已跟踪变量的使用 ——
if (insideFunction && tracked.length > 0) {
let col = 0;
while (col < rawLine.length) {
while (col < rawLine.length && isWhitespace(rawLine[col])) { col++; }
if (col >= rawLine.length) { break; }
// 遇到注释停止
if (rawLine[col] === '/' && col + 1 < rawLine.length && rawLine[col + 1] === '/') { break; }
if (rawLine[col] === '/' && col + 1 < rawLine.length && rawLine[col + 1] === '*') { break; }
// 快速跳过非标识符起始字符(数字、符号等),避免无谓的变量名匹配
if (!isIdentStart(rawLine[col])) { col++; continue; }
// 尝试匹配每个跟踪的变量名
let matched = false;
for (const tv of tracked) {
if (lineIdx === tv.declLine) { continue; } // 跳过声明行本身
if (col + tv.name.length <= rawLine.length &&
rawLine.startsWith(tv.name, col) &&
(col === 0 || !isIdentChar(rawLine[col - 1])) &&
(col + tv.name.length >= rawLine.length || !isIdentChar(rawLine[col + tv.name.length]))) {
usages.push(new vscode.Range(lineIdx, col, lineIdx, col + tv.name.length));
col += tv.name.length;
matched = true;
break;
}
}
if (!matched) { col++; }
}
}
// —— 更新 braceDepth,清理离开作用域的变量 ——
braceDepth += lineOpen;
braceDepth -= lineClose;
if (braceDepth < 0) { braceDepth = 0; }
// 退出函数(braceDepth == 0)时清空所有跟踪变量
if (braceDepth === 0) {
tracked.length = 0;
} else if (tracked.length > 0) {
// 清理离开内层作用域的变量(filter 重建比逆序 splice 更快)
for (let i = 0; i < tracked.length;) {
if (tracked[i].depth > braceDepth) {
tracked[i] = tracked[tracked.length - 1];
tracked.pop();
} else {
i++;
}
}
}
}
return { decls, usages };
}
// —— Semantic Tokens Provider ——
// 为 static 局部变量提供 semantic token(类型: variable, 修饰符: static)
// 当用户在 editor.semanticTokenColorCustomizations 中配置 *.static 规则时,
// VS Code 会自动应用用户配置的颜色/样式。
class StaticVariableSemanticTokensProvider implements vscode.DocumentSemanticTokensProvider {
private _onDidChange = new vscode.EventEmitter<void>();
readonly onDidChangeSemanticTokens = this._onDidChange.event;
getLegend(): vscode.SemanticTokensLegend {
return new vscode.SemanticTokensLegend(['variable'], ['static']);
}
provideDocumentSemanticTokens(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.SemanticTokens {
// 扩展被禁用时返回空 token
const config = vscode.workspace.getConfiguration('staticLocalVar');
if (!config.get<boolean>('enabled', true)) {
return new vscode.SemanticTokens(new Uint32Array(0));
}
const { decls, usages } = scanStaticLocalVars(document);
const legend = this.getLegend();
const builder = new vscode.SemanticTokensBuilder(legend);
for (const range of decls) {
builder.push(range, 'variable', ['static']);
}
for (const range of usages) {
builder.push(range, 'variable', ['static']);
}
return builder.build();
}
fire(): void {
this._onDidChange.fire();
}
}
// 检测用户是否在 editor.semanticTokenColorCustomizations 中配置了 *.static 规则
function hasStaticSemanticRule(): boolean {
const customizations = vscode.workspace.getConfiguration('editor').get<any>('semanticTokenColorCustomizations');
if (!customizations) { return false; }
// 如果 semanticTokenColorCustomizations.enabled 为 false,规则不生效
if (customizations.enabled === false) { return false; }
const checkRules = (rules: any): boolean => {
if (!rules || typeof rules !== 'object') { return false; }
return '*.static' in rules || 'variable.static' in rules;
};
// 检查全局规则
if (checkRules(customizations.rules)) { return true; }
// 检查主题特定规则(如 "[Dark+ (default dark)]")
for (const key of Object.keys(customizations)) {
if (key.startsWith('[') && key.endsWith(']')) {
if (checkRules(customizations[key]?.rules)) { return true; }
}
}
return false;
}
// —— 激活扩展 ——
export function activate(context: vscode.ExtensionContext) {
console.log('[static-scope] ACTIVATED');
let decorationType: vscode.TextEditorDecorationType;
let timeout: NodeJS.Timeout | undefined;
let statusBarItem: vscode.StatusBarItem;
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
statusBarItem.command = 'cStaticScope.helloWorld';
context.subscriptions.push(statusBarItem);
// 初始化装饰器类型
// color 从 editor.semanticTokenColorCustomizations.rules["*.static"].foreground 映射;未配置则使用默认值 #FFD700
const semCustom = vscode.workspace.getConfiguration('editor').get<any>('semanticTokenColorCustomizations');
const staticRule = semCustom?.rules?.['*.static'] ?? semCustom?.rules?.['variable.static'];
const staticColor = (staticRule && typeof staticRule.foreground === 'string') ? staticRule.foreground : '#FFD700';
decorationType = vscode.window.createTextEditorDecorationType({
color: staticColor,
isWholeLine: false,
});
// 注册 Semantic Tokens Provider(为 C/C++ 文件提供 variable.static 语义令牌)
const semanticProvider = new StaticVariableSemanticTokensProvider();
const semanticLegend = semanticProvider.getLegend();
context.subscriptions.push(
vscode.languages.registerDocumentSemanticTokensProvider({ language: 'c' }, semanticProvider, semanticLegend),
vscode.languages.registerDocumentSemanticTokensProvider({ language: 'cpp' }, semanticProvider, semanticLegend),
);
context.subscriptions.push(
vscode.commands.registerCommand('cStaticScope.helloWorld', () => {
vscode.window.showInformationMessage('🎉 C Static Scope Highlighter is active!');
})
);
context.subscriptions.push(
vscode.commands.registerCommand('cStaticScope.scanNow', () => {
const ed = vscode.window.activeTextEditor;
if (ed) {
// 每次创建全新装饰器
const dt = vscode.window.createTextEditorDecorationType({
backgroundColor: 'red',
isWholeLine: false,
});
ed.setDecorations(dt, [new vscode.Range(0, 0, 0, 999)]);
console.log('[static-scope] MANUAL: fresh decoration applied');
vscode.window.showInformationMessage('Fresh red decoration applied to line 0');
}
})
);
function applyDecorations(editor: vscode.TextEditor) {
const doc = editor.document;
const lang = doc.languageId;
if (lang !== 'c' && lang !== 'cpp') {
statusBarItem.hide();
return;
}
const config = vscode.workspace.getConfiguration('staticLocalVar');
if (!config.get<boolean>('enabled', true)) {
statusBarItem.hide();
return;
}
const { decls, usages } = scanStaticLocalVars(doc);
const total = decls.length + usages.length;
if (hasStaticSemanticRule()) {
// 用户配置了 *.static 语义令牌规则,由 Semantic Tokens 处理着色,清除装饰避免覆盖
editor.setDecorations(decorationType, []);
} else {
editor.setDecorations(decorationType, [...decls, ...usages]);
}
if (total > 0) {
statusBarItem.text = `$(symbol-variable) Static Locals: ${total}`;
statusBarItem.show();
} else {
statusBarItem.hide();
}
}
function triggerUpdate(editor: vscode.TextEditor | undefined) {
if (!editor) { statusBarItem.hide(); return; }
if (timeout) { clearTimeout(timeout); }
timeout = setTimeout(() => applyDecorations(editor), 150);
}
const activeEditor = vscode.window.activeTextEditor;
if (activeEditor) { triggerUpdate(activeEditor); }
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(e => triggerUpdate(e)));
let semanticTimeout: NodeJS.Timeout | undefined;
context.subscriptions.push(vscode.workspace.onDidChangeTextDocument(e => {
const ed = vscode.window.activeTextEditor;
if (ed && e.document === ed.document) { triggerUpdate(ed); }
// 触发 semantic tokens 刷新(防抖)
if (e.document.languageId === 'c' || e.document.languageId === 'cpp') {
if (semanticTimeout) { clearTimeout(semanticTimeout); }
semanticTimeout = setTimeout(() => semanticProvider.fire(), 150);
}
}));
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('staticLocalVar') ||
e.affectsConfiguration('editor.semanticTokenColorCustomizations') ||
e.affectsConfiguration('editor.semanticHighlighting.enabled')) {
triggerUpdate(vscode.window.activeTextEditor);
semanticProvider.fire();
}
}));
}
export function deactivate() {}
已经编译过的插件下载地址 https://download.csdn.net/download/yushikong/93048714?spm=1001.2014.3001.5503
更多推荐
所有评论(0)