Add Cursor Sync

#121
This commit is contained in:
Josh Johnson 2018-04-29 00:47:12 -04:00
parent 76fba19c0b
commit 323c5c310c
5 changed files with 240 additions and 107 deletions

View file

@ -13,6 +13,8 @@ export namespace contextKeys {
export namespace configKeys {
export const enableXmlTreeView = "enableXmlTreeView";
export const enableXmlTreeViewMetadata = "enableXmlTreeViewMetadata";
export const enableXmlTreeViewCursorSync = "enableXmlTreeViewCursorSync";
export const ignoreDefaultNamespace = "ignoreDefaultNamespace";
export const persistXPathQuery = "persistXPathQuery";
export const removeCommentsOnMinify = "removeCommentsOnMinify";

View file

@ -1,5 +1,8 @@
import { languages, window, workspace, commands } from "vscode";
import { ExtensionContext, Memento, TextEditor, TextEditorSelectionChangeEvent, WorkspaceConfiguration } from "vscode";
import {
ExtensionContext, Memento, TextEditor, TextEditorSelectionChangeEvent,
TextEditorSelectionChangeKind, WorkspaceConfiguration
} from "vscode";
import { createDocumentSelector } from "./common/create-document-selector";
import { XQueryCompletionItemProvider } from "./completion/xquery-completion-item-provider";
@ -47,8 +50,21 @@ export function activate(context: ExtensionContext) {
);
/* Tree View Features */
const treeViewDataProvider = new XmlTreeDataProvider(context);
const treeView = window.createTreeView<Node>(constants.views.xmlTreeView, {
treeDataProvider: treeViewDataProvider
});
if (config.get<boolean>(constants.configKeys.enableXmlTreeViewCursorSync)) {
window.onDidChangeTextEditorSelection(x => {
if (x.kind === TextEditorSelectionChangeKind.Mouse && x.selections.length > 0) {
treeView.reveal(treeViewDataProvider.getNodeAtPosition(x.selections[0].start));
}
});
}
context.subscriptions.push(
window.registerTreeDataProvider(constants.views.xmlTreeView, new XmlTreeDataProvider(context))
treeView
);
/* XPath Features */

View file

@ -1,7 +1,7 @@
import { commands, window, workspace } from "vscode";
import {
Event, EventEmitter, ExtensionContext, TextEditor, TreeDataProvider,
TreeItem, TreeItemCollapsibleState
Event, EventEmitter, ExtensionContext, Position, TextEditor, TreeDataProvider,
TreeItem, TreeItemCollapsibleState, WorkspaceConfiguration
} from "vscode";
import * as path from "path";
@ -10,10 +10,13 @@ import { DOMParser } from "xmldom";
import * as constants from "../constants";
export class XmlTreeDataProvider implements TreeDataProvider<any> {
private _config: WorkspaceConfiguration;
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
private _xmlDocument: any;
private _xmlDocument: Document;
constructor(private _context: ExtensionContext) {
this._config = workspace.getConfiguration(constants.extensionPrefix);
window.onDidChangeActiveTextEditor(() => {
this._refreshTree();
});
@ -30,13 +33,16 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
}
getTreeItem(element: Node): TreeItem | Thenable<TreeItem> {
const enableMetadata = this._config.get<boolean>(constants.configKeys.enableXmlTreeViewMetadata);
const enableSync = this._config.get<boolean>(constants.configKeys.enableXmlTreeViewCursorSync);
const treeItem = new TreeItem(element.localName);
if (!this._isElement(element)) {
treeItem.label = `${element.localName} = "${element.nodeValue}"`;
}
else {
else if (enableMetadata) {
const childAttributes = this._getChildAttributeArray(<Element>element);
const childElements = this._getChildElementArray(<Element>element);
const totalChildren = (childAttributes.length + childElements.length);
@ -57,6 +63,10 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
treeItem.label = treeItem.label.substr(0, treeItem.label.length - 2);
treeItem.label += ")";
}
if (this._hasSimilarSiblings(<Element>element) && enableSync) {
treeItem.label += ` [line ${(element as any).lineNumber}]`;
}
}
treeItem.command = {
@ -73,7 +83,7 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
return treeItem;
}
getChildren(element?: Node): any[] | Thenable<any[]> {
getChildren(element?: Node): Node[] | Thenable<Node[]> {
if (!this._xmlDocument) {
this._refreshTree();
}
@ -91,6 +101,43 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
}
}
getParent(element: Node): Node {
if (!element || !element.parentNode || !element.parentNode.parentNode) {
return undefined;
}
return element.parentNode;
}
getNodeAtPosition(position: Position): Node {
return this._getNodeAtPositionCore(position, this._xmlDocument.documentElement);
}
private _getNodeAtPositionCore(position: Position, contextElement: Element): Node {
if (!contextElement) {
return undefined;
}
if (((contextElement as any).lineNumber - 1) === position.line) {
return contextElement;
}
const children = this._getChildElementArray(<Element>contextElement);
let result: Node;
for (let i = 0; i < children.length; i++) {
const child = children[i];
result = this._getNodeAtPositionCore(position, child);
if (result) {
return result;
}
}
return undefined;
}
private _getChildAttributeArray(node: Element): any[] {
if (!node.attributes) {
return [];
@ -138,6 +185,16 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
return icon;
}
private _hasSimilarSiblings(element: Element): boolean {
if (!element || !element.parentNode) {
return false;
}
const siblings = this._getChildElementArray(<Element>element.parentNode);
return (siblings.filter(x => x.tagName === element.tagName).length > 1);
}
private _isElement(node: Node): boolean {
return (!!node && !!(node as Element).tagName);
}
@ -151,8 +208,7 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
return;
}
const config = workspace.getConfiguration(constants.extensionPrefix);
const enableTreeView = config.get<boolean>(constants.configKeys.enableXmlTreeView, true);
const enableTreeView = this._config.get<boolean>(constants.configKeys.enableXmlTreeView, true);
commands.executeCommand(constants.nativeCommands.setContext, constants.contextKeys.xmlTreeViewEnabled, enableTreeView);
@ -160,12 +216,13 @@ export class XmlTreeDataProvider implements TreeDataProvider<any> {
try {
this._xmlDocument = new DOMParser().parseFromString(xml, "text/xml");
this._onDidChangeTreeData.fire();
}
catch {
this._xmlDocument = new DOMParser().parseFromString("<InvalidDocument />", "text/xml");
}
this._onDidChangeTreeData.fire();
}
}