[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));
}
);
});
}
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
asview/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"
}
]
...
}
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:
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