diff --git a/package-lock.json b/package-lock.json
index 48eff04..8b3ac2b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "xml",
- "version": "2.0.0",
+ "version": "2.0.0-preview.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -16,6 +16,12 @@
"integrity": "sha512-jjpyQsKGsOF/wUElNjfPULk+d8PKvJOIXk3IUeBYYmNCy5dMWfrI+JiixYNw8ppKOlcRwWTXFl0B+i5oGrf95Q==",
"dev": true
},
+ "@types/xmldom": {
+ "version": "0.1.29",
+ "resolved": "https://registry.npmjs.org/@types/xmldom/-/xmldom-0.1.29.tgz",
+ "integrity": "sha1-xEKLDKhtO4gUdXJv2UmAs4onw4E=",
+ "dev": true
+ },
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
@@ -2616,14 +2622,6 @@
"integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=",
"dev": true
},
- "string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "requires": {
- "safe-buffer": "5.1.1"
- }
- },
"string-length": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
@@ -2632,6 +2630,14 @@
"strip-ansi": "3.0.1"
}
},
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
@@ -3067,6 +3073,11 @@
"user-home": "1.1.1"
}
},
+ "xmldom": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
+ "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk="
+ },
"xqlint": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xqlint/-/xqlint-0.4.0.tgz",
diff --git a/package.json b/package.json
index 3b303db..f96e064 100644
--- a/package.json
+++ b/package.json
@@ -63,6 +63,12 @@
"title": "XML Tools Configuration",
"type": "object",
"properties": {
+ "xmlTools.enableXmlTreeView": {
+ "type": "boolean",
+ "default": true,
+ "description": "Enables the XML Document view in the explorer for XML documents.",
+ "scope": "window"
+ },
"xmlTools.ignoreDefaultNamespace": {
"type": "boolean",
"default": true,
@@ -153,7 +159,16 @@
],
"configuration": "./languages/xquery/xquery.json"
}
- ]
+ ],
+ "views": {
+ "explorer": [
+ {
+ "id": "xmlTreeView",
+ "name": "XML Document",
+ "when": "xmlTreeViewEnabled"
+ }
+ ]
+ }
},
"scripts": {
"vscode:prepublish": "npm run compile",
@@ -166,11 +181,13 @@
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^7.0.43",
+ "@types/xmldom": "^0.1.29",
"tslint": "^5.9.1",
"typescript": "^2.6.1",
"vscode": "^1.1.6"
},
"dependencies": {
+ "xmldom": "^0.1.27",
"xqlint": "^0.4.0"
}
}
diff --git a/resources/icons/attribute.dark.svg b/resources/icons/attribute.dark.svg
new file mode 100644
index 0000000..f208dc3
--- /dev/null
+++ b/resources/icons/attribute.dark.svg
@@ -0,0 +1,35 @@
+
+
+
diff --git a/resources/icons/attribute.light.svg b/resources/icons/attribute.light.svg
new file mode 100644
index 0000000..2c5fc76
--- /dev/null
+++ b/resources/icons/attribute.light.svg
@@ -0,0 +1,56 @@
+
+
+
diff --git a/resources/icons/element.dark.svg b/resources/icons/element.dark.svg
new file mode 100644
index 0000000..45eced5
--- /dev/null
+++ b/resources/icons/element.dark.svg
@@ -0,0 +1,42 @@
+
+
+
+
diff --git a/resources/icons/element.light.svg b/resources/icons/element.light.svg
new file mode 100644
index 0000000..8e6cfd1
--- /dev/null
+++ b/resources/icons/element.light.svg
@@ -0,0 +1,54 @@
+
+
+
+
diff --git a/src/constants.ts b/src/constants.ts
index 33b87fe..552a47f 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -1 +1,13 @@
export const extensionPrefix = "xmlTools";
+
+export namespace commands {
+ export const setContext = "setContext";
+}
+
+export namespace contextKeys {
+ export const xmlTreeViewEnabled = "xmlTreeViewEnabled";
+}
+
+export namespace configKeys {
+ export const enableXmlTreeView = "enableXmlTreeView";
+}
diff --git a/src/extension.ts b/src/extension.ts
index 834d097..7b34170 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -7,6 +7,7 @@ import { MinifyXmlCommandName, minifyXml } from "./formatting/commands/minifyXml
import { XmlFormatterFactory } from "./formatting/xml-formatter";
import { XmlFormattingEditProvider } from "./formatting/xml-formatting-edit-provider";
import { XQueryLinter } from "./linting/xquery-linter";
+import { XmlTreeDataProvider } from "./tree-view/xml-tree-data-provider";
import * as constants from "./constants";
@@ -33,6 +34,11 @@ export function activate(context: ExtensionContext) {
window.onDidChangeActiveTextEditor(_handleChangeActiveTextEditor),
window.onDidChangeTextEditorSelection(_handleChangeTextEditorSelection)
);
+
+ /* Tree View Features */
+ context.subscriptions.push(
+ window.registerTreeDataProvider("xmlTreeView", new XmlTreeDataProvider(context))
+ );
}
export function deactivate() {
diff --git a/src/formatting/xml-formatting-options.ts b/src/formatting/xml-formatting-options.ts
index fe4670c..bcaf173 100644
--- a/src/formatting/xml-formatting-options.ts
+++ b/src/formatting/xml-formatting-options.ts
@@ -14,7 +14,7 @@ export interface XmlFormattingOptions {
export class XmlFormattingOptionsFactory {
static getXmlFormattingOptions(formattingOptions: FormattingOptions, eol: EndOfLine): XmlFormattingOptions {
const config = workspace.getConfiguration(constants.extensionPrefix);
-
+
return {
editorOptions: formattingOptions,
newLine: (eol === EndOfLine.CRLF) ? "\r\n" : "\n",
@@ -23,4 +23,4 @@ export class XmlFormattingOptionsFactory {
splitXmlnsOnFormat: config.get("splitXmlnsOnFormat")
};
}
-}
\ No newline at end of file
+}
diff --git a/src/tree-view/xml-tree-data-provider.ts b/src/tree-view/xml-tree-data-provider.ts
new file mode 100644
index 0000000..4012321
--- /dev/null
+++ b/src/tree-view/xml-tree-data-provider.ts
@@ -0,0 +1,142 @@
+import { commands, window, workspace } from "vscode";
+import {
+ Event, EventEmitter, ExtensionContext, TextEditor, TreeDataProvider,
+ TreeItem, TreeItemCollapsibleState
+} from "vscode";
+
+import * as path from "path";
+import { DOMParser } from "xmldom";
+
+import * as constants from "../constants";
+
+export class XmlTreeDataProvider implements TreeDataProvider {
+ private _onDidChangeTreeData: EventEmitter = new EventEmitter();
+ private _xmlDocument: any;
+
+ constructor(private _context: ExtensionContext) {
+ window.onDidChangeActiveTextEditor(() => {
+ this._refreshTree();
+ });
+
+ workspace.onDidChangeTextDocument(() => {
+ this._refreshTree();
+ });
+ }
+
+ onDidChangeTreeData = this._onDidChangeTreeData.event;
+
+ get activeEditor(): TextEditor {
+ return window.activeTextEditor || null;
+ }
+
+ getTreeItem(element: any): TreeItem | Thenable {
+ const treeItem = new TreeItem(element.localName);
+
+ if (this._getChildAttributeArray(element).length > 0) {
+ treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed;
+ }
+
+ if (this._getChildElementArray(element).length > 0) {
+ treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed;
+ }
+
+ treeItem.command = {
+ command: "revealLine",
+ title: "",
+ arguments: [{
+ lineNumber: element.lineNumber - 1,
+ at: "top"
+ }]
+ };
+
+ treeItem.iconPath = this._getIcon(element);
+
+ return treeItem;
+ }
+
+ getChildren(element?: any): any[] | Thenable {
+ if (!this._xmlDocument) {
+ this._refreshTree();
+ }
+
+ if (element) {
+ return [].concat(this._getChildAttributeArray(element), this._getChildElementArray(element));
+ }
+
+ else if (this._xmlDocument) {
+ return [this._xmlDocument.lastChild];
+ }
+
+ else {
+ return [];
+ }
+ }
+
+ private _getChildAttributeArray(node: any): any[] {
+ if (!node.attributes) {
+ return [];
+ }
+
+ const array = new Array();
+
+ for (let i = 0; i < node.attributes.length; i++) {
+ array.push(node.attributes[i]);
+ }
+
+ return array;
+ }
+
+ private _getChildElementArray(node: any): any[] {
+ if (!node.childNodes) {
+ return [];
+ }
+
+ const array = new Array();
+
+ for (let i = 0; i < node.childNodes.length; i++) {
+ let child = node.childNodes[i];
+
+ if (child.tagName) {
+ array.push(child);
+ }
+ }
+
+ return array;
+ }
+
+ private _getIcon(element: any): any {
+ let type = "element";
+
+ if (!element.tagName) {
+ type = "attribute";
+ }
+
+ const icon = {
+ dark: this._context.asAbsolutePath(path.join("resources", "icons", `${type}.dark.svg`)),
+ light: this._context.asAbsolutePath(path.join("resources", "icons", `${type}.light.svg`))
+ };
+
+ return icon;
+ }
+
+ private _refreshTree(): void {
+ if (!this.activeEditor || this.activeEditor.document.languageId !== "xml") {
+ commands.executeCommand(constants.commands.setContext, constants.contextKeys.xmlTreeViewEnabled, false);
+
+ this._xmlDocument = null;
+ this._onDidChangeTreeData.fire();
+ return;
+ }
+
+ const config = workspace.getConfiguration(constants.extensionPrefix);
+ const enableTreeView = config.get(constants.configKeys.enableXmlTreeView, true);
+
+ commands.executeCommand(constants.commands.setContext, constants.contextKeys.xmlTreeViewEnabled, enableTreeView);
+
+ const xml = this.activeEditor.document.getText();
+ this._xmlDocument = new DOMParser().parseFromString(xml, "text/xml");
+
+ this._onDidChangeTreeData.fire();
+ }
+
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index c3e70b8..b75f03e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,6 +4,7 @@
"target": "es6",
"outDir": "out",
"lib": [
+ "dom",
"es6"
],
"sourceMap": true,