VSCode: Create a document in memory with URI for automated testing?

Viewed 317

Background

I created an extension that interacts with documents. In order to test the extension I need to create documents, that the extension can work with. The extension has to access the document via uri.

Currently I'm using vscode.workspace.openTextDocument({content: _content, language: _language}); for document creation. The problem is, it does not have a valid URI.

Question

How can I create a virtual document in memory, that has a valid URI?

2 Answers

As there was no native solution to this, I created my and I'd like to share it here:

A TextDocumentContentProvider for files in memory. Example usage shown below

memoryfile.ts

import * as vscode from 'vscode';

const _SCHEME = "inmemoryfile";

/**
 *  Registration function for In-Memory files.
 *  You need to call this once, if you want to make use of
 *  `MemoryFile`s.
 **/
export function register_memoryFileProvider ({ subscriptions }: vscode.ExtensionContext)
{
  const myProvider = new (class implements vscode.TextDocumentContentProvider
        {
            provideTextDocumentContent(uri: vscode.Uri): string
            {
                let memDoc = MemoryFile.getDocument (uri);
                if (memDoc == null)
                    return "";
                return memDoc.read ();
            }
  })();
  subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(
        _SCHEME, myProvider));
}




/**
 *  Management class for in-memory files.
 **/
class MemoryFileManagement
{
    private static _documents: {[key: string]: MemoryFile} = {};
    private static _lastDocId: number = 0;



    public static getDocument(uri: vscode.Uri) : MemoryFile | null
    {
        return MemoryFileManagement._documents[uri.path];
    }



    private static _getNextDocId(): string{
        MemoryFileManagement._lastDocId++;
        return "_" + MemoryFileManagement._lastDocId + "_";
    }



    public static createDocument(extension = "")
    {
      let path = MemoryFileManagement._getNextDocId ();

        if (extension != "")
            path += "." + extension;

        let self = new MemoryFile(path);

        MemoryFileManagement._documents[path] = self;

        return self;
    }
}



/**
 * A file in memory
 **/
export class MemoryFile
{
    /******************
     ** Static Area  **
     ******************/

    public static getDocument(uri: vscode.Uri) : MemoryFile | null {
        return MemoryFileManagement.getDocument (uri);
    }

    public static createDocument(extension = "") {
        return MemoryFileManagement.createDocument (extension);
    }



    /******************
     ** Object Area  **
     ******************/

    public content: string = "";
    public uri: vscode.Uri;

    constructor (path: string)
    {
        this.uri = vscode.Uri.from ({scheme: _SCHEME, path: path})
    }


    public write(strContent: string){
        this.content += strContent;
    }


    public read(): string {
        return this.content;
    }


    public getUri(): vscode.Uri {
        return this.uri;
    }
}

Example usage

Register the provider

You need to register the provider somewhere in the beginning of your test code (I do it in index.ts before Mocha is instantiated):

register_memoryFileProvider (extensionContext);

(How do I get the extension context?)

Create a document

Creating and using a file works as follows:

// create the in-memory document
let memfile = MemoryFile.createDocument ("ts");
memfile.write ("my content");

// create a vscode.TextDocument from the in-memory document.
let doc = await vscode.workspace.openTextDocument (memfile.getUri ());

Notes

  • Be aware, that LSP commands might not work with with approach, because they might be registered to a certain specific schema.

As rioV8 said, you can also use an existing document and change its content. Here the code:

export class TmpFile
{
  private static _lastDocId: number = 0;
    private static _getNextDocId(): string{
        this._lastDocId++;
        return "tmpfile_" + this._lastDocId;
    }

    public static async createDocument(strContent: string, extension:string = "")
        : Promise<vscode.TextDocument | null>
    {
        let folder = "/tmp"
        let filename = this._getNextDocId ();
        let ext = (extension != "" ? "." + extension : "");


        const newFile = vscode.Uri.parse('untitled:' + path.join(folder, filename + ext));

        {
            const edit = new vscode.WorkspaceEdit();
            edit.insert(newFile, new vscode.Position(0, 0), strContent);
            
            let success = await vscode.workspace.applyEdit(edit);           
            
            if (!success)
                return null;
        }

        let document = await vscode.workspace.openTextDocument(newFile);
        return document;
    }
}

Pro's

  • It's a file (schema), so all LSP commands will work
  • The path (used above) does not even need to exist.

Con's

  • The file is really opened in the editor. You need to close it later
  • The file is a changed file in the editor, so it will ask you to save the changes upon closing.
  • Files cannot be closed in vscode. You can only run:
vscode.window.showTextDocument(doc.uri, {preview: true, preserveFocus: false})
    .then(() => {        
        return vscode.commands.executeCommand('workbench.action.closeActiveEditor');
    });
```<br>
which is a rather nasty workaround.
Related