Search code examples
visual-studio-codevscode-extensions

setDecorations vscode api is not applying decorations on the correct range of text


I'm trying to apply a decoration on words in vscode text that matches some specific words.

      const document = activeEditor.document;

      const decoration = window.createTextEditorDecorationType({
         textDecoration: 'underline wavy #0000ff'
      });

      let ranges = [];

      for (let word of words) {
         let matches = [...document.getText().matchAll(new RegExp(word, 'g'))];

         if (matches.length === 0) continue;

         for (let match of matches) {
            let startPos = document.positionAt(match.index as number);
            let endPos = document.positionAt(match.index as number + match[0].length);
      
            ranges.push({
               range: new Range(startPos, endPos),
            });
         }
      }

      activeEditor.setDecorations(decoration, ranges);

/* listeners when the text changes retriggers the code etc*/

Regex is correct and also the range of the curr words pos.

When I apply the styles vscode makes a mess, actually it does apply them on matched word but also on all the following ones. If I delete the words the decoration persist (IDK why) in the point where the word were (like if I write anything else in the point the new text gets the previous decoration).

Following vscode docs the decorations should update automatically when the ranges change btw is not.


Solution

  • There is an issue in the code, related on how you create the decoration. You must create only once, no matter how many times you use it. So, you could create it right after the extension initialization and use it latter.

    Also, the phrase "Following vscode docs the decorations should update automatically when the ranges change btw is not.", is not quite accurate. Some updates are indeed automatic, but most of them, no. The safest approach is you take care of text changes and fire the decoration again.

    An updated, runnable code, is bellow.

    export function activate(context: vscode.ExtensionContext) {
    
        // declare a timer to delay the decoration update
        let timeout: NodeJS.Timer;
    
        // creates the decoration only once
        const decoration = vscode.window.createTextEditorDecorationType({
            textDecoration: 'underline wavy #0000ff'
        });
    
        triggerUpdateDecorations();
    
    
        // whenever the text changes, update the decorators
        vscode.workspace.onDidChangeTextDocument(event => {
            if (vscode.window.activeTextEditor && event.document === vscode.window.activeTextEditor.document) {
                updateDecorations();
            }
        }, null, context.subscriptions);
    
        // basically your original source, without the createDecoration piece
        function updateDecorations() {
            if (!vscode.window.activeTextEditor) return;
    
            const document = vscode.window.activeTextEditor.document;        
    
            const ranges: vscode.Range[] =  [];
            // let words = ["bola", "coisa"];
    
            for (let word of words) {
                let matches = [...document.getText().matchAll(new RegExp(word, 'g'))];
    
                if (matches.length === 0) continue;
    
                for (let match of matches) {
                    let startPos = document.positionAt(match.index as number);
                    let endPos = document.positionAt(match.index as number + match[0].length);
            
                    ranges.push(
                    new vscode.Range(startPos, endPos),
                    );
                }
            }
    
            vscode.window.activeTextEditor.setDecorations(decoration, ranges);
        }
    
        // fire decoration update using a small delay
        function triggerUpdateDecorations() {
            if (timeout) {
                clearTimeout(timeout);
            }
            timeout = setTimeout(updateDecorations, 100);
        }
    }
    

    Hope this helps