blob: dd27ab6c7b9e939dcc7c293eee803bef09e5fd42 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as vscode from 'vscode';
import { Ffx, FuchsiaDevice } from '../ffx';
import { DebugTarget } from '../zxdb/zxdb';
class Moniker {
constructor(readonly value: string) { }
get basename(): string {
const index = this.value.lastIndexOf('/');
if (index === -1) {
return this.value;
} else {
return this.value.slice(index + 1);
}
}
get parent(): string | null {
const index = this.value.lastIndexOf('/');
if (index === -1) {
return null;
} else {
return this.value.slice(0, index);
}
}
}
class ComponentInfo implements DebugTarget {
parent: ComponentInfo | null = null;
children: Array<ComponentInfo> = [];
constructor(
readonly moniker: Moniker,
readonly url: string,
) { }
setParent(info: ComponentInfo) {
if (this.parent) {
let index = this.parent.children.indexOf(this);
if (index !== -1) {
this.parent.children.splice(index, 1);
}
}
this.parent = info;
info.children.push(this);
}
get attachDescriptor() { return this.moniker.value; }
static fromJSON(object: any): ComponentInfo {
let moniker = new Moniker(object['moniker'] as string);
let url = object['url'] as string;
return new ComponentInfo(moniker, url);
}
}
class ComponentTree {
readonly map: Map<string, ComponentInfo> = new Map();
readonly roots: Array<ComponentInfo> = [];
constructor(object: any) {
for (const entry of object) {
let info = ComponentInfo.fromJSON(entry);
this.map.set(info.moniker.value, info);
}
for (const info of this.map.values()) {
let parentMoniker = info.moniker.parent;
if (parentMoniker) {
let parent = this.map.get(parentMoniker);
if (parent) {
info.setParent(parent);
} else {
this.roots.push(info);
}
} else {
this.roots.push(info);
}
}
}
}
export class ComponentExplorerDataProvider implements vscode.TreeDataProvider<ComponentInfo> {
private currentDevice: FuchsiaDevice | null = null;
private tree: ComponentTree | null = null;
constructor(
private readonly ffx: Ffx
) {
this.ffx.onSetTarget((device: FuchsiaDevice | null) => {
if (this.currentDevice?.nodeName !== device?.nodeName) {
this.currentDevice = device;
this.refresh();
}
});
}
private _onDidChangeTreeData: vscode.EventEmitter<ComponentInfo | undefined | null | void> =
new vscode.EventEmitter<ComponentInfo | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<ComponentInfo | undefined | null | void> =
this._onDidChangeTreeData.event;
refresh(): void {
this.tree = null;
this._onDidChangeTreeData.fire();
}
getTreeItem(element: ComponentInfo): vscode.TreeItem {
let collapsibleState = element.children.length > 0 ?
vscode.TreeItemCollapsibleState.Expanded :
vscode.TreeItemCollapsibleState.None;
return new ComponentTreeItem(element, collapsibleState);
}
async getChildren(element?: ComponentInfo | undefined): Promise<ComponentInfo[]> {
if (element) {
return element.children;
} else if (this.currentDevice) {
if (!this.tree) {
this.tree = new ComponentTree(await this.ffx.listComponents(this.currentDevice));
}
return this.tree.roots;
} else {
return [];
}
}
getParent?(element: ComponentInfo): vscode.ProviderResult<ComponentInfo> {
return element.parent;
}
}
class ComponentTreeItem extends vscode.TreeItem {
static readonly icon = new vscode.ThemeIcon('symbol-interface');
constructor(
public readonly info: ComponentInfo,
public readonly collapsibleState: vscode.TreeItemCollapsibleState
) {
super(info.moniker.basename, collapsibleState);
this.description = info.url;
this.tooltip = `Moniker: ${info.moniker.value}\nURL: ${info.url}`;
this.iconPath = ComponentTreeItem.icon;
}
}