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,