blob: 6389c35e6b0a5156a19830162de856ab79d4f878 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import * as vscode from 'vscode';
import { ThemeIcon } from 'vscode';
import { LanguageClient } from 'vscode-languageclient/node';
import { NbLanguageClient } from './extension';
import { NodeChangedParams, NodeInfoNotification, NodeInfoRequest, GetResourceParams, NodeChangeType, NodeChangesParams } from './protocol';
const doLog : boolean = false;
const EmptyIcon = "EMPTY_ICON";
/**
* Listener that can watch for node structure or property changes.
*/
export interface TreeNodeListener {
/**
* Node has been destroyed. No more events will be delivered.
* @param n the node
*/
nodeDestroyed?(n : Visualizer) : void;
/**
* Node itself (description, icon, ...) has been changed.
* @param n the node
*/
nodeChanged?(n : Visualizer) : void;
/**
* Node's children changed.
* @param n the node
*/
nodeChildrenChanged?(n : Visualizer) : void;
/**
* Informs that some properties of the node changed. If list of properties is undefined, then
* not only an unspecified property could change, but also the set of properties could be changed as well.
* @param n the node
* @param properties list of changed properties or undefined.
*/
nodePropertiesChanged?(n : Visualizer, properties?: String[]) : void;
}
/**
* Cached image information.
*/
class CachedImage {
constructor(
/**
* Base URI of the image, if available.
*/
public baseUri? : vscode.Uri,
/**
* Icon URI as sent by the LSP server. Images translated to ThemeIcons have this field undefined.
*/
public iconUri? : vscode.Uri,
/**
* Local resource or theme icon.
*/
public icon? : string | ThemeIcon,
/**
* Additional matched values
*/
public values? : string[],
) {}
}
class ViewInfo {
constructor(
readonly treeView : vscode.TreeView<Visualizer>,
readonly visProvider : VisualizerProvider)
{}
}
export class TreeViewService extends vscode.Disposable {
private handler : vscode.Disposable | undefined;
private client : NbLanguageClient;
private trees : Map<string, ViewInfo> = new Map();
private images : Map<number | vscode.Uri, CachedImage> = new Map();
private providers : Map<number, VisualizerProvider> = new Map();
log : vscode.OutputChannel;
private entries : ImageEntry[] = [];
constructor (log : vscode.OutputChannel, c : NbLanguageClient, dd : vscode.Disposable[]) {
super(() => {
this.disposeAllViews();
for (const d of dd) {
d?.dispose();
}
});
this.log = log;
this.client = c;
this.refreshImages();
dd.push(vscode.extensions.onDidChange(() => this.refreshImages()));
}
getClient() : NbLanguageClient {
return this.client;
}
private disposeAllViews() : void {
for (let tree of this.trees.values()) {
tree.visProvider.dispose();
tree.treeView.dispose();
}
this.trees.clear();
this.providers.clear();
this.handler?.dispose();
}
public async createView(id : string, title? : string, options? :
Partial<vscode.TreeViewOptions<any> & {
providerInitializer : (provider : CustomizableTreeDataProvider<Visualizer>) => void }
>) : Promise<vscode.TreeView<Visualizer>> {
let tv : ViewInfo | undefined = this.trees.get(id);
if (tv) {
return tv.treeView;
}
const res = await createViewProvider(this.client, id);
this.providers.set(res.getRoot().data.id, res);
options?.providerInitializer?.(res)
let opts : vscode.TreeViewOptions<Visualizer> = {
treeDataProvider : res,
canSelectMany: true,
showCollapseAll: true,
}
if (options?.canSelectMany !== undefined) {
opts.canSelectMany = options.canSelectMany;
}
if (options?.showCollapseAll !== undefined) {
opts.showCollapseAll = options.showCollapseAll;
}
let view = vscode.window.createTreeView(id, opts);
this.trees.set(id, new ViewInfo(view, res));
// this will replace the handler over and over, but never mind
this.handler = this.client.onNotification(NodeInfoNotification.type, params => this.nodeChanged(params));
return view;
}
private listeners: Map<string, {types : NodeChangeType[], listener : TreeNodeListener}[]> = new Map();
private removeListenerRegistration(key : string, data : {types : NodeChangeType[], listener : TreeNodeListener}) : void {
let a = this.listeners.get(key);
if (!a) {
return;
}
let index = a?.findIndex((x) => x === data);
if (index !== undefined) {
a?.splice(index, 1);
if (!a?.length) {
this.listeners.delete(key);
}
}
}
public addNodeChangeListener(node : Visualizer, listener : TreeNodeListener, ...types : NodeChangeType[]) : vscode.Disposable {
const listenerKey = node.rootId + ':' + (node.id || '');
let a = this.listeners.get(listenerKey);
if (a === undefined) {
a = [];
this.listeners.set(listenerKey, a);
}
const data = { types, listener };
a.push(data);
let success = false;
const r = this.client.sendRequest(NodeInfoRequest.changes, { rootId : node.rootId, nodeId: Number(node.id), types });
r.catch(() => {
// remove the listener registration
this.removeListenerRegistration(listenerKey, data);
});
return new vscode.Disposable(() => {
this.removeListenerRegistration(listenerKey, data);
});
}
private nodeChanged(params : NodeChangedParams) : void {
let p : VisualizerProvider | undefined = this.providers.get(params.rootId);
if (!p) {
return;
}
p.refresh(params);
const key = params.rootId + ':' + (params.nodeId || '');
const list = this.listeners.get(key);
if (!list || !params.nodeId) {
return;
}
const v = p.item(params.nodeId);
if (!v) {
return;
}
for (let { types, listener } of list) {
if (!params.types) {
// unspecified change
listener.nodeChanged?.(v);
continue;
}
const filtered = params.types.filter((t) => !types || types.indexOf(t) != -1);
if (filtered.includes(NodeChangeType.CHILDEN)) {
listener.nodeChildrenChanged?.(v);
}
if (filtered.includes(NodeChangeType.SELF)) {
listener.nodeChanged?.(v);
}
if (filtered.includes(NodeChangeType.DESTROY)) {
listener.nodeDestroyed?.(v);
}
if (filtered.includes(NodeChangeType.PROPERTY)) {
listener.nodePropertiesChanged?.(v, params.properties);
}
}
}
/**
* Requests an image data from the LSP server.
* @param nodeData
* @returns icon specification or undefined
*/
async fetchImageUri(nodeData : NodeInfoRequest.Data) : Promise<vscode.Uri | string | ThemeIcon | undefined> {
let res : vscode.Uri | string | ThemeIcon | undefined = this.imageUri(nodeData);
if (res) {
return res;
}
if (!nodeData?.iconDescriptor) {
return undefined;
}
let ci : CachedImage | undefined;
ci = this.images.get(nodeData.iconDescriptor.baseUri);
if (ci != null) {
return ci?.iconUri;
}
const p : GetResourceParams = {
acceptEncoding: [ 'base64' ],
uri : nodeData.iconDescriptor.baseUri
};
let iconData = await this.client.sendRequest(NodeInfoRequest.getresource, p);
if (!iconData?.content) {
return undefined;
}
let iconString = `data: ${iconData.contentType || 'image/png'};${iconData.encoding || 'base64'},${iconData.content}`;
ci = new CachedImage(nodeData.iconDescriptor.baseUri, vscode.Uri.parse(iconString), undefined);
this.images.set(nodeData.iconDescriptor.baseUri, ci);
return ci.iconUri;
}
imageUri(nodeData : NodeInfoRequest.Data) : vscode.Uri | string | ThemeIcon | undefined {
if (nodeData.id < 0) {
return undefined;
}
let ci : CachedImage | undefined;
if (nodeData.iconDescriptor?.baseUri) {
const r = this.findProductIcon(nodeData.iconDescriptor.baseUri, nodeData.name, nodeData.contextValue);
// override the icon with local.
if (r) {
if (r === EmptyIcon) {
ci = new CachedImage(nodeData.iconDescriptor.baseUri, undefined, undefined, [ nodeData.name, nodeData.contextValue ]);
}
ci = new CachedImage(nodeData.iconDescriptor.baseUri, undefined, r, [ nodeData.name, nodeData.contextValue ]);
this.images.set(nodeData.iconIndex, ci);
}
}
if (!ci) {
// hardcode visual vscode's File icons for regular files:
if (nodeData.resourceUri && nodeData.contextValue.includes('is:file')) {
const uri : vscode.Uri | undefined = nodeData.iconUri ? vscode.Uri.parse(nodeData.iconUri) : undefined;
// do not cache
return ThemeIcon.File;
}
}
return ci?.icon ? ci.icon : ci?.iconUri;
}
public setTranslations(entries : ImageEntry[]) {
this.entries = entries;
}
public findProductIcon(res : vscode.Uri, ...values: string[]) : string | ThemeIcon | undefined {
const s : string = res.toString();
outer: for (let e of this.entries) {
if (e.uriRegexp.test(s)) {
if (e.valueRegexps) {
let s : string = " " + values.join(" ") + " ";
for (let vr of e.valueRegexps) {
if (!vr.test(s)) {
continue outer;
}
}
}
if (e.codeicon === '*file') {
return ThemeIcon.File;
} else if (e.codeicon == '*folder') {
return ThemeIcon.Folder;
} else if (e.codeicon == '') {
return EmptyIcon;
} else if (e.iconPath) {
return e.iconPath;
}
let resultIcon;
if (e.color) {
resultIcon = new ThemeIcon(e.codeicon, new vscode.ThemeColor(e.color));
} else {
resultIcon = new ThemeIcon(e.codeicon);
}
return resultIcon;
}
}
return undefined;
}
public refreshImages() {
let newEntries : ImageEntry[] = [];
for (const ext of vscode.extensions.all) {
const iconMapping = ext.packageJSON?.contributes && ext.packageJSON?.contributes['netbeans.iconMapping'];
if (Array.isArray(iconMapping)) {
for (const m of iconMapping) {
const reString = m?.uriExpression;
if (reString) {
try {
let re : RegExp = new RegExp(reString);
let vals = [];
if (m?.valueMatch) {
for (const vm of m.valueMatch) {
const re = new RegExp(vm);
vals.push(re);
}
}
newEntries.push(new ImageEntry(re, m?.codeicon, m?.iconPath, vals, m?.color));
} catch (e) {
console.log("Invalid icon mapping in extension %s: %s -> %s", ext.id, reString, m?.codicon);
}
}
}
}
}
this.setTranslations(newEntries);
}
public async findPath(tree : vscode.TreeView<Visualizer>, selectData : any) : Promise<Visualizer | undefined> {
let selected : ViewInfo | undefined;
for (let vinfo of this.trees.values()) {
if (vinfo.treeView === tree) {
selected = vinfo;
}
}
if (!selected) {
return undefined;
}
return selected.visProvider.findTreeItem(selectData);
}
}
export interface TreeItemDecorator<T> extends vscode.Disposable {
decorateTreeItem(element: T, item : vscode.TreeItem): vscode.TreeItem | Thenable<vscode.TreeItem>;
decorateChildren(element: T, children: Visualizer[]): Visualizer[] | Thenable<Visualizer[]>;
}
export interface CustomizableTreeDataProvider<T> extends vscode.TreeDataProvider<T> {
fireItemChange(item? : T) : void;
addItemDecorator(deco : TreeItemDecorator<T>) : vscode.Disposable;
getRoot() : T;
}
class VisualizerProvider extends vscode.Disposable implements CustomizableTreeDataProvider<Visualizer> {
private root: Visualizer;
private treeData : Map<number, Visualizer> = new Map();
private decorators : TreeItemDecorator<Visualizer>[] = [];
constructor(
private client: LanguageClient,
private ts : TreeViewService,
private log : vscode.OutputChannel,
readonly id : string,
rootData : NodeInfoRequest.Data,
uri : vscode.Uri | string | ThemeIcon | undefined
) {
super(() => this.disconnect());
this.root = new Visualizer(rootData.id, rootData.id, rootData, uri);
this.treeData.set(rootData.id, this.root);
}
private _onDidChangeTreeData: vscode.EventEmitter<Visualizer | undefined | null | void> = new vscode.EventEmitter<Visualizer | undefined | null | void>();
readonly onDidChangeTreeData: vscode.Event<Visualizer | undefined | null | void> = this._onDidChangeTreeData.event;
private disconnect() : void {
// nothing at the moment.
for (let deco of this.decorators) {
deco.dispose();
}
}
item(id : number) : Visualizer | undefined {
return this.treeData.get(id);
}
fireItemChange(item : Visualizer | undefined) : void {
if (doLog) {
this.log.appendLine(`Firing change on ${item?.idstring()}`);
}
if (!item || item == this.root) {
this._onDidChangeTreeData.fire();
} else {
this._onDidChangeTreeData.fire(item);
}
}
addItemDecorator(decoInstance : TreeItemDecorator<Visualizer>) : vscode.Disposable {
this.decorators.push(decoInstance);
const self = this;
return new vscode.Disposable(() => {
const idx = this.decorators.indexOf(decoInstance);
if (idx > 0) {
this.decorators.splice(idx, 1);
decoInstance.dispose();
}
});
}
refresh(params : NodeChangedParams): void {
if (this.root.data.id === params.rootId) {
let v : Visualizer | undefined;
if (this. root.data.id == params.nodeId || !params.nodeId) {
v = this.root;
} else {
v = this.treeData.get(params.nodeId);
}
if (v) {
if (this.delayedFire.has(v)) {
if (doLog) {
this.log.appendLine(`Delaying change on ${v.idstring()}`);
}
v.pendingChange = true;
} else {
this.fireItemChange(v);
}
}
}
}
async findTreeItem(toSelect : any) : Promise<Visualizer | undefined> {
let path : number[] = await this.client.sendRequest(NodeInfoRequest.findparams, {
selectData : toSelect,
rootNodeId : Number(this.root.id)
});
if (!path) {
return;
}
let current : Visualizer = this.root;
if (path.length > 1 && path[0] == Number(this.root.id)) {
path.shift();
}
for (let nodeId of path) {
let children : Visualizer[];
if (current.children) {
children = Array.from(current.children.values());
} else {
children = await this.getChildren(current);
}
if (!children) {
return undefined;
}
let selected : Visualizer | null = null;
for (let c of children) {
if (c.id == String(nodeId)) {
selected = c;
break;
}
}
if (!selected) {
return undefined;
}
current = selected;
}
return current;
}
getRoot() : Visualizer {
return this.root.copy();
}
getParent(element : Visualizer) : Visualizer | null | Thenable<Visualizer | null> {
// rely on that children was called first
return element.parent;
}
getTreeItem(element: Visualizer): vscode.TreeItem | Thenable<vscode.TreeItem> {
const n : number = Number(element.id);
const self = this;
if (doLog) {
this.log.appendLine(`Doing getTreeItem on ${element.idstring()}`);
}
return this.wrap(async (arr) => {
const pn : number = Number(element.parent?.id) || -1;
let fetched = await this.queryVisualizer(element, arr, () => this.fetchItem(pn, n));
let origin : vscode.TreeItem;
if (fetched) {
element.update(fetched);
origin = await self.getTreeItem2(fetched);
} else {
// fire a change, this was unexpected
const pn : number = Number(element.parent?.id) || -1;
let pv = this.treeData.get(pn);
if (pv) {
this.fireItemChange(pv);
}
origin = element;
}
let ti : vscode.TreeItem = new vscode.TreeItem(origin.label || "", origin.collapsibleState);
// See #4113 -- vscode broke icons display, if resourceUri is defined in TreeItem. We're OK with files,
// but folders can have a semantic icon, so let hide resourceUri from vscode for folders.
ti.command = origin.command;
ti.contextValue = origin.contextValue;
ti.description = origin.description;
ti.iconPath = origin.iconPath;
ti.id = origin.id;
ti.label = origin.label;
ti.tooltip = origin.tooltip;
ti.accessibilityInformation = origin.accessibilityInformation;
if (origin.resourceUri) {
if (!origin.resourceUri.toString().endsWith("/")) {
ti.resourceUri = origin.resourceUri;
}
}
return ti;
});
}
/**
* Wraps code that queries individual Visualizers so that blocked changes are fired after
* the code terminated.
*
* Usage:
* wrap(() => { ... code ... ; queryVisualizer(vis, () => { ... })});
* @param fn the code to execute
* @returns value of the code function
*/
async wrap<X>(fn : (pending : Visualizer[]) => Thenable<X>) : Promise<X> {
let arr : Visualizer[] = [];
try {
return await fn(arr);
} finally {
this.releaseVisualizersAndFire(arr);
}
}
/**
* Just creates a string list from visualizer IDs. Diagnostics only.
*/
private visualizerList(arr : Visualizer[]) : string {
let s = "";
for (let v of arr) {
s += v.idstring() + " ";
}
return s;
}
/**
* Do not use directly, use wrap(). Fires delayed events for visualizers that have no pending queries.
*/
private releaseVisualizersAndFire(list : Visualizer[] | undefined) {
if (!list) {
list = Array.from(this.delayedFire);
}
if (doLog) {
this.log.appendLine(`Done with ${this.visualizerList(list)}`);
}
// v can be in list several times, each push increased its counter, so we need to decrease it.
for (let v of list) {
if (this.treeData?.get(Number(v.id || -1)) === v) {
if (--v.pendingQueries) {
if (doLog) {
this.log.appendLine(`${v.idstring()} has pending ${v.pendingQueries} queries`);
}
continue;
}
if (v.pendingChange) {
if (doLog) {
this.log.appendLine(`Fire delayed change on ${v.idstring()}`);
}
this.fireItemChange(v);
v.pendingChange = false;
}
}
this.delayedFire.delete(v);
}
if (doLog) {
this.log.appendLine("Pending queue: " + this.visualizerList(Array.from(this.delayedFire)));
this.log.appendLine("---------------");
}
}
/**
* Should wrap calls to NBLS for individual visualizers (info, children). Puts visualizer on the delayed fire list.
* Must be itself wrapped in wrap() -- wrap(... queryVisualizer()).
* @param element visualizer to be queried, possibly undefined (new item is expected)
* @param fn code to execute
* @returns code's result
*/
async queryVisualizer<X>(element : Visualizer | undefined, pending : Visualizer[], fn : () => Promise<X>) : Promise<X> {
if (!element) {
return fn();
}
this.delayedFire.add(element);
pending.push(element);
element.pendingQueries++;
if (doLog) {
this.log.appendLine(`Delaying visualizer ${element.idstring()}, queries = ${element.pendingQueries}`)
}
return fn();
}
async getTreeItem2(element: Visualizer): Promise<vscode.TreeItem> {
const n = Number(element.id);
if (this.decorators.length == 0) {
return element;
}
let list : TreeItemDecorator<Visualizer>[] = [...this.decorators];
async function f(item : vscode.TreeItem) : Promise<vscode.TreeItem> {
const deco = list.shift();
if (!deco) {
return item;
}
const decorated = deco.decorateTreeItem(element, item);
if (decorated instanceof vscode.TreeItem) {
return f(decorated);
} else {
return (decorated as Thenable<vscode.TreeItem>).then(f);
}
}
return f(element.copy());
}
delayedFire : Set<Visualizer> = new Set<Visualizer>();
async fetchItem(parent : number, n : number) : Promise<Visualizer | undefined> {
let d = await this.client.sendRequest(NodeInfoRequest.info, { nodeId : n });
if (!d || d?.id < 0) {
return undefined;
}
let iconUri = await this.ts.fetchImageUri(d);
let v = new Visualizer(this.root.data.id, n, d, iconUri);
if (d.command) {
// PENDING: provide an API to register command (+ parameters) -> command translators.
if (d.command === 'vscode.open') {
v.command = { command : d.command, title: '', arguments: [v.resourceUri]};
} else {
v.command = { command : d.command, title: '', arguments: [v]};
}
}
return v;
}
getChildren(e?: Visualizer): Thenable<Visualizer[]> {
const self = this;
if (doLog) {
this.log.appendLine(`Doing getChildren on ${e?.idstring()}`);
}
let decos : TreeItemDecorator<Visualizer>[] = [...this.decorators];
const parent = e || this.root;
async function collectResults(list : Visualizer[], arr: any, element: Visualizer): Promise<Visualizer[]> {
let res : Visualizer[] = [];
let now : Visualizer[] | undefined;
const pn : number = Number(element.id) || -1;
for (let i = 0; i < arr.length; i++) {
const old : Visualizer | undefined = self.treeData.get(arr[i]);
let v : Visualizer | undefined = await self.queryVisualizer(old, list, () => self.fetchItem(pn, arr[i]));
if (v) {
res.push(v);
}
}
if (decos.length > 0) {
async function f(orig: Visualizer[]) : Promise<Visualizer[]> {
const deco = decos.shift();
if (!deco) {
return orig;
}
// decorateChildren(element: T, item : Visualizer, children: Visualizer[]): Visualizer[] | Thenable<Visualizer[]>;
const decorated = deco.decorateChildren(parent, orig);
if (Array.isArray(decorated)) {
return f(decorated);
} else {
return (decorated as Thenable<Visualizer[]>).then(f);
}
}
res = await f(res);
}
now = element.updateChildren(res, self);
for (let i = 0; i < now.length; i++) {
const v = now[i];
const n : number = Number(v.id || -1);
self.treeData.set(n, v);
v.parent = element;
}
return now || [];
}
return self.wrap((list) => self.queryVisualizer(e, list, () => {
return this.client.sendRequest(NodeInfoRequest.children, { nodeId : parent.data.id}).then(async (arr) => {
return collectResults(list, arr, parent);
});
}
));
}
removeVisualizers(vis : number[]) {
let ch : number[] = [];
vis.forEach(a => {
let v : Visualizer | undefined = this.treeData.get(a);
if (v && v.children) {
ch.push(...v.children.keys());
this.treeData.delete(a);
}
});
// cascade
if (ch.length > 0) {
this.removeVisualizers(ch);
}
}
}
let visualizerSerial = 1;
export class Visualizer extends vscode.TreeItem {
visId : number;
pendingQueries : number = 0;
pendingChange : boolean = false;
constructor(
public rootId : number,
explicitId : number,
public data : NodeInfoRequest.Data,
public image : vscode.Uri | string | ThemeIcon | undefined
) {
super(data.id < 0 ? "< obsolete >" : data.label, data.collapsibleState);
this.visId = visualizerSerial++;
this.id = "" + explicitId;
this.label = data.label;
this.description = data.description;
this.tooltip = data.tooltip;
this.collapsibleState = data.collapsibleState;
this.iconPath = image;
if (data.resourceUri) {
this.resourceUri = vscode.Uri.parse(data.resourceUri);
}
this.contextValue = data.contextValue;
}
copy() : Visualizer {
let v : Visualizer = new Visualizer(this.rootId, Number(this.id), this.data, this.image);
v.id = this.id;
v.label = this.label;
v.description = this.description;
v.tooltip = this.tooltip;
v.iconPath = this.iconPath;
v.resourceUri = this.resourceUri;
v.contextValue = this.contextValue;
return v;
}
parent: Visualizer | null = null;
children: Map<number, Visualizer> | null = null;
idstring() : string {
return `[${this.id} : ${this.visId} - "${this.label}"]`;
}
update(other : Visualizer) : Visualizer {
this.label = other.label;
this.description = other.description;
this.tooltip = other.tooltip;
this.collapsibleState = other.collapsibleState;
this.iconPath = other.iconPath;
this.resourceUri = other.resourceUri;
this.contextValue = other.contextValue;
this.data = other.data;
this.image = other.image;
this.collapsibleState = other.collapsibleState;
this.command = other.command;
return this;
}
updateChildren(newChildren : Visualizer[], provider : VisualizerProvider) : Visualizer[] {
let toRemove : number[] = [];
let ch : Map<number, Visualizer> = new Map();
for (let i = 0; i < newChildren.length; i++) {
let c = newChildren[i];
const n : number = Number(c.id || -1);
const v : Visualizer | undefined = this.children?.get(n);
if (v) {
v.update(c);
newChildren[i] = c = v;
}
ch.set(n, c);
}
if (this.children) {
for (let k of this.children.keys()) {
if (!ch.get(k)) {
toRemove.push(k);
}
}
}
this.children = ch;
if (toRemove.length) {
provider.removeVisualizers(toRemove);
}
return newChildren;
}
}
class ImageEntry {
constructor(
readonly uriRegexp : RegExp,
readonly codeicon : string,
readonly iconPath? : string,
readonly valueRegexps? : RegExp[],
readonly color?: string
) {}
}
class ImageTranslator {
private entries : ImageEntry[] = [];
public setTranslations(entries : ImageEntry[]) {
this.entries = entries;
}
public findProductIcon(res : string) : string | undefined {
for (let e of this.entries) {
if (e.uriRegexp.exec(res)) {
return e.codeicon;
}
}
return undefined;
}
}
export async function createViewProvider(c : NbLanguageClient, id : string) : Promise<VisualizerProvider> {
const ts = c.findTreeViewService();
const client = ts.getClient();
const res = client.sendRequest(NodeInfoRequest.explorermanager, { explorerId: id }).then(async node => {
if (!node) {
throw "Unsupported view: " + id;
}
return new VisualizerProvider(client, ts, ts.log, id, node, await ts.fetchImageUri(node));
});
if (!res) {
throw "Unsupported view: " + id;
}
return res;
}
/**
* Creates a view of the specified type or returns an existing one. The View has to be registered in package.json in
* some workspace position. Waits until the view service initializes.
*
* @param id view ID, consistent with package.json registration
* @param viewTitle title for the new view, optional.
* @returns promise of the tree view instance.
*/
export async function createTreeView<T>(c: NbLanguageClient, viewId: string, viewTitle? : string, options? : Partial<vscode.TreeViewOptions<any>>) : Promise<vscode.TreeView<Visualizer>> {
let ts = c.findTreeViewService();
return ts.createView(viewId, viewTitle, options);
}
/**
* Registers the treeview service with the language server.
*/
export function createTreeViewService(log : vscode.OutputChannel, c : NbLanguageClient): TreeViewService {
const d = vscode.commands.registerCommand("foundProjects.deleteEntry", async function (this: any, args: any) {
let v = args as Visualizer;
let ok = await c.sendRequest(NodeInfoRequest.destroy, { nodeId : v.data.id });
if (!ok) {
vscode.window.showErrorMessage('Cannot delete node ' + v.label);
}
});
const ts : TreeViewService = new TreeViewService(log, c, [ d ]);
return ts;
}