[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
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"
}
]
...
}
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