mirror of
https://github.com/microsoft/vscode-extension-samples.git
synced 2026-06-13 07:10:26 +08:00
198 lines
7.1 KiB
TypeScript
198 lines
7.1 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import * as json from 'jsonc-parser';
|
|
import * as path from 'path';
|
|
|
|
export class JsonOutlineProvider implements vscode.TreeDataProvider<number> {
|
|
|
|
private _onDidChangeTreeData: vscode.EventEmitter<number | undefined> = new vscode.EventEmitter<number | undefined>();
|
|
readonly onDidChangeTreeData: vscode.Event<number | undefined> = this._onDidChangeTreeData.event;
|
|
|
|
private tree: json.Node | undefined;
|
|
private text = '';
|
|
private editor: vscode.TextEditor | undefined;
|
|
private autoRefresh = true;
|
|
|
|
constructor(private context: vscode.ExtensionContext) {
|
|
vscode.window.onDidChangeActiveTextEditor(() => this.onActiveEditorChanged());
|
|
vscode.workspace.onDidChangeTextDocument(e => this.onDocumentChanged(e));
|
|
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh', false);
|
|
vscode.workspace.onDidChangeConfiguration(() => {
|
|
this.autoRefresh = vscode.workspace.getConfiguration('jsonOutline').get('autorefresh', false);
|
|
});
|
|
this.onActiveEditorChanged();
|
|
}
|
|
|
|
refresh(offset?: number): void {
|
|
this.parseTree();
|
|
if (offset) {
|
|
this._onDidChangeTreeData.fire(offset);
|
|
} else {
|
|
this._onDidChangeTreeData.fire(undefined);
|
|
}
|
|
}
|
|
|
|
rename(offset: number): void {
|
|
vscode.window.showInputBox({ placeHolder: 'Enter the new label' }).then(value => {
|
|
const editor = this.editor;
|
|
const tree = this.tree;
|
|
if (value !== null && value !== undefined && editor && tree) {
|
|
editor.edit(editBuilder => {
|
|
const path = json.getLocation(this.text, offset).path;
|
|
let propertyNode: json.Node | undefined = json.findNodeAtLocation(tree, path);
|
|
if (propertyNode.parent?.type !== 'array') {
|
|
propertyNode = propertyNode.parent?.children ? propertyNode.parent.children[0] : undefined;
|
|
}
|
|
if (propertyNode) {
|
|
const range = new vscode.Range(editor.document.positionAt(propertyNode.offset), 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.tree && 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 = undefined;
|
|
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 && this.tree) {
|
|
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[] = [];
|
|
if (node.children && this.tree) {
|
|
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 {
|
|
if (!this.tree) {
|
|
throw new Error('Invalid tree');
|
|
}
|
|
if (!this.editor) {
|
|
throw new Error('Invalid editor');
|
|
}
|
|
|
|
const path = json.getLocation(this.text, offset).path;
|
|
const valueNode = json.findNodeAtLocation(this.tree, path);
|
|
if (valueNode) {
|
|
const hasChildren = valueNode.type === 'object' || valueNode.type === 'array';
|
|
const 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;
|
|
}
|
|
throw (new Error(`Could not find json node at ${path}`));
|
|
}
|
|
|
|
select(range: vscode.Range) {
|
|
if (this.editor) {
|
|
this.editor.selection = new vscode.Selection(range.start, range.end);
|
|
}
|
|
}
|
|
|
|
private getIcon(node: json.Node): any {
|
|
const 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') {
|
|
const 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 ? 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}`;
|
|
}
|
|
}
|
|
}
|