Tree Data Providers enable the creation of hierarchical data structures in VSCode that are displayed as tree-like views in the Explorer sidebar or custom panels. These components conceptually correspond to TreeViewer implementations in Eclipse and form the foundation for structured data representation in extensions.
The Tree Data Provider is based on an event-driven model where VSCode
requests data from the provider as needed. The architecture follows the
TreeDataProvider<T> interface, where T
represents the type of tree elements. These elements must implement the
TreeItem interface or be adapted accordingly by VSCode.
The data structure is lazy loaded, meaning VSCode only retrieves data currently needed for display. Expanded nodes are only loaded when required, which is performance-critical for large datasets.
export interface TreeDataProvider<T> {
// Main method: Returns child elements for a given node
getChildren(element?: T): ProviderResult<T[]>;
// Converts data elements into displayable TreeItems
getTreeItem(element: T): TreeItem | Thenable<TreeItem>;
// Optional: Event for data changes
onDidChangeTreeData?: Event<T | T[] | null | undefined>;
}The following example shows a simple implementation of a Tree Data Provider for a file system-like structure:
import * as vscode from 'vscode';
import * as path from 'path';
class FileSystemNode {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly children?: FileSystemNode[]
) {}
}
export class FileSystemProvider implements vscode.TreeDataProvider<FileSystemNode> {
private _onDidChangeTreeData: vscode.EventEmitter<FileSystemNode | undefined | null | void> = new vscode.EventEmitter<FileSystemNode | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<FileSystemNode | undefined | null | void> = this._onDidChangeTreeData.event;
// Main data access: Determines the children of a node
getChildren(element?: FileSystemNode): Thenable<FileSystemNode[]> {
if (!element) {
// Root elements: Called when the tree is initially loaded
return Promise.resolve([
new FileSystemNode('Documents', vscode.TreeItemCollapsibleState.Expanded, [
new FileSystemNode('file1.txt', vscode.TreeItemCollapsibleState.None),
new FileSystemNode('file2.txt', vscode.TreeItemCollapsibleState.None)
]),
new FileSystemNode('Downloads', vscode.TreeItemCollapsibleState.Collapsed, [
new FileSystemNode('image.png', vscode.TreeItemCollapsibleState.None)
])
]);
}
// Child elements: Called when a node is expanded
return Promise.resolve(element.children || []);
}
// Conversion: Transforms data model into displayable TreeItems
getTreeItem(element: FileSystemNode): vscode.TreeItem {
const item = new vscode.TreeItem(element.label, element.collapsibleState);
// Additional properties for display
item.tooltip = `${element.label} - Custom tooltip`;
item.description = element.children ? `${element.children.length} items` : 'File';
return item;
}
// Method for updating data
refresh(): void {
this._onDidChangeTreeData.fire();
}
}This implementation shows the three essential components: the data
model (FileSystemNode), the provider logic
(getChildren, getTreeItem), and the event
system for updates.
The Tree Data Provider is registered via the VSCode API and linked to
a view definition in package.json:
// In the extension's activate function
export function activate(context: vscode.ExtensionContext) {
const provider = new FileSystemProvider();
// Register the provider with a view ID
vscode.window.createTreeView('fileSystemView', {
treeDataProvider: provider,
showCollapseAll: true
});
// Command for manual refresh
vscode.commands.registerCommand('fileSystemView.refresh', () => {
provider.refresh();
});
}The corresponding view definition in package.json
defines where the tree appears in the UI:
{
"contributes": {
"views": {
"explorer": [
{
"id": "fileSystemView",
"name": "File System",
"when": "workbenchState != empty"
}
]
},
"commands": [
{
"command": "fileSystemView.refresh",
"title": "Refresh",
"icon": "$(refresh)"
}
],
"menus": {
"view/title": [
{
"command": "fileSystemView.refresh",
"when": "view == fileSystemView",
"group": "navigation"
}
]
}
}
}Tree Data Providers support various interaction patterns that go beyond pure data display:
export class InteractiveFileSystemProvider implements vscode.TreeDataProvider<FileSystemNode> {
private _onDidChangeTreeData: vscode.EventEmitter<FileSystemNode | undefined | null | void> = new vscode.EventEmitter<FileSystemNode | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<FileSystemNode | undefined | null | void> = this._onDidChangeTreeData.event;
private data: FileSystemNode[] = [];
getChildren(element?: FileSystemNode): Thenable<FileSystemNode[]> {
if (!element) {
return Promise.resolve(this.data);
}
return Promise.resolve(element.children || []);
}
getTreeItem(element: FileSystemNode): vscode.TreeItem {
const item = new vscode.TreeItem(element.label, element.collapsibleState);
// Command linking for click actions
if (element.collapsibleState === vscode.TreeItemCollapsibleState.None) {
item.command = {
command: 'fileSystemView.openFile',
title: 'Open File',
arguments: [element]
};
}
// Context menu support
item.contextValue = element.children ? 'folder' : 'file';
// Icons and theming
item.iconPath = new vscode.ThemeIcon(
element.children ? 'folder' : 'file'
);
return item;
}
// Programmatic data manipulation
addNode(parent: FileSystemNode | undefined, newNode: FileSystemNode): void {
if (!parent) {
this.data.push(newNode);
} else {
if (!parent.children) {
parent.children = [];
}
parent.children.push(newNode);
}
// Specific update of the affected node
this._onDidChangeTreeData.fire(parent);
}
removeNode(node: FileSystemNode): void {
// Implementation for removing nodes
// Then fire event
this._onDidChangeTreeData.fire();
}
}Context menu integration is done through package.json
configuration:
{
"contributes": {
"menus": {
"view/item/context": [
{
"command": "fileSystemView.deleteFile",
"when": "view == fileSystemView && viewItem == file",
"group": "inline"
},
{
"command": "fileSystemView.addFile",
"when": "view == fileSystemView && viewItem == folder",
"group": "1_modification"
}
]
}
}
}When implementing Tree Data Providers, performance aspects are crucial, especially with large datasets or slow data sources:
export class OptimizedTreeProvider implements vscode.TreeDataProvider<DataNode> {
private cache = new Map<string, DataNode[]>();
private loadingNodes = new Set<string>();
async getChildren(element?: DataNode): Promise<DataNode[]> {
const key = element?.id || 'root';
// Cache lookup
if (this.cache.has(key)) {
return this.cache.get(key)!;
}
// Avoid parallel requests
if (this.loadingNodes.has(key)) {
return [];
}
this.loadingNodes.add(key);
try {
// Asynchronous data source (e.g., API call)
const data = await this.fetchDataFromSource(element);
// Update cache
this.cache.set(key, data);
return data;
} finally {
this.loadingNodes.delete(key);
}
}
private async fetchDataFromSource(element?: DataNode): Promise<DataNode[]> {
// Simulation of an asynchronous data source
return new Promise((resolve) => {
setTimeout(() => {
resolve([
// Simulated data
]);
}, 200);
});
}
// Cache invalidation on data changes
invalidateCache(nodeId?: string): void {
if (nodeId) {
this.cache.delete(nodeId);
} else {
this.cache.clear();
}
this._onDidChangeTreeData.fire();
}
}Tree Data Providers are particularly suitable for extensions that need to organize and make structured data navigable. Typical application scenarios include project structures, database connections, API endpoints, or configurable workflows. The hierarchical display enables intuitive navigation through complex data structures while providing flexibility for context-specific actions.
The comparison to Eclipse TreeViewers shows conceptual similarities in the lazy-loading architecture and the separation of data model and presentation. However, VSCode Tree Data Providers offer stronger integration into the command system and extended theming capabilities.
Implementation should always consider the asynchronous nature of VSCode and employ performance optimizations such as caching and selective updates to ensure a responsive user experience.