vscode extension for auto complete inside html script tag

I’m creating a vscode extension that adds some custom autofill capabilities to the script tags inside an html file. It’s based on this example from vscodes extension tutorial page.

I took that example and changed it to look at script tags. So overall the flow of the extension is something like:

  1. A user goes to an .html file, the extension starts
  2. When the user starts typing and vscode generates autocomplete items, it calls provideCompletionItem from my extension
  3. My extension checks if the document is currently located inside a script tag before continuing
  4. Create a virtual document which replaces everything in the file with spaces except for script tag content
  5. Append ;class ExampleClass{method1(){}}; to the end of the virtual document string
  6. Run the default behavior for getting the autocompletion list in the virtual document

The current result of this is when you go to a script tag and start typing, ExampleClass will show up like I want. However when you type ExampleClass.| (‘|’ represents where the cursor is) it only displays the default completion elements. It also produces an error:

[Extension Host] Unexpected resource embedded-content://js/path/to/file.html.js

Before this error pops up it isn’t running the registerTextDocumentContentProvider which returns the virtual content when given a virtual URI. Presumably this means I’m missing some special case for when some path saved in the CompletionItem is accessed but I can’t figure out exactly what that is.

Here is some of the relevant code:

the function that creates virtual file content:

    // the above code is unchanged from the vscode example

    let content = documentText
        .split('n')
        .map(line => {
            return ' '.repeat(line.length);
        }).join('n');

    regions.forEach(r => {
        if (r.languageId === languageId) { // languageId == javascript
            content = content.slice(0, r.start) + documentText.slice(r.start, r.end) + content.slice(r.end);
        }
    });

    let inject = ';class ExampleClass{method1(){}};';

    return content + inject; // does not run on error
}

serves up the virtual documents:

const virtualDocumentContents = new Map<string, string>();

workspace.registerTextDocumentContentProvider('embedded-content', {
    provideTextDocumentContent: uri => {
        console.log('get virt file'); // in the case of the error this never logs
        // Remove leading `/` and ending `.js` to get original URI
        const originalUri = uri.path.slice(1).slice(0, -3);
        const decodedUri = decodeURIComponent(originalUri);
        if (!virtualDocumentContents.has(decodedUri)) console.warn(`no such virtual file ${decodedUri}`); // this warning never goes off
        return virtualDocumentContents.get(decodedUri);
    }
});

Provide CompletionItems

const clientOptions: LanguageClientOptions = {
    documentSelector: [{ scheme: 'file', language: 'html' }],
    middleware: {
        provideCompletionItem: async (document, position, context, token, next) => {
            console.log('auto completion...'); // does not run on error

            if (!isInsideScriptRegion(htmlLanguageService, document.getText(), document.offsetAt(position))) {
                return await next(document, position, context, token);
            }

            const originalUri = document.uri.toString(true);
            
            const virtContent = getVirtualContent(htmlLanguageService, document.getText(), 'javascript');
            virtualDocumentContents.set(originalUri, virtContent);

            const vdocUriString = `embedded-content://js/${encodeURIComponent(
                originalUri
            )}.js`;
            const vdocUri = Uri.parse(vdocUriString);

            const compArr = await commands.executeCommand<CompletionList>(
                'vscode.executeCompletionItemProvider',
                vdocUri,
                position,
                context.triggerCharacter
            );

            env.clipboard.writeText(virtContent); // for debugging, this of course also does not run when the error occurs
            console.log('write board');

            return compArr;
        }
    }
}