Files
vscode-extension-samples/tree-view-sample/src/jsonOutline.ts
2018-01-15 16:33:23 +01:00

185 lines
6.4 KiB
TypeScript

import * as vscode from 'vscode';
import * as json from 'jsonc-parser';
import * as path from 'path';
import { isNumber } from 'util';
export class JsonOutlineProvider implements vscode.TreeDataProvider<number> {
private _onDidChangeTreeData: vscode.EventEmitter<number | null> = new vscode.EventEmitter<number | null>();
readonly onDidChangeTreeData: vscode.Event<number | null> = this._onDidChangeTreeData.event;
private tree: json.Node;
private text: string;
private editor: vscode.TextEditor;
private autoRefresh: boolean = true;
constructor(private context: vscode.ExtensionContext) {
vscode.window.onDidChangeActiveTextEditor(() => this.onActiveEditorChanged());
vscode.workspace.onDidChangeTextDocument(e => this.onDocumentChanged(e));
this.parseTree();
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh');
vscode.workspace.onDidChangeConfiguration(() => {
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh');
});
this.onActiveEditorChanged();
}
refresh(offset?: number): void {
this.parseTree();
if (offset) {
this._onDidChangeTreeData.fire(offset);
} else {
this._onDidChangeTreeData.fire();
}
}
rename(offset: number): void {
vscode.window.showInputBox({ placeHolder: 'Enter the new label' })
.then(value => {
if (value !== null && value !== undefined) {
this.editor.edit(editBuilder => {
const path = json.getLocation(this.text, offset).path
let propertyNode = json.findNodeAtLocation(this.tree, path);
if (propertyNode.parent.type !== 'array') {
propertyNode = propertyNode.parent.children[0];
}
const range = new vscode.Range(this.editor.document.positionAt(propertyNode.offset), this.editor.document.positionAt(propertyNode.offset + propertyNode.length));
editBuilder.replace(range, `"${value}"`);
setTimeout(() => {
this.parseTree();
this.refresh(offset);
}, 100)
});
}
});
}
private onActiveEditorChanged(): void {
if (vscode.window.activeTextEditor) {
if (vscode.window.activeTextEditor.document.uri.scheme === 'file') {
const enabled = vscode.window.activeTextEditor.document.languageId === 'json' || vscode.window.activeTextEditor.document.languageId === 'jsonc';
vscode.commands.executeCommand('setContext', 'jsonOutlineEnabled', enabled);
if (enabled) {
this.refresh();
}
}
} else {
vscode.commands.executeCommand('setContext', 'jsonOutlineEnabled', false);
}
}
private onDocumentChanged(changeEvent: vscode.TextDocumentChangeEvent): void {
if (this.autoRefresh && changeEvent.document.uri.toString() === this.editor.document.uri.toString()) {
for (const change of changeEvent.contentChanges) {
const path = json.getLocation(this.text, this.editor.document.offsetAt(change.range.start)).path;
path.pop();
const node = path.length ? json.findNodeAtLocation(this.tree, path) : void 0;
this.parseTree();
this._onDidChangeTreeData.fire(node ? node.offset : void 0);
}
}
}
private parseTree(): void {
this.text = '';
this.tree = null;
this.editor = vscode.window.activeTextEditor;
if (this.editor && this.editor.document) {
this.text = this.editor.document.getText();
this.tree = json.parseTree(this.text);
}
}
getChildren(offset?: number): Thenable<number[]> {
if (offset) {
const path = json.getLocation(this.text, offset).path
const node = json.findNodeAtLocation(this.tree, path);
return Promise.resolve(this.getChildrenOffsets(node));
} else {
return Promise.resolve(this.tree ? this.getChildrenOffsets(this.tree) : []);
}
}
private getChildrenOffsets(node: json.Node): number[] {
const offsets: number[] = [];
for (const child of node.children) {
const childPath = json.getLocation(this.text, child.offset).path
const childNode = json.findNodeAtLocation(this.tree, childPath);
if (childNode) {
offsets.push(childNode.offset);
}
}
return offsets;
}
getTreeItem(offset: number): vscode.TreeItem {
const path = json.getLocation(this.text, offset).path
const valueNode = json.findNodeAtLocation(this.tree, path);
if (valueNode) {
let hasChildren = valueNode.type === 'object' || valueNode.type === 'array';
let treeItem: vscode.TreeItem = new vscode.TreeItem(this.getLabel(valueNode), hasChildren ? valueNode.type === 'object' ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None);
treeItem.command = {
command: 'extension.openJsonSelection',
title: '',
arguments: [new vscode.Range(this.editor.document.positionAt(valueNode.offset), this.editor.document.positionAt(valueNode.offset + valueNode.length))]
};
treeItem.iconPath = this.getIcon(valueNode);
treeItem.contextValue = valueNode.type;
return treeItem;
}
return null;
}
select(range: vscode.Range) {
this.editor.selection = new vscode.Selection(range.start, range.end);
}
private getIcon(node: json.Node): any {
let nodeType = node.type;
if (nodeType === 'boolean') {
return {
light: this.context.asAbsolutePath(path.join('resources', 'light', 'boolean.svg')),
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'boolean.svg'))
}
}
if (nodeType === 'string') {
return {
light: this.context.asAbsolutePath(path.join('resources', 'light', 'string.svg')),
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'string.svg'))
}
}
if (nodeType === 'number') {
return {
light: this.context.asAbsolutePath(path.join('resources', 'light', 'number.svg')),
dark: this.context.asAbsolutePath(path.join('resources', 'dark', 'number.svg'))
}
}
return null;
}
private getLabel(node: json.Node): string {
if (node.parent.type === 'array') {
let prefix = node.parent.children.indexOf(node).toString();
if (node.type === 'object') {
return prefix + ':{ }';
}
if (node.type === 'array') {
return prefix + ':[ ]';
}
return prefix + ':' + node.value.toString();
}
else {
const property = node.parent.children[0].value.toString();
if (node.type === 'array' || node.type === 'object') {
if (node.type === 'object') {
return '{ } ' + property;
}
if (node.type === 'array') {
return '[ ] ' + property;
}
}
const value = this.editor.document.getText(new vscode.Range(this.editor.document.positionAt(node.offset), this.editor.document.positionAt(node.offset + node.length)))
return `${property}: ${value}`;
}
}
}