一直以来用的是微软的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

更多推荐