[TypeScript] VS Code API: Let’s Create A Tree-View (Part 2)

You can find the project here on GitHub, I added a tag part-2 for this article.

In my last article we started to create a tree view in VS Code. I did a minor refactoring in the tree_item class where I now created a line class to store all line information:

class line {
    readonly text : string;
    readonly row : number;
    readonly length : number;

    constructor (text : string, row : number) {
        this.text = text;
        this.length = text.length;
        this.row = row;
    }
}
class tree_item extends vscode.TreeItem {
    readonly file: string;
    readonly line: line;
    readonly children: tree_item[] = [];

    constructor(label: string, file: string, line: line) {
        super(label, vscode.TreeItemCollapsibleState.None);
        this.file = file;
        this.line = line;
        this.collapsibleState = vscode.TreeItemCollapsibleState.None;
    }
}

And this brings (for me) one task to solve in programming: naming.

I don't like to name variables as their type: readonly line: line where I didn't come up with something better yet. However, it feels right to use a tree_item like: item.line.row or item.line.text. If we'd need line more often than just in tree_item I'd consider renaming. So for now we leave it as it is.

 

Let's Implement on_item_clicked()

When we click on an item, we want to call on_item_clicked(..) and we want to get the item on which we have clicked. In the last article we already provided this function in package.json and registered it in the tree_view constructor.

In the getTreeItem(..) we return the selected item. In vscode.TreeItem we have a member command, which we need to provide to our returned item. And since we already declared and registered cwt_cucumber.on_item_clicked, it is linked to our memberfunction on_item_clicked(..).

export class tree_view implements vscode.TreeDataProvider<tree_item>
{
    // ... 
    public constructor()  {
        // let's register the methods we want
        vscode.commands.registerCommand('cwt_cucumber.on_item_clicked', item => this.on_item_clicked(item));
        vscode.commands.registerCommand('cwt_cucumber.refresh', () => this.refresh());
    }

    public getTreeItem(item: tree_item): vscode.TreeItem|Thenable<vscode.TreeItem> {
        let title = item.label ? item.label.toString() : "";
        let result = new vscode.TreeItem(title, item.collapsibleState);
        // here we add our command which executes our memberfunction
        result.command = { command: 'cwt_cucumber.on_item_clicked', title : title, arguments: [item] };
        return result;
    }
    // ... 
}

Now we can continue to implement on_item_clicked(..). Everytime we click on a item, we want to open the file and set the cursor to the location of the Feature/Scenario:

public on_item_clicked(item: tree_item) {
    if (item.file === undefined) return;
    // first we open the document
    vscode.workspace.openTextDocument(item.file).then( document => {
        // after opening the document, we set the cursor 
        // and here we make use of the line property which makes imo the code easier to read
        vscode.window.showTextDocument(document).then( editor => {
                let pos = new vscode.Position(item.line.row, item.line.length);
                // here we set the cursor
                editor.selection = new vscode.Selection(pos, pos);
                // here we set the focus of the opened editor
                editor.revealRange(new vscode.Range(pos, pos));
            }
        );
    });
}

And that is it. After clicking on an item we open the appropriate file and set the cursor at the end of the line.

 

Let's Create A Context Menu

Now we'll add a custom context menu. This opens, when we right click on an item. Let's open package.json and add:

  • New commands in commands
  • A context menue in menus as view/item/context and link the created commmands

And that's all to pop up a context menu with a right click on an item.

...
"commands": [
... 
	{
		"command": "cwt_cucumber.context_menu_command_0",
		"title": "context menu method 0"
	},
	{
		"command": "cwt_cucumber.context_menu_command_1",
		"title": "context menu method 1"
	}			
],
"menus": {
...
	"view/item/context": [
		{
			"command": "cwt_cucumber.context_menu_command_0",
			"when": "view == cwt_cucumber",
			"group": "cwt_cucumber@0"
		},
		{
			"command": "cwt_cucumber.context_menu_command_1",
			"when": "view == cwt_cucumber",
			"group": "cwt_cucumber@1"
		}
	]
...
}

And this is our created context menu

Let us add functions, which are execured when we select an entrie on our context menu. We'll use the vscode.commands.registerCommand(..) in the tree_view constructor like we did with on_item_clicked and refresh function:

export class tree_view implements vscode.TreeDataProvider<tree_item>
{
// ...
    public constructor()  {
        vscode.commands.registerCommand('cwt_cucumber.on_item_clicked', item => this.on_item_clicked(item));
        vscode.commands.registerCommand('cwt_cucumber.refresh', () => this.refresh());
        // first link our memberfunctions to the vscode command
        vscode.commands.registerCommand('cwt_cucumber.context_menu_command_0', item => this.command_0(item));
        vscode.commands.registerCommand('cwt_cucumber.context_menu_command_1', item => this.command_1(item));
    }
    
    // and second implement them
    public command_0(item: tree_item) {
        console.log("context menu command 0 clickd with: ", item.label);
    }
    public command_1(item: tree_item) {
        console.log("context menu command 1 clickd with: ", item.label);
    }
//...
}

And for this demonstration I'll just add logs, that we can see the method is called and the right item is passed to our function:

After clicking some items, we can see command0 or command_1 is called with the appropriate item.

 

Conclusion

So that's it for now and there is not much code needed to add this functionality to our extension.

You can find the project here on GitHub, I added a tag part-2 for this article.

In the future we'll add a button to run our tests and use the here created context menue to run a single Scenario or Feature.

I hope this helped and see you next time.

Best Thomas

Previous
Previous

[TypeScript] VS Code API: Let’s Create A Tree-View (Part 3)

Next
Next

[TypeScript] VS-Code API: Let’s Create A Tree-View (Part 1)