如何使用 TextEditor.setDecorations() 动态更改 VSCode 中的装订线图标

How to dynamically change gutter icons in VSCode with TextEditor.setDecorations()

使用 TextEditor.setDecorations() https://code.visualstudio.com/api/references/vscode-api#TextEditor.

更新 VSCode 中的装订线图标时出现一些奇怪的行为

激活我的 VSCode 扩展后,下面的 class 被实例化,它的构造函数被称为“一次性”创建 3 个 TextEditorDecorationType 的 https://code.visualstudio.com/api/references/vscode-api#TextEditorDecorationType,每个状态对应一个测试结果可以用 NONE、PASS、FAIL 图标表示,最后调用 triggerUpdateDecorations() 以在 3 个数组中收集当前 globalResults 并使用 vscode.window.activeTextEditor.setDecorations()[ 设置装订线图标=20=]

目前一切正常。没有测试 运行 并且编辑器中显示的每个测试都更新为 NONE 装订线图标。

现在每个测试都在编辑器中 运行,完成后,将再次调用 triggerUpdateDecorations() 来收集结果并更新装订线图标。

如果编辑器中有例如 10 个测试,每个测试都有一个 NONE 装订线图标,如果我 运行 测试正确的单个测试更新为通过或失败装订线图标。此行为会在所有后续测试 运行 中重复出现,但最后一个除外。最后一个测试 运行 仍然设置为 NONE 装订线图标。它与特定测试无关,因为我可以跳来跳去,并且行为遵循上次测试 运行.

我尝试将虚拟 NONE 图标添加到装订线中未与测试绑定的随机位置,这允许所有与测试绑定的装订线图标更新为通过或失败装订线图标。

我一直在尝试解决这个问题,但似乎找不到根本原因。非常感谢有关如何解决此问题的任何见解。

请注意,部分代码源自此处显示的 VSCode 示例 https://github.com/microsoft/vscode-extension-samples/blob/main/decorator-sample/src/extension.ts#L58

import * as path from 'path';
import * as vscode from 'vscode';

class ProviderDecorations
{
    private timeout: NodeJS.Timeout;
    private context: vscode.ExtensionContext;
    private activeEditor: vscode.TextEditor;
    private readonly decorationNone: vscode.TextEditorDecorationType;
    private readonly decorationPass: vscode.TextEditorDecorationType;
    private readonly decorationFail: vscode.TextEditorDecorationType;

    constructor(context: vscode.ExtensionContext)
    {
        this.context = context;
        this.activeEditor = vscode.window.activeTextEditor;

        this.decorationNone = this.getDecorationType(ENTRY_STATE.NONE);
        this.decorationPass = this.getDecorationType(ENTRY_STATE.PASS);
        this.decorationFail = this.getDecorationType(ENTRY_STATE.FAIL);

        vscode.window.onDidChangeActiveTextEditor(editor =>
        {
            this.activeEditor = editor;
            if (editor)
            {
                this.triggerUpdateDecorations();
            }
        }, null, this.context.subscriptions);
      
        vscode.workspace.onDidChangeTextDocument(event =>
        {
            if (this.activeEditor && event.document === this.activeEditor.document)
            {
                this.triggerUpdateDecorations();
            }
        }, null, this.context.subscriptions);

        this.triggerUpdateDecorations();
    }

    public updateDecorations()
    {
        if (!this.activeEditor)
        {
            return;
        }

        let rangeNone = [];
        let rangePass = [];
        let rangeFail = [];

        globalResults.forEach((result) =>
        {
            let range = new vscode.Range(result.line, 0, result.line, 0);

            switch (result.state)
            {
                case ENTRY_STATE.NONE:
                    rangeNone.push({ range });
                    break;
                
                case ENTRY_STATE.PASS:
                    rangePass.push({ range });
                    break;

                case ENTRY_STATE.FAIL:
                    rangeFail.push({ range });
                    break;
            }
        });

        if (rangePass.length > 0)
        {
            this.activeEditor.setDecorations(this.decorationPass, rangePass);
        }

        if (rangeFail.length > 0)
        {
            this.activeEditor.setDecorations(this.decorationFail, rangeFail);
        }

        if (rangeNone.length > 0)
        {
            this.activeEditor.setDecorations(this.decorationNone, rangeNone);
        }
    }

    private getDecorationType(state: ENTRY_STATE): vscode.TextEditorDecorationType
    {
        let icon = 'none.svg';

        if (state === ENTRY_STATE.PASS)
        {
            icon = 'pass.svg';
        }
        else if (state === ENTRY_STATE.FAIL)
        {
            icon = 'fail.svg';
        }

        const decorationType = vscode.window.createTextEditorDecorationType(
        {
            light:
            {
                gutterIconPath: path.join(__dirname, '..', 'resources', 'light', icon),
                gutterIconSize: '85%',
            },
            dark:
            {
                gutterIconPath: path.join(__dirname, '..', 'resources', 'dark', icon),
                gutterIconSize: '85%'
            }
        });

        return decorationType;
    }

    public triggerUpdateDecorations()
    {
        if (this.timeout)
        {
            clearTimeout(this.timeout);
            this.timeout = undefined;
        }

        this.timeout = setTimeout(() =>
        {
            this.updateDecorations();
        }, 250);
    }
}

export default ProviderDecorations;

您永远不会清除 Decorator 类型,删除 if (rangePass.length > 0) 部分

            this.activeEditor.setDecorations(this.decorationPass, rangePass);
            this.activeEditor.setDecorations(this.decorationFail, rangeFail);
            this.activeEditor.setDecorations(this.decorationNone, rangeNone);