forked from external/vscode-xml
commit
e60b94b384
85 changed files with 6570 additions and 4611 deletions
BIN
.github/wiki-resources/xml-attribute.png
vendored
BIN
.github/wiki-resources/xml-attribute.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 643 B |
BIN
.github/wiki-resources/xml-tag.png
vendored
BIN
.github/wiki-resources/xml-tag.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 456 B |
BIN
.github/wiki-resources/xml-text.png
vendored
BIN
.github/wiki-resources/xml-text.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 143 B |
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
out
|
||||
node_modules
|
||||
src/**/*.js
|
||||
**/*.vsix
|
||||
.vscode-test/
|
||||
.vsix
|
||||
|
|
|
|||
20
.travis.yml
20
.travis.yml
|
|
@ -1,10 +1,10 @@
|
|||
notifications:
|
||||
email: false
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- gulp build
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
sudo: false
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
41
.vscode/launch.json
vendored
41
.vscode/launch.json
vendored
|
|
@ -1,15 +1,28 @@
|
|||
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceRoot}"
|
||||
],
|
||||
"preLaunchTask": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
"version": "0.1.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Extension",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/out/**/*.js" ],
|
||||
"preLaunchTask": "npm: watch"
|
||||
},
|
||||
{
|
||||
"name": "Extension Tests",
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "${execPath}",
|
||||
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
||||
"stopOnEntry": false,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/out/test/**/*.js" ],
|
||||
"preLaunchTask": "npm: watch"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
14
.vscode/settings.json
vendored
14
.vscode/settings.json
vendored
|
|
@ -1,11 +1,9 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/.DS_Store": true,
|
||||
"**/*.vsix": true,
|
||||
"**/*.js": {"when": "$(basename).ts"}
|
||||
}
|
||||
,
|
||||
"typescript.tsdk": "./node_modules/typescript/lib"
|
||||
"files.exclude": {
|
||||
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||
},
|
||||
"search.exclude": {
|
||||
"out": true // set this to false to include "out" folder in search results
|
||||
}
|
||||
}
|
||||
29
.vscode/tasks.json
vendored
29
.vscode/tasks.json
vendored
|
|
@ -1,21 +1,20 @@
|
|||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
{
|
||||
"version": "0.1.0",
|
||||
"command": "gulp",
|
||||
"isShellCommand": true,
|
||||
"args": [
|
||||
"--no-color"
|
||||
],
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "build",
|
||||
"args": [],
|
||||
"isBuildCommand": true,
|
||||
"isWatching": false,
|
||||
"problemMatcher": [
|
||||
"$lessCompile",
|
||||
"$tsc",
|
||||
"$jshint"
|
||||
]
|
||||
"type": "npm",
|
||||
"script": "watch",
|
||||
"problemMatcher": "$tsc-watch",
|
||||
"isBackground": true,
|
||||
"presentation": {
|
||||
"reveal": "never"
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
tsconfig.json
|
||||
CONTRIBUTING.md
|
||||
.gitignore
|
||||
.vscode
|
||||
resources/wiki-images
|
||||
typings
|
||||
**/*.ts
|
||||
**/*.vsix
|
||||
.vscode/**
|
||||
.vscode-test/**
|
||||
out/test/**
|
||||
out/**/*.map
|
||||
src/**
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
vsc-extension-quickstart.md
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
# Contribution Guide
|
||||
Thanks for helping out! This guide provides some guidelines on coding styles and architecture for this extension.
|
||||
Keep in mind that these are guidelines, not rules. Use your best judgement and feel free to make suggestions!
|
||||
|
||||
## Workflow
|
||||
Please use the follow general workflow when making contributions to the code:
|
||||
|
||||
1. Fork the repository.
|
||||
2. Create a feature branch and make your changes.
|
||||
3. Create a pull request to request that your commits be merged to `master`.
|
||||
|
||||
|
||||
## Building the Extension
|
||||
1. Clone the repository locally and run `npm install` from the vscode-xml directory.
|
||||
2. Run the `build` task in VS Code (`CTRL + SHIFT + B`) or start extension debugging (`F5`), which will run the `build` task for you.
|
||||
|
||||
## Style Guide
|
||||
### Quotes
|
||||
Use double quotes for strings whenever possible.
|
||||
|
||||
### Imports
|
||||
As a general rule, always import the `vscode` namespace using the `vsc` alias for consistency.
|
||||
|
||||
### Static Classes
|
||||
When possible, try to use a static class to wrap utility functions so you are importing the class, not just a function.
|
||||
For libraries that do not follow this construct (such as the `xpath` module), *always* import the module using an alias (`import * as xpath from "xpath").
|
||||
|
||||
### Constants
|
||||
Where applicable, try to use constants instead of inline strings and numbers.
|
||||
|
||||
## Implicit Types
|
||||
Moving forward, the compiler is instructed (via tsconfig.json) to throw warnings if any expressions imply an `any` type. In other words, always use type declarations where applicable so it is clear what type is being used.
|
||||
There is an exception to this guideline. If you are using a thrid party library that does not have a *.d.ts file available, you do not need to write one. Just ensure the API is documented (either in this repo or in the library's repo).
|
||||
|
||||
## Folder Structure
|
||||
All TypeScript files should reside under the top `src` folder. Under this, there are several subfolders and top-level files defined below:
|
||||
|
||||
### providers
|
||||
This folder contains any classes that implement provider interfaces from the `vscode` namespace. This folder also contains any code that works directly with the
|
||||
APIs exposed by the `vscode` namespace, such as user interaction or configuration access. Aside for the `vscode` module, no code in this folder should be directly dependent on
|
||||
any external NodeJS modules or libraries.
|
||||
|
||||
### services
|
||||
This folder contains any classes that perform actions/logic required by the providers mentioned above. Services should not be dependent on the `vscode` namespace and *can* be
|
||||
dependent on external Node modules and libraries.
|
||||
|
||||
### utils
|
||||
This folder contains any utility classes/functions.
|
||||
|
||||
### Commands.ts
|
||||
This file acts as an interface to all registered commands in the extension. If a substantial amount of code is required to implement a command, it should be moved to a provider and/or service.
|
||||
|
||||
### Extension.ts
|
||||
Previously named `main.ts`, this is the primary entry point to the extension. Anything that needs done on activation or deactivation of the extension is done here. Both the workspace and global `Memento` instances are exposed from this module, which can be used by providers as needed.
|
||||
2
LICENSE
2
LICENSE
|
|
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
18
README.md
18
README.md
|
|
@ -10,6 +10,22 @@
|
|||
* [XQuery Execution](https://github.com/DotJoshJohnson/vscode-xml/wiki/xquery-script-execution)
|
||||
* [XQuery Code Completion](https://github.com/DotJoshJohnson/vscode-xml/wiki/xquery-code-completion)
|
||||
|
||||
## Requirements
|
||||
* VS Code `1.22.2` or higher
|
||||
|
||||
## Extension Settings
|
||||
* **`xmlTools.enableXmlTreeView`:** Enables the XML Tree View for XML documents.
|
||||
* **`xmlTools.enableXmlTreeViewMetadata`:** Enables attribute and child element counts in the XML Document view.
|
||||
* **`xmlTools.enableXmlTreeViewCursorSync`:** Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.
|
||||
* **`xmlTools.ignoreDefaultNamespace`:** Ignore default xmlns attributes when evaluating XPath.
|
||||
* **`xmlTools.persistXPathQuery`:** Remember the last XPath query used.
|
||||
* **`xmlTools.removeCommentsOnMinify`:** Remove XML comments during minification.
|
||||
* **`xmlTools.splitAttributesOnFormat`:** Put each attribute on a new line when formatting XML. Overrides `xmlTools.splitXmlsOnFormat` if set to `true`. (V2 Formatter Only)
|
||||
* **`xmlTools.splitXmlnsOnFormat`:** Put each xmlns attribute on a new line when formatting XML.
|
||||
* **`xmlTools.xmlFormatterImplementation`:** Supported XML Formatters: `classic`, `v2`.
|
||||
* **`xmlTools.xqueryExecutionArguments`:** Arguments to be passed to the XQuery execution engine.
|
||||
* **`xmlTools.xqueryExecutionEngine`:** The full path to the executable to run when executing XQuery scripts.
|
||||
|
||||
## Release Notes
|
||||
Detailed release notes are available [here](https://github.com/DotJoshJohnson/vscode-xml/releases).
|
||||
|
||||
|
|
@ -19,4 +35,4 @@ Run into a bug? Report it [here](https://github.com/DotJoshJohnson/vscode-xml/is
|
|||
## Icon Credits
|
||||
Icons used in the XML Tree View are used under the Creative Commons 3.0 BY license.
|
||||
* "Code" icon by Dave Gandy from www.flaticon.com
|
||||
* "At" icon by FreePik from www.flaticon.com
|
||||
* "At" icon by FreePik from www.flaticon.com
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
var gulp = require("gulp");
|
||||
|
||||
var shell = require("gulp-shell");
|
||||
|
||||
gulp.task("compile-typescript", function () {
|
||||
gulp.src("package.json").pipe(shell("tsc"));
|
||||
});
|
||||
|
||||
gulp.task("build", ["compile-typescript"]);
|
||||
|
|
@ -1,11 +1,23 @@
|
|||
{
|
||||
"comments": {
|
||||
"lineComment": "//",
|
||||
"blockComment": ["/*", "*/"]
|
||||
},
|
||||
"brackets": [
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"]
|
||||
]
|
||||
{
|
||||
"comments": {
|
||||
"lineComment": "//",
|
||||
"blockComment": [
|
||||
"/*",
|
||||
"*/"
|
||||
]
|
||||
},
|
||||
"brackets": [
|
||||
[
|
||||
"{",
|
||||
"}"
|
||||
],
|
||||
[
|
||||
"[",
|
||||
"]"
|
||||
],
|
||||
[
|
||||
"(",
|
||||
")"
|
||||
]
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
3454
package-lock.json
generated
Normal file
3454
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
291
package.json
291
package.json
|
|
@ -1,47 +1,51 @@
|
|||
{
|
||||
"name": "xml",
|
||||
"displayName": "XML Tools",
|
||||
"description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
|
||||
"version": "1.9.2",
|
||||
"publisher": "DotJoshJohnson",
|
||||
"author": {
|
||||
"name": "Josh Johnson",
|
||||
"url": "https://github.com/DotJoshJohnson"
|
||||
},
|
||||
"icon": "resources/xml.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
"theme": "light"
|
||||
},
|
||||
"homepage": "https://github.com/DotJoshJohnson/vscode-xml",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DotJoshJohnson/vscode-xml.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/DotJoshJohnson/vscode-xml/issues"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.13.0",
|
||||
"node": "^0.12.0"
|
||||
},
|
||||
"categories": [
|
||||
"Languages",
|
||||
"name": "xml",
|
||||
"displayName": "XML Tools",
|
||||
"description": "XML Formatting, XQuery, and XPath Tools for Visual Studio Code",
|
||||
"version": "2.0.0-preview.3",
|
||||
"preview": true,
|
||||
"publisher": "DotJoshJohnson",
|
||||
"author": {
|
||||
"name": "Josh Johnson",
|
||||
"url": "https://github.com/DotJoshJohnson"
|
||||
},
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
"theme": "light"
|
||||
},
|
||||
"homepage": "https://github.com/DotJoshJohnson/vscode-xml",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/DotJoshJohnson/vscode-xml.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/DotJoshJohnson/vscode-xml/issues"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.22.2"
|
||||
},
|
||||
"categories": [
|
||||
"Formatters",
|
||||
"Languages",
|
||||
"Linters",
|
||||
"Other",
|
||||
"Formatters"
|
||||
],
|
||||
"main": "./src/Extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "xmlTools.minifyXml",
|
||||
"title": "XML Tools: Minify XML"
|
||||
},
|
||||
{
|
||||
"command": "xmlTools.evaluateXPath",
|
||||
"title": "XML Tools: Evaluate XPath"
|
||||
},
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onCommand:xmlTools.evaluateXPath",
|
||||
"onCommand:xmlTools.executeXQuery",
|
||||
"onCommand:xmlTools.formatAsXml",
|
||||
"onCommand:xmlTools.minifyXml",
|
||||
"onLanguage:xml",
|
||||
"onLanguage:xquery",
|
||||
"onLanguage:xsl"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "xmlTools.evaluateXPath",
|
||||
"title": "XML Tools: Evaluate XPath"
|
||||
},
|
||||
{
|
||||
"command": "xmlTools.executeXQuery",
|
||||
"title": "XML Tools: Execute XQuery"
|
||||
|
|
@ -49,100 +53,175 @@
|
|||
{
|
||||
"command": "xmlTools.formatAsXml",
|
||||
"title": "XML Tools: Format as XML"
|
||||
},
|
||||
{
|
||||
"command": "xmlTools.minifyXml",
|
||||
"title": "XML Tools: Minify XML"
|
||||
}
|
||||
],
|
||||
],
|
||||
"configuration": {
|
||||
"title": "XML Tools Configuration",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"xmlTools.persistXPathQuery": {
|
||||
"xmlTools.enableXmlTreeView": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Remember the last XPath query used."
|
||||
"description": "Enables the XML Document view in the explorer for XML documents.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.removeCommentsOnMinify": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Remove XML comments when XML is minified."
|
||||
},
|
||||
"xmlTools.splitXmlnsOnFormat": {
|
||||
"xmlTools.enableXmlTreeViewMetadata": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Put each xmlns attribute on a new line when fromatting XML."
|
||||
"description": "Enables attribute and child element counts in the XML Document view.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.xqueryExecutionEngine": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The full path to the execution engine executable."
|
||||
},
|
||||
"xmlTools.xqueryExecutionArguments": {
|
||||
"type": "array",
|
||||
"default": ["-xquery", "$(script)", "-in", "$(input)", "-out", "$(input).output.xml"],
|
||||
"description": "Arguments to be passed to the execution engine. '$(script)' and '$(input)' refer to the XQuery script and input XML file, respectively."
|
||||
"xmlTools.enableXmlTreeViewCursorSync": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Enables auto-reveal of elements in the XML Document view when a start tag is clicked in the editor.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.ignoreDefaultNamespace": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Ignores default xmlns attribute when evaluating XPath."
|
||||
"description": "Ignore default xmlns attributes when evaluating XPath.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.persistXPathQuery": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Remember the last XPath query used.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.removeCommentsOnMinify": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Remove XML comments during minification.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"xmlTools.splitAttributesOnFormat": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Put each attribute on a new line when formatting XML. Overrides `xmlTools.splitXmlsOnFormat` if set to `true`.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"xmlTools.splitXmlnsOnFormat": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Put each xmlns attribute on a new line when formatting XML.",
|
||||
"scope": "resource"
|
||||
},
|
||||
"xmlTools.xmlFormatterImplementation": {
|
||||
"type": "string",
|
||||
"default": "v2",
|
||||
"description": "Supported XML Formatters: classic",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.xqueryExecutionArguments": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"-xquery",
|
||||
"$(script)",
|
||||
"-in",
|
||||
"$(input)",
|
||||
"-out",
|
||||
"$(input.output.xml"
|
||||
],
|
||||
"description": "Arguments to be passed to the XQuery execution engine.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.xqueryExecutionEngine": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "The full path to the executable to run when executing XQuery scripts.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.xqueryExecutionInputLimit": {
|
||||
"type": "integer",
|
||||
"default": 100,
|
||||
"description": "The maximum number of input files to enumerate when executing XQuery scripts.",
|
||||
"scope": "window"
|
||||
},
|
||||
"xmlTools.xqueryExecutionInputSearchPattern": {
|
||||
"type": "string",
|
||||
"default": "**/*.xml",
|
||||
"description": "The pattern used to search for input XML files when executing XQuery scripts.",
|
||||
"scope": "window"
|
||||
}
|
||||
}
|
||||
},
|
||||
"keybindings": [
|
||||
{
|
||||
"key": "ctrl+shift+alt+b",
|
||||
"command": "xmlTools.formatXml"
|
||||
},
|
||||
{
|
||||
"key": "ctrl+shift+alt+x",
|
||||
"command": "xmlTools.evaluateXPath"
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"id": "xquery",
|
||||
"aliases": ["XQuery", "xquery"],
|
||||
"extensions": [".xq",".xql",".xqm",".xqy",".xquery"],
|
||||
"configuration": "./languages/xquery/xquery.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "xquery",
|
||||
"scopeName": "source.xquery",
|
||||
"path": "./languages/xquery/xquery.tmLanguage"
|
||||
"path": "./languages/xquery/xquery.tmLanguage",
|
||||
"scopeName": "source.xquery"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"key": "ctrl+shift+alt+x",
|
||||
"command": "xmlTools.evaluateXPath"
|
||||
},
|
||||
{
|
||||
"key": "ctrl+shift+alt+b",
|
||||
"command": "xmlTools.formatAsXml"
|
||||
}
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"id": "xquery",
|
||||
"aliases": [
|
||||
"XQuery",
|
||||
"xquery"
|
||||
],
|
||||
"extensions": [
|
||||
".xq",
|
||||
".xql",
|
||||
".xqm",
|
||||
".xqy",
|
||||
".xquery"
|
||||
],
|
||||
"configuration": "./languages/xquery/xquery.json"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"editor/context": [
|
||||
{
|
||||
"command": "xmlTools.minifyXml",
|
||||
"group": "1_modification@100",
|
||||
"when": "editorLangId == 'xml'"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"explorer": [
|
||||
{
|
||||
"id": "xmlTreeView",
|
||||
"name": "XML Document"
|
||||
"name": "XML Document",
|
||||
"when": "xmlTreeViewEnabled"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"activationEvents": [
|
||||
"onLanguage:xml",
|
||||
"onLanguage:xsl",
|
||||
"onLanguage:xquery",
|
||||
"onCommand:xmlTools.minifyXml",
|
||||
"onCommand:xmlTools.evaluateXPath",
|
||||
"onCommand:xmlTools.executeXQuery",
|
||||
"onCommand:xmlTools.formatAsXml"
|
||||
],
|
||||
"devDependencies": {
|
||||
"vscode": "^1.1.0",
|
||||
"typescript": "^2.3.4",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-shell": "^0.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"xmldom": "^0.1.22",
|
||||
"xpath": "^0.0.9",
|
||||
"xqlint": "^0.2.9"
|
||||
},
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install"
|
||||
"vscode:prepublish": "npm run compile",
|
||||
"compile": "npm run lint && tsc -p ./",
|
||||
"watch": "tsc -watch -p ./",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install",
|
||||
"test": "npm run compile && mocha ./out/test/**/*.js",
|
||||
"lint": "tslint -p tslint.json --fix"
|
||||
},
|
||||
"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.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"xmldom": "^0.1.27",
|
||||
"xpath": "0.0.27",
|
||||
"xqlint": "^0.4.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 3.2 KiB |
|
|
@ -1,69 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import * as ext from "./Extension";
|
||||
import * as xpath from "xpath";
|
||||
import { RangeUtil } from "./utils/RangeUtil";
|
||||
import { XmlFormatter } from "./services/XmlFormatter";
|
||||
import { XPathFeatureProvider } from "./providers/XPath";
|
||||
import { XQueryExecutionProvider } from "./providers/Execution";
|
||||
import { XmlFormattingEditProvider } from "./providers/Formatting";
|
||||
|
||||
const CFG_SECTION: string = "xmlTools";
|
||||
const CFG_REMOVE_COMMENTS: string = "removeCommentsOnMinify";
|
||||
|
||||
export class TextEditorCommands {
|
||||
static minifyXml(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
|
||||
let removeComments: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_REMOVE_COMMENTS, false);
|
||||
|
||||
let range: vsc.Range = RangeUtil.getRangeForDocument(editor.document);
|
||||
|
||||
let formatter: XmlFormatter = new XmlFormatter();
|
||||
let xml: string = formatter.minify(editor.document.getText());
|
||||
|
||||
edit.replace(range, xml);
|
||||
}
|
||||
|
||||
static evaluateXPath(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
|
||||
XPathFeatureProvider.evaluateXPathAsync(editor, edit);
|
||||
}
|
||||
|
||||
static executeXQuery(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
|
||||
XQueryExecutionProvider.executeXQueryAsync(editor);
|
||||
}
|
||||
|
||||
static formatAsXml(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): void {
|
||||
let edits: vsc.TextEdit[];
|
||||
let formattingEditProvider = new XmlFormattingEditProvider();
|
||||
let formattingOptions: vsc.FormattingOptions = {
|
||||
insertSpaces: (editor.options.insertSpaces as boolean),
|
||||
tabSize: (editor.options.tabSize as number)
|
||||
};
|
||||
|
||||
// if the user has selected text, only format what is selected
|
||||
// otherwise, attempt to format the entire document
|
||||
if (!editor.selection.isEmpty) {
|
||||
edits = formattingEditProvider.provideDocumentRangeFormattingEdits(editor.document, editor.selection, formattingOptions);
|
||||
}
|
||||
|
||||
else {
|
||||
edits = formattingEditProvider.provideDocumentFormattingEdits(editor.document, formattingOptions);
|
||||
}
|
||||
|
||||
if (edits) {
|
||||
for (let i = 0; i < edits.length; i++) {
|
||||
editor.edit(async (editBuilder) => {
|
||||
editBuilder.replace(edits[i].range, edits[i].newText);
|
||||
|
||||
// wiggle the cursor to deselect the formatted XML (is there a non-hacky way to go about this?)
|
||||
await vsc.commands.executeCommand("cursorMove", {
|
||||
to: "left",
|
||||
by: "character"
|
||||
});
|
||||
await vsc.commands.executeCommand("cursorMove", {
|
||||
to: "right",
|
||||
by: "character"
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import { TextEditorCommands } from "./Commands";
|
||||
import { XmlFormattingEditProvider } from "./providers/Formatting";
|
||||
import { XQueryLintingFeatureProvider } from "./providers/Linting";
|
||||
import { XQueryCompletionItemProvider } from "./providers/Completion";
|
||||
import { XmlTreeViewDataProvider } from "./providers/XmlTreeView";
|
||||
|
||||
export var GlobalState: vsc.Memento;
|
||||
export var WorkspaceState: vsc.Memento;
|
||||
|
||||
const LANG_XML: string = "xml";
|
||||
const LANG_XSL: string = "xsl";
|
||||
const LANG_XQUERY: string = "xquery;"
|
||||
const MEM_QUERY_HISTORY: string = "xpathQueryHistory";
|
||||
|
||||
export function activate(ctx: vsc.ExtensionContext) {
|
||||
console.log("activate extension");
|
||||
// expose global and workspace state to the entire extension
|
||||
GlobalState = ctx.globalState;
|
||||
WorkspaceState = ctx.workspaceState;
|
||||
|
||||
// register palette commands
|
||||
ctx.subscriptions.push(
|
||||
vsc.commands.registerTextEditorCommand("xmlTools.minifyXml", TextEditorCommands.minifyXml),
|
||||
vsc.commands.registerTextEditorCommand("xmlTools.evaluateXPath", TextEditorCommands.evaluateXPath),
|
||||
vsc.commands.registerTextEditorCommand("xmlTools.executeXQuery", TextEditorCommands.executeXQuery),
|
||||
vsc.commands.registerTextEditorCommand("xmlTools.formatAsXml", TextEditorCommands.formatAsXml)
|
||||
);
|
||||
|
||||
// register language feature providers
|
||||
ctx.subscriptions.push(
|
||||
vsc.languages.registerDocumentFormattingEditProvider([LANG_XML, LANG_XSL], new XmlFormattingEditProvider()),
|
||||
vsc.languages.registerDocumentRangeFormattingEditProvider([LANG_XML, LANG_XSL], new XmlFormattingEditProvider()),
|
||||
|
||||
vsc.languages.registerCompletionItemProvider(LANG_XQUERY, new XQueryCompletionItemProvider(), ":", "$")
|
||||
);
|
||||
|
||||
// listen to editor events (for linting)
|
||||
ctx.subscriptions.push(
|
||||
vsc.window.onDidChangeActiveTextEditor(_handleChangeActiveTextEditor),
|
||||
vsc.window.onDidChangeTextEditorSelection(_handleChangeTextEditorSelection)
|
||||
);
|
||||
|
||||
// add views
|
||||
ctx.subscriptions.push(
|
||||
vsc.window.registerTreeDataProvider("xmlTreeView", new XmlTreeViewDataProvider(ctx))
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
// clean up xpath history
|
||||
let memento: vsc.Memento = WorkspaceState || GlobalState;
|
||||
let history = memento.get<any[]>(MEM_QUERY_HISTORY, []);
|
||||
history.splice(0);
|
||||
memento.update(MEM_QUERY_HISTORY, history);
|
||||
}
|
||||
|
||||
function _handleContextChange(editor: vsc.TextEditor): void {
|
||||
if (!editor || !editor.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (editor.document.languageId) {
|
||||
case "xquery":
|
||||
XQueryLintingFeatureProvider.provideXQueryDiagnostics(editor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function _handleChangeActiveTextEditor(editor: vsc.TextEditor): void {
|
||||
_handleContextChange(editor);
|
||||
}
|
||||
|
||||
function _handleChangeTextEditorSelection(e: vsc.TextEditorSelectionChangeEvent): void {
|
||||
_handleContextChange(e.textEditor);
|
||||
}
|
||||
65
src/common/configuration.ts
Normal file
65
src/common/configuration.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { workspace, Uri } from "vscode";
|
||||
|
||||
const ExtensionTopLevelSection = "xmlTools";
|
||||
|
||||
export class Configuration {
|
||||
static get enableXmlTreeView(): boolean {
|
||||
return this._getForWindow<boolean>("enableXmlTreeView");
|
||||
}
|
||||
|
||||
static get enableXmlTreeViewMetadata(): boolean {
|
||||
return this._getForWindow<boolean>("enableXmlTreeViewMetadata");
|
||||
}
|
||||
|
||||
static get enableXmlTreeViewCursorSync(): boolean {
|
||||
return this._getForWindow<boolean>("enableXmlTreeViewCursorSync");
|
||||
}
|
||||
|
||||
static get ignoreDefaultNamespace(): boolean {
|
||||
return this._getForWindow<boolean>("ignoreDefaultNamespace");
|
||||
}
|
||||
|
||||
static get persistXPathQuery(): boolean {
|
||||
return this._getForWindow<boolean>("persistXPathQuery");
|
||||
}
|
||||
|
||||
static get xmlFormatterImplementation(): string {
|
||||
return this._getForWindow<string>("xmlFormatterImplementation");
|
||||
}
|
||||
|
||||
static get xqueryExecutionArguments(): string[] {
|
||||
return this._getForWindow<string[]>("xqueryExecutionArguments");
|
||||
}
|
||||
|
||||
static get xqueryExecutionEngine(): string {
|
||||
return this._getForWindow<string>("xqueryExecutionEngine");
|
||||
}
|
||||
|
||||
static get xqueryExecutionInputLimit(): number {
|
||||
return this._getForWindow<number>("xqueryExecutionInputLimit");
|
||||
}
|
||||
|
||||
static get xqueryExecutionInputSearchPattern(): string {
|
||||
return this._getForWindow<string>("xqueryExecutionInputSearchPattern");
|
||||
}
|
||||
|
||||
static removeCommentsOnMinify(resource: Uri): boolean {
|
||||
return this._getForResource<boolean>("removeCommentsOnMinify", resource);
|
||||
}
|
||||
|
||||
static splitAttributesOnFormat(resource: Uri): boolean {
|
||||
return this._getForResource<boolean>("splitAttributesOnFormat", resource);
|
||||
}
|
||||
|
||||
static splitXmlnsOnFormat(resource: Uri): boolean {
|
||||
return this._getForResource<boolean>("splitXmlnsOnFormat", resource);
|
||||
}
|
||||
|
||||
private static _getForResource<T>(section: string, resource: Uri): T {
|
||||
return workspace.getConfiguration(ExtensionTopLevelSection, resource).get<T>(section);
|
||||
}
|
||||
|
||||
private static _getForWindow<T>(section: string): T {
|
||||
return workspace.getConfiguration(ExtensionTopLevelSection).get<T>(section);
|
||||
}
|
||||
}
|
||||
10
src/common/create-document-selector.ts
Normal file
10
src/common/create-document-selector.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { DocumentFilter } from "vscode";
|
||||
|
||||
import * as constants from "../constants";
|
||||
|
||||
export function createDocumentSelector(language: string): DocumentFilter[] {
|
||||
return [
|
||||
{ language, scheme: constants.uriSchemes.file },
|
||||
{ language, scheme: constants.uriSchemes.untitled },
|
||||
];
|
||||
}
|
||||
17
src/common/extension-state.ts
Normal file
17
src/common/extension-state.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { ExtensionContext, Memento } from "vscode";
|
||||
|
||||
export class ExtensionState {
|
||||
private static _context: ExtensionContext;
|
||||
|
||||
static get global(): Memento {
|
||||
return this._context.globalState;
|
||||
}
|
||||
|
||||
static get workspace(): Memento {
|
||||
return this._context.workspaceState;
|
||||
}
|
||||
|
||||
static configure(context: ExtensionContext): void {
|
||||
this._context = context;
|
||||
}
|
||||
}
|
||||
4
src/common/index.ts
Normal file
4
src/common/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./configuration";
|
||||
export * from "./create-document-selector";
|
||||
export * from "./extension-state";
|
||||
export * from "./native-commands";
|
||||
18
src/common/native-commands.ts
Normal file
18
src/common/native-commands.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { commands } from "vscode";
|
||||
|
||||
export class NativeCommands {
|
||||
static async cursorMove(to: string, by: string): Promise<void> {
|
||||
await commands.executeCommand("cursorMove", {
|
||||
to: to,
|
||||
by: by
|
||||
});
|
||||
}
|
||||
|
||||
static openGlobalSettings(): void {
|
||||
commands.executeCommand("workbench.action.openGlobalSettings");
|
||||
}
|
||||
|
||||
static setContext(key: string, value: any): void {
|
||||
commands.executeCommand("setContext", key, value);
|
||||
}
|
||||
}
|
||||
1
src/completion/index.ts
Normal file
1
src/completion/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./xquery-completion-item-provider";
|
||||
51
src/completion/xquery-completion-item-provider.ts
Normal file
51
src/completion/xquery-completion-item-provider.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { CompletionItem, CompletionItemKind, CompletionItemProvider, Position, TextDocument } from "vscode";
|
||||
|
||||
const XQLint = require("xqlint").XQLint;
|
||||
|
||||
export class XQueryCompletionItemProvider implements CompletionItemProvider {
|
||||
|
||||
provideCompletionItems(document: TextDocument, position: Position): CompletionItem[] {
|
||||
const completionItems = new Array<CompletionItem>();
|
||||
const linter = new XQLint(document.getText());
|
||||
|
||||
linter.getCompletions({ line: position.line, col: position.character }).forEach((x: any) => {
|
||||
completionItems.push(this._getCompletionItem(x));
|
||||
});
|
||||
|
||||
return completionItems;
|
||||
}
|
||||
|
||||
private _getCompletionItem(xqLintCompletionItem: any): CompletionItem {
|
||||
const completionItem = new CompletionItem(xqLintCompletionItem.name);
|
||||
completionItem.insertText = xqLintCompletionItem.value;
|
||||
|
||||
switch (xqLintCompletionItem.meta) {
|
||||
// functions (always qualified with a colon)
|
||||
case "function":
|
||||
completionItem.kind = CompletionItemKind.Function;
|
||||
|
||||
const funcStart = (xqLintCompletionItem.value.indexOf(":") + 1);
|
||||
const funcEnd = xqLintCompletionItem.value.indexOf("(");
|
||||
|
||||
completionItem.insertText = xqLintCompletionItem.value.substring(funcStart, funcEnd);
|
||||
break;
|
||||
|
||||
// variables and parameters (always qualified with a dollar sign)
|
||||
case "Let binding":
|
||||
case "Local variable":
|
||||
case "Window variable":
|
||||
case "Function parameter":
|
||||
completionItem.kind = CompletionItemKind.Variable;
|
||||
completionItem.insertText = xqLintCompletionItem.value.substring(1);
|
||||
break;
|
||||
|
||||
// everything else
|
||||
default:
|
||||
completionItem.kind = CompletionItemKind.Text;
|
||||
break;
|
||||
}
|
||||
|
||||
return completionItem;
|
||||
}
|
||||
|
||||
}
|
||||
43
src/constants.ts
Normal file
43
src/constants.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
export namespace commands {
|
||||
export const evaluateXPath = "xmlTools.evaluateXPath";
|
||||
export const executeXQuery = "xmlTools.executeXQuery";
|
||||
export const formatAsXml = "xmlTools.formatAsXml";
|
||||
export const minifyXml = "xmlTools.minifyXml";
|
||||
}
|
||||
|
||||
export namespace contextKeys {
|
||||
export const xmlTreeViewEnabled = "xmlTreeViewEnabled";
|
||||
}
|
||||
|
||||
export namespace diagnosticCollections {
|
||||
export const xquery = "XQueryDiagnostics";
|
||||
}
|
||||
|
||||
export namespace languageIds {
|
||||
export const xml = "xml";
|
||||
export const xsd = "xsd";
|
||||
export const xquery = "xquery";
|
||||
}
|
||||
|
||||
export namespace nativeCommands {
|
||||
export const revealLine = "revealLine";
|
||||
}
|
||||
|
||||
export namespace stateKeys {
|
||||
export const xpathQueryHistory = "xpathQueryHistory";
|
||||
export const xPathQueryLast = "xPathQueryLast";
|
||||
}
|
||||
|
||||
export namespace uriSchemes {
|
||||
export const file = "file";
|
||||
export const untitled = "untitled";
|
||||
}
|
||||
|
||||
export namespace views {
|
||||
export const xmlTreeView = "xmlTreeView";
|
||||
}
|
||||
|
||||
export namespace xmlFormatterImplementations {
|
||||
export const classic = "classic";
|
||||
export const v2 = "v2";
|
||||
}
|
||||
100
src/extension.ts
Normal file
100
src/extension.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
commands, languages, window, workspace, ExtensionContext, Memento,
|
||||
TextEditor, TextEditorSelectionChangeEvent, TextEditorSelectionChangeKind
|
||||
} from "vscode";
|
||||
|
||||
import { createDocumentSelector, ExtensionState, Configuration } from "./common";
|
||||
import { XQueryCompletionItemProvider } from "./completion";
|
||||
import { XmlFormatterFactory, XmlFormattingEditProvider } from "./formatting";
|
||||
import { formatAsXml, minifyXml } from "./formatting/commands";
|
||||
import { XQueryLinter } from "./linting";
|
||||
import { XmlTreeDataProvider } from "./tree-view";
|
||||
import { evaluateXPath } from "./xpath/commands";
|
||||
import { executeXQuery } from "./xquery-execution/commands";
|
||||
|
||||
import * as constants from "./constants";
|
||||
|
||||
export function activate(context: ExtensionContext) {
|
||||
ExtensionState.configure(context);
|
||||
|
||||
const xmlXsdDocSelector = [...createDocumentSelector(constants.languageIds.xml), ...createDocumentSelector(constants.languageIds.xsd)];
|
||||
const xqueryDocSelector = createDocumentSelector(constants.languageIds.xquery);
|
||||
|
||||
/* Completion Features */
|
||||
context.subscriptions.push(
|
||||
languages.registerCompletionItemProvider(xqueryDocSelector, new XQueryCompletionItemProvider(), ":", "$")
|
||||
);
|
||||
|
||||
/* Formatting Features */
|
||||
const xmlFormattingEditProvider = new XmlFormattingEditProvider(XmlFormatterFactory.getXmlFormatter());
|
||||
|
||||
context.subscriptions.push(
|
||||
commands.registerTextEditorCommand(constants.commands.formatAsXml, formatAsXml),
|
||||
commands.registerTextEditorCommand(constants.commands.minifyXml, minifyXml),
|
||||
languages.registerDocumentFormattingEditProvider(xmlXsdDocSelector, xmlFormattingEditProvider),
|
||||
languages.registerDocumentRangeFormattingEditProvider(xmlXsdDocSelector, xmlFormattingEditProvider)
|
||||
);
|
||||
|
||||
/* Linting Features */
|
||||
context.subscriptions.push(
|
||||
window.onDidChangeActiveTextEditor(_handleChangeActiveTextEditor),
|
||||
window.onDidChangeTextEditorSelection(_handleChangeTextEditorSelection)
|
||||
);
|
||||
|
||||
/* Tree View Features */
|
||||
const treeViewDataProvider = new XmlTreeDataProvider(context);
|
||||
const treeView = window.createTreeView<Node>(constants.views.xmlTreeView, {
|
||||
treeDataProvider: treeViewDataProvider
|
||||
});
|
||||
|
||||
if (Configuration.enableXmlTreeViewCursorSync) {
|
||||
window.onDidChangeTextEditorSelection(x => {
|
||||
if (x.kind === TextEditorSelectionChangeKind.Mouse && x.selections.length > 0) {
|
||||
treeView.reveal(treeViewDataProvider.getNodeAtPosition(x.selections[0].start));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
context.subscriptions.push(
|
||||
treeView
|
||||
);
|
||||
|
||||
/* XPath Features */
|
||||
context.subscriptions.push(
|
||||
commands.registerTextEditorCommand(constants.commands.evaluateXPath, evaluateXPath)
|
||||
);
|
||||
|
||||
/* XQuery Features */
|
||||
context.subscriptions.push(
|
||||
commands.registerTextEditorCommand(constants.commands.executeXQuery, executeXQuery)
|
||||
);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
|
||||
function _handleContextChange(editor: TextEditor): void {
|
||||
const supportedSchemes = [constants.uriSchemes.file, constants.uriSchemes.untitled];
|
||||
|
||||
if (!editor || !editor.document || supportedSchemes.indexOf(editor.document.uri.scheme) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (editor.document.languageId) {
|
||||
case constants.languageIds.xquery:
|
||||
languages
|
||||
.createDiagnosticCollection(constants.diagnosticCollections.xquery)
|
||||
.set(editor.document.uri, new XQueryLinter().lint(editor.document.getText()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function _handleChangeActiveTextEditor(editor: TextEditor): void {
|
||||
_handleContextChange(editor);
|
||||
}
|
||||
|
||||
function _handleChangeTextEditorSelection(e: TextEditorSelectionChangeEvent): void {
|
||||
_handleContextChange(e.textEditor);
|
||||
}
|
||||
46
src/formatting/commands/formatAsXml.ts
Normal file
46
src/formatting/commands/formatAsXml.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { workspace } from "vscode";
|
||||
import { ProviderResult, Range, TextEdit, TextEditor, TextEditorEdit } from "vscode";
|
||||
|
||||
import { NativeCommands } from "../../common";
|
||||
import * as constants from "../../constants";
|
||||
|
||||
import { XmlFormatterFactory } from "../xml-formatter";
|
||||
import { XmlFormattingEditProvider } from "../xml-formatting-edit-provider";
|
||||
import { XmlFormattingOptionsFactory } from "../xml-formatting-options";
|
||||
|
||||
export function formatAsXml(editor: TextEditor, edit: TextEditorEdit): void {
|
||||
const xmlFormattingEditProvider = new XmlFormattingEditProvider(XmlFormatterFactory.getXmlFormatter());
|
||||
const formattingOptions = {
|
||||
insertSpaces: <boolean>editor.options.insertSpaces,
|
||||
tabSize: <number>editor.options.tabSize
|
||||
};
|
||||
|
||||
let edits: ProviderResult<TextEdit[]>;
|
||||
|
||||
if (!editor.selection.isEmpty) {
|
||||
edits = xmlFormattingEditProvider.provideDocumentRangeFormattingEdits(
|
||||
editor.document,
|
||||
new Range(editor.selection.start, editor.selection.end),
|
||||
formattingOptions,
|
||||
null);
|
||||
}
|
||||
|
||||
else {
|
||||
edits = xmlFormattingEditProvider.provideDocumentFormattingEdits(
|
||||
editor.document,
|
||||
formattingOptions,
|
||||
null);
|
||||
}
|
||||
|
||||
for (let i = 0; i < (edits as TextEdit[]).length; i++) {
|
||||
const textEdit = (edits as TextEdit[])[i];
|
||||
|
||||
editor.edit(async (editBuilder) => {
|
||||
editBuilder.replace(textEdit.range, textEdit.newText);
|
||||
|
||||
// wiggle the cursor to deselect the formatted XML (is there a non-hacky way to go about this?)
|
||||
await NativeCommands.cursorMove("left", "character");
|
||||
await NativeCommands.cursorMove("right", "character");
|
||||
});
|
||||
}
|
||||
}
|
||||
2
src/formatting/commands/index.ts
Normal file
2
src/formatting/commands/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./formatAsXml";
|
||||
export * from "./minifyXml";
|
||||
21
src/formatting/commands/minifyXml.ts
Normal file
21
src/formatting/commands/minifyXml.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { workspace } from "vscode";
|
||||
import { ProviderResult, Range, TextEdit, TextEditor, TextEditorEdit } from "vscode";
|
||||
|
||||
import * as constants from "../../constants";
|
||||
|
||||
import { XmlFormatterFactory } from "../xml-formatter";
|
||||
import { XmlFormattingEditProvider } from "../xml-formatting-edit-provider";
|
||||
import { XmlFormattingOptionsFactory } from "../xml-formatting-options";
|
||||
|
||||
export function minifyXml(editor: TextEditor, edit: TextEditorEdit): void {
|
||||
const xmlFormatter = XmlFormatterFactory.getXmlFormatter();
|
||||
const xmlFormattingOptions = XmlFormattingOptionsFactory.getXmlFormattingOptions({
|
||||
insertSpaces: <boolean>editor.options.insertSpaces,
|
||||
tabSize: <number>editor.options.tabSize
|
||||
}, editor.document);
|
||||
|
||||
const endPosition = editor.document.lineAt(editor.document.lineCount - 1).rangeIncludingLineBreak.end;
|
||||
const range = new Range(editor.document.positionAt(0), endPosition);
|
||||
|
||||
edit.replace(range, xmlFormatter.minifyXml(editor.document.getText(), xmlFormattingOptions));
|
||||
}
|
||||
123
src/formatting/formatters/classic-xml-formatter.ts
Normal file
123
src/formatting/formatters/classic-xml-formatter.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { XmlFormatter } from "../xml-formatter";
|
||||
import { XmlFormattingOptions } from "../xml-formatting-options";
|
||||
|
||||
export class ClassicXmlFormatter implements XmlFormatter {
|
||||
|
||||
formatXml(xml: string, options: XmlFormattingOptions): string {
|
||||
xml = this.minifyXml(xml, options);
|
||||
xml = xml.replace(/</g, "~::~<");
|
||||
|
||||
if (options.splitXmlnsOnFormat) {
|
||||
xml = xml
|
||||
.replace(/xmlns\:/g, "~::~xmlns:")
|
||||
.replace(/xmlns\=/g, "~::~xmlns=");
|
||||
}
|
||||
|
||||
const parts: string[] = xml.split("~::~");
|
||||
let inComment = false;
|
||||
let level = 0;
|
||||
let output = "";
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
// <!
|
||||
if (parts[i].search(/<!/) > -1) {
|
||||
output += this._getIndent(options, level, parts[i]);
|
||||
inComment = true;
|
||||
|
||||
// end <!
|
||||
if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1 || parts[i].search(/!DOCTYPE/) > -1) {
|
||||
inComment = false;
|
||||
}
|
||||
} else if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1) {
|
||||
output += parts[i];
|
||||
inComment = false;
|
||||
} else if (/^<(\w|:)/.test(parts[i - 1]) && /^<\/(\w|:)/.test(parts[i])
|
||||
&& /^<[\w:\-\.\,\/]+/.exec(parts[i - 1])[0] === /^<\/[\w:\-\.\,]+/.exec(parts[i])[0].replace("/", "")) {
|
||||
|
||||
output += parts[i];
|
||||
if (!inComment) { level--; }
|
||||
} else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) === -1 && parts[i].search(/\/>/) === -1) {
|
||||
output = (!inComment) ? output += this._getIndent(options, level++, parts[i]) : output += parts[i];
|
||||
} else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) > -1) {
|
||||
output = (!inComment) ? output += this._getIndent(options, level, parts[i]) : output += parts[i];
|
||||
} else if (parts[i].search(/<\//) > -1) {
|
||||
output = (!inComment) ? output += this._getIndent(options, --level, parts[i]) : output += parts[i];
|
||||
} else if (parts[i].search(/\/>/) > -1 && (!options.splitXmlnsOnFormat || parts[i].search(/xmlns(:|=)/) === -1)) {
|
||||
output = (!inComment) ? output += this._getIndent(options, level, parts[i]) : output += parts[i];
|
||||
} else if (parts[i].search(/\/>/) > -1 && parts[i].search(/xmlns(:|=)/) > -1 && options.splitXmlnsOnFormat) {
|
||||
output = (!inComment) ? output += this._getIndent(options, level--, parts[i]) : output += parts[i];
|
||||
} else if (parts[i].search(/<\?/) > -1) {
|
||||
output += this._getIndent(options, level, parts[i]);
|
||||
} else if (options.splitXmlnsOnFormat && (parts[i].search(/xmlns\:/) > -1 || parts[i].search(/xmlns\=/) > -1)) {
|
||||
output += this._getIndent(options, level, parts[i]);
|
||||
} else {
|
||||
output += parts[i];
|
||||
}
|
||||
}
|
||||
|
||||
// remove leading newline
|
||||
if (output[0] === options.newLine) {
|
||||
output = output.slice(1);
|
||||
} else if (output.substring(0, 1) === options.newLine) {
|
||||
output = output.slice(2);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
minifyXml(xml: string, options: XmlFormattingOptions): string {
|
||||
xml = this._stripLineBreaks(options, xml); // all line breaks outside of CDATA elements
|
||||
xml = (options.removeCommentsOnMinify) ? xml.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, "") : xml;
|
||||
xml = xml.replace(/>\s{0,}</g, "><"); // insignificant whitespace between tags
|
||||
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
|
||||
xml = xml.replace(/"\s+(?=>)/g, "\""); // spaces between the last attribute and tag close (>)
|
||||
xml = xml.replace(/"\s+(?=\/>)/g, "\" "); // spaces between the last attribute and tag close (/>)
|
||||
xml = xml.replace(/[^ <>="]\s+[^ <>="]+=/g, (match: string) => { // spaces between the node name and the first attribute
|
||||
return match.replace(/\s+/g, " ");
|
||||
});
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
private _getIndent(options: XmlFormattingOptions, level: number, trailingValue?: string): string {
|
||||
trailingValue = trailingValue || "";
|
||||
|
||||
const indentPattern = (options.editorOptions.preferSpaces) ? " ".repeat(options.editorOptions.tabSize) : "\t";
|
||||
|
||||
return `${options.newLine}${indentPattern.repeat(level)}${trailingValue}`;
|
||||
}
|
||||
|
||||
private _stripLineBreaks(options: XmlFormattingOptions, xml: string): string {
|
||||
let output = "";
|
||||
const inTag = false;
|
||||
const inTagName = false;
|
||||
let inCdata = false;
|
||||
const inAttribute = false;
|
||||
|
||||
for (let i = 0; i < xml.length; i++) {
|
||||
const char: string = xml.charAt(i);
|
||||
const prev: string = xml.charAt(i - 1);
|
||||
const next: string = xml.charAt(i + 1);
|
||||
|
||||
if (char === "!" && (xml.substr(i, 8) === "![CDATA[" || xml.substr(i, 3) === "!--")) {
|
||||
inCdata = true;
|
||||
} else if (char === "]" && (xml.substr(i, 3) === "]]>")) {
|
||||
inCdata = false;
|
||||
} else if (char === "-" && (xml.substr(i, 3) === "-->")) {
|
||||
inCdata = false;
|
||||
} else if (char.search(/[\r\n]/g) > -1 && !inCdata) {
|
||||
if (/\r/.test(char) && /\S|\r|\n/.test(prev) && /\S|\r|\n/.test(xml.charAt(i + options.newLine.length))) {
|
||||
output += char;
|
||||
} else if (/\n/.test(char) && /\S|\r|\n/.test(xml.charAt(i - options.newLine.length)) && /\S|\r|\n/.test(next)) {
|
||||
output += char;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
output += char;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
2
src/formatting/formatters/index.ts
Normal file
2
src/formatting/formatters/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./classic-xml-formatter";
|
||||
export * from "./v2-xml-formatter";
|
||||
254
src/formatting/formatters/v2-xml-formatter.ts
Normal file
254
src/formatting/formatters/v2-xml-formatter.ts
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
import { XmlFormatter } from "../xml-formatter";
|
||||
import { XmlFormattingOptions } from "../xml-formatting-options";
|
||||
import { ClassicXmlFormatter } from "./classic-xml-formatter";
|
||||
|
||||
const MagicalStringOfWonders = "~::~MAAAGIC~::~";
|
||||
|
||||
/* tslint:disable no-use-before-declare */
|
||||
export class V2XmlFormatter implements XmlFormatter {
|
||||
formatXml(xml: string, options: XmlFormattingOptions): string {
|
||||
// this replaces all "<" brackets inside of comments to a magical string
|
||||
// so the following minification steps don't mess with comment formatting
|
||||
xml = this._sanitizeComments(xml);
|
||||
|
||||
// remove whitespace from between tags, except for line breaks
|
||||
xml = xml.replace(/>\s{0,}</g, (match: string) => {
|
||||
return match.replace(/[^\S\r\n]/g, "");
|
||||
});
|
||||
|
||||
// do some light minification to get rid of insignificant whitespace
|
||||
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
|
||||
xml = xml.replace(/"\s+(?=>)/g, "\""); // spaces between the last attribute and tag close (>)
|
||||
xml = xml.replace(/"\s+(?=\/>)/g, "\" "); // spaces between the last attribute and tag close (/>)
|
||||
xml = xml.replace(/[^ <>="]\s+[^ <>="]+=/g, (match: string) => { // spaces between the node name and the first attribute
|
||||
return match.replace(/\s+/g, " ");
|
||||
});
|
||||
|
||||
// the coast is clear - we can drop those "<" brackets back in
|
||||
xml = this._unsanitizeComments(xml);
|
||||
|
||||
let output = "";
|
||||
|
||||
let indentLevel = 0;
|
||||
let location = Location.Text;
|
||||
let lastNonTextLocation = Location.Text; // hah
|
||||
|
||||
// NOTE: all "exiting" checks should appear after their associated "entering" checks
|
||||
for (let i = 0; i < xml.length; i++) {
|
||||
const cc = xml[i];
|
||||
const nc = xml.charAt(i + 1);
|
||||
const nnc = xml.charAt(i + 2);
|
||||
const pc = xml.charAt(i - 1);
|
||||
|
||||
// entering CData
|
||||
if (location === Location.Text && cc === "<" && nc === "!" && nnc === "[") {
|
||||
output += `${this._getIndent(options, indentLevel)}<`;
|
||||
location = Location.CData;
|
||||
}
|
||||
|
||||
// exiting CData
|
||||
else if (location === Location.CData && cc === "]" && nc === "]" && nnc === ">") {
|
||||
output += "]]>";
|
||||
|
||||
i += 2;
|
||||
lastNonTextLocation = location;
|
||||
location = Location.Text;
|
||||
}
|
||||
|
||||
// entering Comment
|
||||
else if (location === Location.Text && cc === "<" && nc === "!" && nnc === "-") {
|
||||
output += `${this._getIndent(options, indentLevel)}<`;
|
||||
location = Location.Comment;
|
||||
}
|
||||
|
||||
// exiting Comment
|
||||
else if (location === Location.Comment && cc === "-" && nc === "-" && nnc === ">") {
|
||||
output += "-->";
|
||||
|
||||
i += 2;
|
||||
lastNonTextLocation = location;
|
||||
location = Location.Text;
|
||||
}
|
||||
|
||||
// entering SpecialTag
|
||||
else if (location === Location.Text && cc === "<" && (nc === "!" || nc === "?")) {
|
||||
output += `${this._getIndent(options, indentLevel)}<`;
|
||||
location = Location.SpecialTag;
|
||||
}
|
||||
|
||||
// exiting SpecialTag
|
||||
else if (location === Location.SpecialTag && cc === ">") {
|
||||
output += `>`;
|
||||
lastNonTextLocation = location;
|
||||
location = Location.Text;
|
||||
}
|
||||
|
||||
// entering StartTag.StartTagName
|
||||
else if (location === Location.Text && cc === "<" && ["/", "!"].indexOf(nc) === -1) {
|
||||
// if this occurs after another tag, prepend a line break
|
||||
if (pc === ">") {
|
||||
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
|
||||
}
|
||||
|
||||
else {
|
||||
output += `${this._getIndent(options, indentLevel)}<`;
|
||||
}
|
||||
|
||||
indentLevel++;
|
||||
location = Location.StartTagName;
|
||||
}
|
||||
|
||||
// exiting StartTag.StartTagName, enter StartTag
|
||||
else if (location === Location.StartTagName && cc === " ") {
|
||||
output += " ";
|
||||
lastNonTextLocation = location;
|
||||
location = Location.StartTag;
|
||||
}
|
||||
|
||||
// entering StartTag.Attribute
|
||||
else if (location === Location.StartTag && [" ", "/", ">"].indexOf(cc) === -1) {
|
||||
if (lastNonTextLocation === Location.AttributeValue
|
||||
&& ((options.splitXmlnsOnFormat
|
||||
&& xml.substr(i, 5).toLowerCase() === "xmlns")
|
||||
|| options.splitAttributesOnFormat)) {
|
||||
output += `${options.newLine}${this._getIndent(options, indentLevel)}`;
|
||||
}
|
||||
|
||||
output += cc;
|
||||
lastNonTextLocation = location;
|
||||
location = Location.Attribute;
|
||||
}
|
||||
|
||||
// entering StartTag.Attribute.AttributeValue
|
||||
else if (location === Location.Attribute && cc === "\"") {
|
||||
output += "\"";
|
||||
lastNonTextLocation = location;
|
||||
location = Location.AttributeValue;
|
||||
}
|
||||
|
||||
// exiting StartTag.Attribute.AttributeValue, entering StartTag
|
||||
else if (location === Location.AttributeValue && cc === "\"") {
|
||||
output += "\"";
|
||||
lastNonTextLocation = location;
|
||||
location = Location.StartTag;
|
||||
}
|
||||
|
||||
// exiting StartTag or StartTag.StartTagName, entering Text
|
||||
else if ((location === Location.StartTag || location === Location.StartTagName) && cc === ">") {
|
||||
// if this was a self-closing tag, we need to decrement the indent level and add a newLine
|
||||
if (pc === "/") {
|
||||
indentLevel--;
|
||||
output += ">";
|
||||
|
||||
// only add a newline here if one doesn't already exist (issue #147)
|
||||
if (nc !== "\r" && nc !== "\n") {
|
||||
output += options.newLine;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
output += ">";
|
||||
}
|
||||
|
||||
lastNonTextLocation = location;
|
||||
location = Location.Text;
|
||||
}
|
||||
|
||||
// entering EndTag
|
||||
else if (location === Location.Text && cc === "<" && nc === "/") {
|
||||
indentLevel--;
|
||||
|
||||
// if the end tag immediately follows a line break, just add an indentation
|
||||
// if the end tag immediately follows another end tag, add a line break and indent
|
||||
// otherwise, this should be treated as a same-line end tag(ex. <element>text</element>)
|
||||
if (pc === "\n") {
|
||||
output += `${this._getIndent(options, indentLevel)}<`;
|
||||
}
|
||||
|
||||
else if (lastNonTextLocation === Location.EndTag) {
|
||||
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
|
||||
}
|
||||
|
||||
else {
|
||||
output += "<";
|
||||
}
|
||||
|
||||
location = Location.EndTag;
|
||||
}
|
||||
|
||||
// exiting EndTag, entering Text
|
||||
else if (location === Location.EndTag && cc === ">") {
|
||||
output += ">";
|
||||
lastNonTextLocation = location;
|
||||
location = Location.Text;
|
||||
}
|
||||
|
||||
// Text
|
||||
else {
|
||||
output += cc;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
minifyXml(xml: string, options: XmlFormattingOptions): string {
|
||||
return new ClassicXmlFormatter().minifyXml(xml, options);
|
||||
}
|
||||
|
||||
private _getIndent(options: XmlFormattingOptions, indentLevel: number): string {
|
||||
return ((options.editorOptions.insertSpaces) ? " ".repeat(options.editorOptions.tabSize) : "\t").repeat(indentLevel);
|
||||
}
|
||||
|
||||
private _sanitizeComments(xml: string): string {
|
||||
let output = "";
|
||||
let inComment = false;
|
||||
|
||||
for (let i = 0; i < xml.length; i++) {
|
||||
const cc = xml[i];
|
||||
const nc = xml.charAt(i + 1);
|
||||
const nnc = xml.charAt(i + 2);
|
||||
const pc = xml.charAt(i - 1);
|
||||
|
||||
if (!inComment && cc === "<" && nc === "!" && nnc === "-") {
|
||||
inComment = true;
|
||||
output += "<!--";
|
||||
|
||||
i += 3;
|
||||
}
|
||||
|
||||
else if (inComment && cc === "<") {
|
||||
output += MagicalStringOfWonders;
|
||||
}
|
||||
|
||||
else if (inComment && cc === "-" && nc === "-" && nnc === ">") {
|
||||
inComment = false;
|
||||
output += "-->";
|
||||
|
||||
i += 2;
|
||||
}
|
||||
|
||||
else {
|
||||
output += cc;
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private _unsanitizeComments(xml: string): string {
|
||||
return xml.replace(new RegExp(MagicalStringOfWonders, "g"), "<");
|
||||
}
|
||||
}
|
||||
|
||||
enum Location {
|
||||
Attribute,
|
||||
AttributeValue,
|
||||
CData,
|
||||
Comment,
|
||||
EndTag,
|
||||
SpecialTag,
|
||||
StartTag,
|
||||
StartTagName,
|
||||
Text
|
||||
}
|
||||
3
src/formatting/index.ts
Normal file
3
src/formatting/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from "./xml-formatter";
|
||||
export * from "./xml-formatting-edit-provider";
|
||||
export * from "./xml-formatting-options";
|
||||
45
src/formatting/xml-formatter.ts
Normal file
45
src/formatting/xml-formatter.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { window, workspace } from "vscode";
|
||||
|
||||
import { Configuration, ExtensionState } from "../common";
|
||||
import * as constants from "../constants";
|
||||
import { ClassicXmlFormatter } from "./formatters/classic-xml-formatter";
|
||||
import { V2XmlFormatter } from "./formatters/v2-xml-formatter";
|
||||
|
||||
import { XmlFormattingOptions } from "./xml-formatting-options";
|
||||
|
||||
export interface XmlFormatter {
|
||||
formatXml(xml: string, options: XmlFormattingOptions): string;
|
||||
minifyXml(xml: string, options: XmlFormattingOptions): string;
|
||||
}
|
||||
|
||||
export class XmlFormatterFactory {
|
||||
private static _xmlFormatter: XmlFormatter;
|
||||
|
||||
static getXmlFormatter(): XmlFormatter {
|
||||
if (XmlFormatterFactory._xmlFormatter) {
|
||||
return XmlFormatterFactory._xmlFormatter;
|
||||
}
|
||||
|
||||
const xmlFormatterImplementationSetting = Configuration.xmlFormatterImplementation;
|
||||
let xmlFormatterImplementation: XmlFormatter;
|
||||
|
||||
switch (xmlFormatterImplementationSetting) {
|
||||
case constants.xmlFormatterImplementations.classic: xmlFormatterImplementation = new ClassicXmlFormatter(); break;
|
||||
case constants.xmlFormatterImplementations.v2:
|
||||
default: xmlFormatterImplementation = new V2XmlFormatter(); break;
|
||||
}
|
||||
|
||||
// warn users about the new formatter
|
||||
const formatterWarningKey = "xmlTools.v2FormatterWarning.shown";
|
||||
|
||||
if (!ExtensionState.global.get<boolean>(formatterWarningKey) && xmlFormatterImplementation instanceof V2XmlFormatter) {
|
||||
// tslint:disable-next-line:max-line-length
|
||||
window.showInformationMessage("Heads up! We've rewritten the XML formatter. If you liked the old one better, it's still there. Just set the 'xmlTools.xmlFormatterImplementation' setting to 'classic'.")
|
||||
.then(() => {
|
||||
ExtensionState.global.update(formatterWarningKey, true);
|
||||
});
|
||||
}
|
||||
|
||||
return (XmlFormatterFactory._xmlFormatter = xmlFormatterImplementation);
|
||||
}
|
||||
}
|
||||
31
src/formatting/xml-formatting-edit-provider.ts
Normal file
31
src/formatting/xml-formatting-edit-provider.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { workspace } from "vscode";
|
||||
import {
|
||||
CancellationToken, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, EndOfLine,
|
||||
FormattingOptions, ProviderResult, Range, TextDocument, TextEdit
|
||||
} from "vscode";
|
||||
|
||||
import * as constants from "../constants";
|
||||
import { XmlFormatter } from "./xml-formatter";
|
||||
import { XmlFormattingOptionsFactory } from "./xml-formatting-options";
|
||||
|
||||
export class XmlFormattingEditProvider implements DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider {
|
||||
|
||||
constructor(
|
||||
public xmlFormatter: XmlFormatter
|
||||
) { }
|
||||
|
||||
provideDocumentFormattingEdits(document: TextDocument, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
|
||||
const lastLine = document.lineAt(document.lineCount - 1);
|
||||
const documentRange = new Range(document.positionAt(0), lastLine.range.end);
|
||||
|
||||
return this.provideDocumentRangeFormattingEdits(document, documentRange, options, token);
|
||||
}
|
||||
|
||||
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
|
||||
let xml = document.getText(range);
|
||||
|
||||
xml = this.xmlFormatter.formatXml(xml, XmlFormattingOptionsFactory.getXmlFormattingOptions(options, document));
|
||||
|
||||
return [ TextEdit.replace(range, xml) ];
|
||||
}
|
||||
}
|
||||
24
src/formatting/xml-formatting-options.ts
Normal file
24
src/formatting/xml-formatting-options.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { EndOfLine, FormattingOptions, TextDocument } from "vscode";
|
||||
|
||||
import { Configuration } from "../common";
|
||||
import * as constants from "../constants";
|
||||
|
||||
export interface XmlFormattingOptions {
|
||||
editorOptions: FormattingOptions;
|
||||
newLine: string;
|
||||
removeCommentsOnMinify: boolean;
|
||||
splitAttributesOnFormat: boolean;
|
||||
splitXmlnsOnFormat: boolean;
|
||||
}
|
||||
|
||||
export class XmlFormattingOptionsFactory {
|
||||
static getXmlFormattingOptions(formattingOptions: FormattingOptions, document: TextDocument): XmlFormattingOptions {
|
||||
return {
|
||||
editorOptions: formattingOptions,
|
||||
newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n",
|
||||
removeCommentsOnMinify: Configuration.removeCommentsOnMinify(document.uri),
|
||||
splitAttributesOnFormat: Configuration.splitAttributesOnFormat(document.uri),
|
||||
splitXmlnsOnFormat: Configuration.splitXmlnsOnFormat(document.uri)
|
||||
};
|
||||
}
|
||||
}
|
||||
1
src/linting/index.ts
Normal file
1
src/linting/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./xquery-linter";
|
||||
37
src/linting/xquery-linter.ts
Normal file
37
src/linting/xquery-linter.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { Diagnostic, DiagnosticSeverity, Position, Range } from "vscode";
|
||||
|
||||
const XQLint = require("xqlint").XQLint;
|
||||
|
||||
export class XQueryLinter {
|
||||
static SEVERITY_WARNING = 1;
|
||||
static SEVERITY_ERROR = 2;
|
||||
|
||||
lint(text: string): Diagnostic[] {
|
||||
const linter = new XQLint(text);
|
||||
const diagnostics = new Array<Diagnostic>();
|
||||
|
||||
linter.getErrors().forEach((error: any) => {
|
||||
diagnostics.push(new Diagnostic(
|
||||
new Range(
|
||||
new Position(error.pos.sl, error.pos.sc),
|
||||
new Position(error.pos.el, error.pos.ec)
|
||||
),
|
||||
error.message,
|
||||
DiagnosticSeverity.Error
|
||||
));
|
||||
});
|
||||
|
||||
linter.getWarnings().forEach((warning: any) => {
|
||||
diagnostics.push(new Diagnostic(
|
||||
new Range(
|
||||
new Position(warning.pos.sl, warning.pos.sc),
|
||||
new Position(warning.pos.el, warning.pos.ec)
|
||||
),
|
||||
warning.message,
|
||||
DiagnosticSeverity.Warning
|
||||
));
|
||||
});
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import { XQueryCompleter, XQueryCompletionItem } from "../services/XQueryCompleter";
|
||||
|
||||
export class XQueryCompletionItemProvider implements vsc.CompletionItemProvider {
|
||||
provideCompletionItems(document: vsc.TextDocument, position: vsc.Position): vsc.CompletionItem[] {
|
||||
let items: vsc.CompletionItem[] = new Array<vsc.CompletionItem>();
|
||||
|
||||
let completer: XQueryCompleter = new XQueryCompleter(document.getText());
|
||||
let completions: XQueryCompletionItem[] = completer.getCompletions(position.line, position.character);
|
||||
|
||||
completions.forEach((completion: XQueryCompletionItem) => {
|
||||
let item: vsc.CompletionItem = new vsc.CompletionItem(completion.name);
|
||||
item.insertText = completion.value;
|
||||
|
||||
switch (completion.meta) {
|
||||
// functions (always qualified with a colon)
|
||||
case "function":
|
||||
item.kind = vsc.CompletionItemKind.Function;
|
||||
|
||||
let funcStart = (completion.value.indexOf(":") + 1);
|
||||
let funcEnd = completion.value.indexOf("(");
|
||||
|
||||
item.insertText = completion.value.substring(funcStart, funcEnd);
|
||||
break;
|
||||
|
||||
// variables and parameters (always qualified with a dollar sign)
|
||||
case "Let binding":
|
||||
case "Local variable":
|
||||
case "Window variable":
|
||||
case "Function parameter":
|
||||
item.kind = vsc.CompletionItemKind.Variable;
|
||||
item.insertText = completion.value.substring(1);
|
||||
break;
|
||||
|
||||
// everything else
|
||||
default: item.kind = vsc.CompletionItemKind.Text;
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import { ChildProcess } from "../services/ChildProcess";
|
||||
|
||||
const CFG_SECTION: string = "xmlTools";
|
||||
const CFG_XQEXEC: string = "xqueryExecutionEngine";
|
||||
const CFG_XQARGS: string = "xqueryExecutionArguments";
|
||||
|
||||
export class XQueryExecutionProvider {
|
||||
static async executeXQueryAsync(editor: vsc.TextEditor): Promise<void> {
|
||||
// this disposable will be used for creating status bar messages
|
||||
let disposable: vsc.Disposable;
|
||||
|
||||
if (editor.document.languageId !== "xquery") {
|
||||
vsc.window.showErrorMessage("This action can only be performed on an XQuery file.");
|
||||
return;
|
||||
}
|
||||
|
||||
let executable = vsc.workspace.getConfiguration(CFG_SECTION).get<string>(CFG_XQEXEC, null);
|
||||
let args = vsc.workspace.getConfiguration(CFG_SECTION).get<string[]>(CFG_XQARGS, []);
|
||||
|
||||
if (!executable || executable == "") {
|
||||
let action = await vsc.window.showWarningMessage("An XQuery execution engine has not been defined.", "Define Now");
|
||||
|
||||
if (action == "Define Now") {
|
||||
vsc.commands.executeCommand("workbench.action.openGlobalSettings");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let inputFile: vsc.Uri;
|
||||
disposable = vsc.window.setStatusBarMessage("Searching for XML files in folder...");
|
||||
let files: vsc.Uri[] = await vsc.workspace.findFiles("**/*.xml", "", 100);
|
||||
disposable.dispose();
|
||||
|
||||
// user does not have a folder open - prompt for file name
|
||||
if (typeof files === "undefined") {
|
||||
vsc.window.showErrorMessage("You must have a folder opened in VS Code to use this feature.");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is only one XML file, default it
|
||||
// otherwise, prompt the user to select one from the open folder
|
||||
if (files.length > 1) {
|
||||
let qpItems: any[] = new Array<any>();
|
||||
|
||||
files.forEach((file) => {
|
||||
let filename: string = file.fsPath.replace("\\", "/");
|
||||
|
||||
qpItems.push({ // must implement vscode.QuickPickItem
|
||||
label: filename.substring(filename.lastIndexOf("/") + 1),
|
||||
description: file.fsPath,
|
||||
file: file
|
||||
});
|
||||
});
|
||||
|
||||
let selection = await vsc.window.showQuickPick(qpItems, { placeHolder: "Please select an input file." });
|
||||
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputFile = selection.file;
|
||||
}
|
||||
|
||||
else {
|
||||
inputFile = files[0];
|
||||
}
|
||||
|
||||
// prompt for output file name
|
||||
let outputPath: string = null;
|
||||
let outputPathPos: number = -1;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (i > 0) {
|
||||
if (args[i - 1].search(/out|result/)) {
|
||||
outputPath = args[i];
|
||||
outputPathPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outputPath) {
|
||||
outputPath = await vsc.window.showInputBox({
|
||||
placeHolder: "ex. C:\\TEMP\XQueryOutput\\MyOutputFile.xml",
|
||||
prompt: "Please specify the output file path. Existing file behavior is determined by the execution engine you have specified.",
|
||||
value: outputPath
|
||||
});
|
||||
|
||||
args[outputPathPos] = outputPath;
|
||||
}
|
||||
|
||||
// call out to the execution engine
|
||||
disposable = vsc.window.setStatusBarMessage("Executing XQuery Script...");
|
||||
args = args.map<string>((value: string) => {
|
||||
return value
|
||||
.replace("$(script)", editor.document.uri.fsPath)
|
||||
.replace("$(input)", inputFile.fsPath)
|
||||
.replace("$(project)", vsc.workspace.rootPath);
|
||||
});
|
||||
|
||||
try {
|
||||
await ChildProcess.spawnAsync(executable, args);
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
if (error.message.search(/[Ll]ine:?\s*\d+/gm) > -1) {
|
||||
let match: RegExpExecArray = /[Ll]ine:?\s*\d+/gm.exec(error.message);
|
||||
let line: number = (Number.parseInt(match[0].replace(/([Ll]ine:?\s*)|\s/, "")) - 1);
|
||||
|
||||
let selection: string = await vsc.window.showErrorMessage(error.message, `Go to Line ${line}`);
|
||||
|
||||
if (selection == `Go to Line ${line}`) {
|
||||
editor.revealRange(new vsc.Range(line, 0, line, 0));
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
vsc.window.showErrorMessage(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
finally {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import { RangeUtil } from "../utils/RangeUtil";
|
||||
import { XmlFormatter, IXmlFormatterOptions } from "../services/XmlFormatter";
|
||||
|
||||
const CFG_SECTION: string = "xmlTools";
|
||||
const CFG_SPLIT_NAMESPACES: string = "splitXmlnsOnFormat";
|
||||
|
||||
export class XmlFormattingEditProvider implements vsc.DocumentFormattingEditProvider, vsc.DocumentRangeFormattingEditProvider {
|
||||
provideDocumentFormattingEdits(document: vsc.TextDocument, options: vsc.FormattingOptions): vsc.TextEdit[] {
|
||||
let range = RangeUtil.getRangeForDocument(document);
|
||||
|
||||
return this._provideFormattingEdits(document, range, options);
|
||||
}
|
||||
|
||||
provideDocumentRangeFormattingEdits(document: vsc.TextDocument, range: vsc.Range, options: vsc.FormattingOptions): vsc.TextEdit[] {
|
||||
return this._provideFormattingEdits(document, range, options);
|
||||
}
|
||||
|
||||
private _provideFormattingEdits(document: vsc.TextDocument, range: vsc.Range, options: vsc.FormattingOptions): vsc.TextEdit[] {
|
||||
let splitNamespaces: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_SPLIT_NAMESPACES, true);
|
||||
|
||||
let formatterOptions: IXmlFormatterOptions = {
|
||||
preferSpaces: options.insertSpaces,
|
||||
tabSize: options.tabSize,
|
||||
splitNamespaces: splitNamespaces
|
||||
};
|
||||
|
||||
let formatter = new XmlFormatter(formatterOptions);
|
||||
let xml = formatter.format(document.getText(range));
|
||||
|
||||
return [ vsc.TextEdit.replace(range, xml) ];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import { XQueryLinter, XQueryDiagnostic } from "../services/XQueryLinter";
|
||||
|
||||
export class XQueryLintingFeatureProvider {
|
||||
private static _coreDiagnostics: vsc.DiagnosticCollection;
|
||||
|
||||
static get coreDiagnostics(): vsc.DiagnosticCollection {
|
||||
if (!XQueryLintingFeatureProvider._coreDiagnostics) {
|
||||
XQueryLintingFeatureProvider._coreDiagnostics = vsc.languages.createDiagnosticCollection("XQueryDiagnostics");
|
||||
}
|
||||
|
||||
return XQueryLintingFeatureProvider._coreDiagnostics;
|
||||
}
|
||||
|
||||
static provideXQueryDiagnostics(editor: vsc.TextEditor): void {
|
||||
let diagnostics: vsc.Diagnostic[] = new Array<vsc.Diagnostic>();
|
||||
let xqDiagnostics: XQueryDiagnostic[] = XQueryLinter.lint(editor.document.getText());
|
||||
|
||||
xqDiagnostics.forEach((xqd: XQueryDiagnostic) => {
|
||||
let vSeverity: vsc.DiagnosticSeverity = (xqd.severity == 1) ? vsc.DiagnosticSeverity.Warning : vsc.DiagnosticSeverity.Error;
|
||||
|
||||
let startPos: vsc.Position = new vsc.Position(xqd.startLine, xqd.startColumn);
|
||||
let endPos: vsc.Position = new vsc.Position(xqd.endLine, xqd.endColumn);
|
||||
let range: vsc.Range = new vsc.Range(startPos, endPos);
|
||||
let diagnostic: vsc.Diagnostic = new vsc.Diagnostic(range, xqd.message, vSeverity);
|
||||
|
||||
diagnostics.push(diagnostic);
|
||||
});
|
||||
|
||||
XQueryLintingFeatureProvider.coreDiagnostics.set(editor.document.uri, diagnostics);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import * as ext from "../Extension";
|
||||
import { XPathEvaluator, EvaluatorResult, EvaluatorResultType } from "../services/XPathEvaluator";
|
||||
|
||||
const CFG_SECTION: string = "xmlTools";
|
||||
const CFG_PERSIST_QUERY: string = "persistXPathQuery";
|
||||
const CFG_IGNORE_DEFAULT_XMLNS: string = "ignoreDefaultNamespace";
|
||||
const MEM_QUERY_HISTORY: string = "xpathQueryHistory";
|
||||
const MEM_QUERY_LAST: string = "xPathQueryLast";
|
||||
const OUTPUT_CHANNEL: string = "XPath Results";
|
||||
|
||||
export class XPathFeatureProvider {
|
||||
static async evaluateXPathAsync(editor: vsc.TextEditor, edit: vsc.TextEditorEdit): Promise<void> {
|
||||
// if there is no workspace, we will track queries in the global Memento
|
||||
let memento: vsc.Memento = ext.WorkspaceState || ext.GlobalState;
|
||||
|
||||
// get the xpath persistence setting
|
||||
let persistQueries: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_PERSIST_QUERY, true);
|
||||
|
||||
// get the last query if there is one for this document
|
||||
// if not, try pulling the last query ran, regardless of document
|
||||
// NOTE: if the user has focus on the output channel when opening the xquery prompt, the channel is the "active" document
|
||||
let history: HistoricQuery[] = memento.get<HistoricQuery[]>(MEM_QUERY_HISTORY, new Array<HistoricQuery>());
|
||||
let globalLastQuery: string = memento.get<string>(MEM_QUERY_LAST, "");
|
||||
|
||||
let lastQuery: HistoricQuery = history.find((item: HistoricQuery) => {
|
||||
if (item.uri == editor.document.uri.toString()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// set the inital display value and prompt the user
|
||||
let query: string = "";
|
||||
|
||||
if (persistQueries) {
|
||||
if (lastQuery) {
|
||||
query = lastQuery.query;
|
||||
}
|
||||
|
||||
else {
|
||||
query = globalLastQuery;
|
||||
}
|
||||
}
|
||||
|
||||
query = await vsc.window.showInputBox({
|
||||
placeHolder: "XPath Query",
|
||||
prompt: "Please enter an XPath query to evaluate.",
|
||||
value: query
|
||||
});
|
||||
|
||||
// showInputBox() will return undefined if the user dimissed the prompt
|
||||
if (query) {
|
||||
|
||||
let ignoreDefaultNamespace: boolean = vsc.workspace.getConfiguration(CFG_SECTION).get<boolean>(CFG_IGNORE_DEFAULT_XMLNS, true);
|
||||
|
||||
// run the query
|
||||
let xml: string = editor.document.getText();
|
||||
let evalResult: EvaluatorResult;
|
||||
|
||||
try {
|
||||
evalResult = XPathEvaluator.evaluate(query, xml, ignoreDefaultNamespace);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
vsc.window.showErrorMessage(`Something went wrong while evaluating the XPath: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// show the results to the user
|
||||
let outputChannel: vsc.OutputChannel = vsc.window.createOutputChannel(OUTPUT_CHANNEL);
|
||||
outputChannel.clear();
|
||||
|
||||
outputChannel.appendLine(`XPath Query: ${query}`);
|
||||
outputChannel.append("\n");
|
||||
|
||||
if (evalResult.type === EvaluatorResultType.NODE_COLLECTION) {
|
||||
(evalResult.result as Node[]).forEach((node: XmlNode) => {
|
||||
outputChannel.appendLine(`[Line ${node.lineNumber}] ${node.localName}: ${node.textContent}`);
|
||||
});
|
||||
} else {
|
||||
outputChannel.appendLine(`[Result]: ${evalResult.result}`);
|
||||
}
|
||||
outputChannel.show(vsc.ViewColumn.Three);
|
||||
|
||||
// if persistence is enabled, save the query for later
|
||||
if (persistQueries) {
|
||||
lastQuery = new HistoricQuery(editor.document.uri.toString(), query);
|
||||
|
||||
let affectedIndex: number = -1;
|
||||
history = history.map<HistoricQuery>((item: HistoricQuery, index: number) => {
|
||||
if (item.uri == lastQuery.uri) {
|
||||
item.query = query;
|
||||
affectedIndex = index;
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
if (affectedIndex == -1) {
|
||||
history.push(lastQuery);
|
||||
}
|
||||
|
||||
memento.update(MEM_QUERY_HISTORY, history);
|
||||
memento.update(MEM_QUERY_LAST, query);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HistoricQuery {
|
||||
constructor(uri: string, query: string) {
|
||||
this.uri = uri;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
uri: string;
|
||||
query: string;
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
import * as path from "path";
|
||||
|
||||
let DOMParser = require("xmldom").DOMParser;
|
||||
|
||||
export class XmlTreeViewDataProvider implements vsc.TreeDataProvider<Node> {
|
||||
private _onDidChangeTreeData: vsc.EventEmitter<Node | null> = new vsc.EventEmitter<Node | null>();
|
||||
private _xmlDocument: Document;
|
||||
|
||||
constructor(private _context: vsc.ExtensionContext) {
|
||||
vsc.window.onDidChangeActiveTextEditor((editor) => {
|
||||
this._refreshTree();
|
||||
});
|
||||
|
||||
vsc.workspace.onDidChangeTextDocument((e) => {
|
||||
this._refreshTree();
|
||||
});
|
||||
}
|
||||
|
||||
readonly onDidChangeTreeData: vsc.Event<Node | null> = this._onDidChangeTreeData.event;
|
||||
|
||||
get activeEditor(): vsc.TextEditor | null {
|
||||
return vsc.window.activeTextEditor || null;
|
||||
}
|
||||
|
||||
getChildren(element?: Node): Node[] {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
||||
getTreeItem(element: Node): vsc.TreeItem {
|
||||
let treeItem = new vsc.TreeItem(element.localName);
|
||||
|
||||
if (this._getChildAttributeArray(element).length > 0) {
|
||||
treeItem.collapsibleState = vsc.TreeItemCollapsibleState.Collapsed;
|
||||
}
|
||||
|
||||
if (this._getChildElementArray(element).length > 0) {
|
||||
treeItem.collapsibleState = vsc.TreeItemCollapsibleState.Collapsed;
|
||||
}
|
||||
|
||||
treeItem.command = {
|
||||
command: "revealLine",
|
||||
title: "",
|
||||
arguments: [{
|
||||
lineNumber: (element as any).lineNumber - 1,
|
||||
at: "top"
|
||||
}]
|
||||
};
|
||||
|
||||
treeItem.iconPath = this._getIcon(element);
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
private _getChildAttributeArray(node: Node): Node[] {
|
||||
if (!node.attributes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let array = new Array<Node>();
|
||||
|
||||
for (let i = 0; i < node.attributes.length; i++) {
|
||||
array.push(node.attributes[i]);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private _getChildElementArray(node: Node): Node[] {
|
||||
if (!node.childNodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let array = new Array<Node>();
|
||||
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
let child = node.childNodes[i];
|
||||
|
||||
if ((child as any).tagName) {
|
||||
array.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private _getIcon(element: Node): any {
|
||||
let type = "element";
|
||||
|
||||
if (!(element as any).tagName) {
|
||||
type = "attribute";
|
||||
}
|
||||
|
||||
let 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") {
|
||||
this._xmlDocument = null;
|
||||
this._onDidChangeTreeData.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
let xml = this.activeEditor.document.getText();
|
||||
this._xmlDocument = new DOMParser().parseFromString(xml, "text/xml");
|
||||
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
let child_process = require("child_process");
|
||||
|
||||
export class ChildProcess {
|
||||
static async spawnAsync(executable: string, args: string[]): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
||||
let output: string = "";
|
||||
let handle = child_process.spawn(executable, args);
|
||||
|
||||
handle.stdout.on("data", (data: string) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
handle.stderr.on("data", (data: string) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
handle.on("close", (code: string) => {
|
||||
if (code == "0") {
|
||||
resolve();
|
||||
}
|
||||
|
||||
else {
|
||||
reject({ code: code, message: output });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
import * as xpath from "xpath";
|
||||
|
||||
let DOMParser = require("xmldom").DOMParser;
|
||||
|
||||
export class EvaluatorResult {
|
||||
type: EvaluatorResultType;
|
||||
result: Node[]|number|string|boolean;
|
||||
}
|
||||
|
||||
export class EvaluatorResultType {
|
||||
static SCALAR_TYPE: number = 0;
|
||||
static NODE_COLLECTION: number = 1;
|
||||
}
|
||||
|
||||
export class XPathEvaluator {
|
||||
static evaluate(query: string, xml: string, ignoreDefaultNamespace: boolean): EvaluatorResult {
|
||||
if (ignoreDefaultNamespace) {
|
||||
xml = xml.replace(/xmlns=".+"/g, (match: string) => {
|
||||
return match.replace(/xmlns/g, "xmlns:default");
|
||||
});
|
||||
}
|
||||
|
||||
let nodes: Node[] = new Array<Node>();
|
||||
|
||||
let xdoc: Document = new DOMParser().parseFromString(xml, "text/xml");
|
||||
|
||||
let resolver: xpath.XPathNSResolver = xpath.createNSResolver(xdoc);
|
||||
let result: xpath.XPathResult = xpath.evaluate(
|
||||
query, // xpathExpression
|
||||
xdoc, // contextNode
|
||||
resolver, // namespaceResolver
|
||||
xpath.XPathResult.ANY_TYPE, // resultType
|
||||
null // result
|
||||
)
|
||||
|
||||
let evalResult = new EvaluatorResult();
|
||||
evalResult.type = EvaluatorResultType.SCALAR_TYPE;
|
||||
|
||||
switch(result.resultType) {
|
||||
case xpath.XPathResult.NUMBER_TYPE:
|
||||
evalResult.result = result.numberValue;
|
||||
break;
|
||||
case xpath.XPathResult.STRING_TYPE:
|
||||
evalResult.result = result.stringValue;
|
||||
break;
|
||||
case xpath.XPathResult.BOOLEAN_TYPE:
|
||||
evalResult.result = result.booleanValue;
|
||||
break;
|
||||
case xpath.XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
|
||||
case xpath.XPathResult.ORDERED_NODE_ITERATOR_TYPE:
|
||||
evalResult.result = result.booleanValue;
|
||||
let node: Node;
|
||||
while (node = result.iterateNext()) {
|
||||
nodes.push(node);
|
||||
}
|
||||
evalResult.result = nodes;
|
||||
evalResult.type = EvaluatorResultType.NODE_COLLECTION;
|
||||
break;
|
||||
}
|
||||
|
||||
return evalResult;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
let XQLint = require("xqlint").XQLint;
|
||||
|
||||
export class XQueryCompleter {
|
||||
constructor(script: string) {
|
||||
this.script = script;
|
||||
}
|
||||
|
||||
private _script: string;
|
||||
private _linter: any;
|
||||
|
||||
get script(): string {
|
||||
return this._script;
|
||||
}
|
||||
|
||||
set script(value: string) {
|
||||
this._script = value;
|
||||
this._linter = new XQLint(this._script);
|
||||
}
|
||||
|
||||
getCompletions(line: number, column: number): XQueryCompletionItem[] {
|
||||
let items: XQueryCompletionItem[] = new Array<XQueryCompletionItem>();
|
||||
|
||||
this._linter.getCompletions({line: line, col: column}).forEach((completion: any) => {
|
||||
items.push(new XQueryCompletionItem(completion.name, completion.value, completion.meta));
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
export class XQueryCompletionItem {
|
||||
constructor(name: string, value: string, meta: string) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.meta = meta;
|
||||
}
|
||||
|
||||
name: string;
|
||||
value: string;
|
||||
meta: string;
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
let XQLint = require("xqlint").XQLint;
|
||||
|
||||
export class XQueryLinter {
|
||||
static SEVERITY_WARNING: number = 1;
|
||||
static SEVERITY_ERROR: number = 2;
|
||||
|
||||
static lint(text: string): XQueryDiagnostic[] {
|
||||
let linter = new XQLint(text);
|
||||
let diagnostics: XQueryDiagnostic[] = new Array<XQueryDiagnostic>();
|
||||
|
||||
linter.getErrors().forEach((error: any) => {
|
||||
diagnostics.push(new XQueryDiagnostic(XQueryLinter.SEVERITY_ERROR, error.message, error.pos.sl, error.pos.sc, error.pos.el, error.pos.ec));
|
||||
});
|
||||
|
||||
linter.getWarnings().forEach((warning: any) => {
|
||||
diagnostics.push(new XQueryDiagnostic(XQueryLinter.SEVERITY_WARNING, warning.message, warning.pos.sl, warning.pos.sc, warning.pos.el, warning.pos.ec));
|
||||
});
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
}
|
||||
|
||||
export class XQueryDiagnostic {
|
||||
constructor(severity: number, message: string, startLine: number, startColumn: number, endLine: number, endColumn: number) {
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
this.startLine = startLine;
|
||||
this.startColumn = startColumn;
|
||||
this.endLine = endLine;
|
||||
this.endColumn = endColumn;
|
||||
}
|
||||
|
||||
severity: number;
|
||||
message: string;
|
||||
startLine: number;
|
||||
startColumn: number;
|
||||
endLine: number;
|
||||
endColumn: number;
|
||||
}
|
||||
|
|
@ -1,192 +0,0 @@
|
|||
// Based on pretty-data (https://github.com/vkiryukhin/pretty-data)
|
||||
export class XmlFormatter {
|
||||
constructor(options?: IXmlFormatterOptions) {
|
||||
options = options || {};
|
||||
|
||||
if (typeof options.preferSpaces === "undefined") {
|
||||
options.preferSpaces = false;
|
||||
}
|
||||
|
||||
if (typeof options.splitNamespaces === "undefined") {
|
||||
options.splitNamespaces = true;
|
||||
}
|
||||
|
||||
options.tabSize = options.tabSize || 4;
|
||||
options.newLine = options.newLine || "\n";
|
||||
|
||||
this.newLine = options.newLine || "\n";
|
||||
this.indentPattern = (options.preferSpaces) ? " ".repeat(options.tabSize) : "\t";
|
||||
this.splitNamespaces = options.splitNamespaces;
|
||||
}
|
||||
|
||||
newLine: string;
|
||||
indentPattern: string;
|
||||
splitNamespaces: boolean;
|
||||
|
||||
format(xml: string): string {
|
||||
xml = this.minify(xml, false);
|
||||
xml = xml.replace(/</g, "~::~<");
|
||||
|
||||
if (this.splitNamespaces) {
|
||||
xml = xml
|
||||
.replace(/xmlns\:/g, "~::~xmlns:")
|
||||
.replace(/xmlns\=/g, "~::~xmlns=");
|
||||
}
|
||||
|
||||
let parts: string[] = xml.split("~::~");
|
||||
console.log(parts);
|
||||
let inComment: boolean = false;
|
||||
let level: number = 0;
|
||||
let output: string = "";
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
// <!
|
||||
if (parts[i].search(/<!/) > -1) {
|
||||
output += this._getIndent(level, parts[i]);
|
||||
inComment = true;
|
||||
|
||||
// end <!
|
||||
if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1 || parts[i].search(/!DOCTYPE/) > -1) {
|
||||
inComment = false;
|
||||
}
|
||||
}
|
||||
|
||||
// end <!
|
||||
else if (parts[i].search(/-->/) > -1 || parts[i].search(/\]>/) > -1) {
|
||||
output += parts[i];
|
||||
inComment = false;
|
||||
}
|
||||
|
||||
// <elm></elm>
|
||||
else if (/^<(\w|:)/.test(parts[i - 1]) && /^<\/(\w|:)/.test(parts[i])
|
||||
&& /^<[\w:\-\.\,\/]+/.exec(parts[i - 1])[0] == /^<\/[\w:\-\.\,]+/.exec(parts[i])[0].replace("/", "")) {
|
||||
|
||||
output += parts[i];
|
||||
if (!inComment) level--;
|
||||
}
|
||||
|
||||
// <elm>
|
||||
else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) == -1 && parts[i].search(/\/>/) == -1) {
|
||||
output = (!inComment) ? output += this._getIndent(level++, parts[i]) : output += parts[i];
|
||||
}
|
||||
|
||||
// <elm>...</elm>
|
||||
else if (parts[i].search(/<(\w|:)/) > -1 && parts[i].search(/<\//) > -1) {
|
||||
output = (!inComment) ? output += this._getIndent(level, parts[i]) : output += parts[i];
|
||||
}
|
||||
|
||||
// </elm>
|
||||
else if (parts[i].search(/<\//) > -1) {
|
||||
output = (!inComment) ? output += this._getIndent(--level, parts[i]) : output += parts[i];
|
||||
}
|
||||
|
||||
// <elm />
|
||||
else if (parts[i].search(/\/>/) > -1 && (!this.splitNamespaces || parts[i].search(/xmlns(:|=)/) == -1)) {
|
||||
output = (!inComment) ? output += this._getIndent(level, parts[i]) : output += parts[i];
|
||||
}
|
||||
|
||||
// xmlns />
|
||||
else if (parts[i].search(/\/>/) > -1 && parts[i].search(/xmlns(:|=)/) > -1 && this.splitNamespaces) {
|
||||
output = (!inComment) ? output += this._getIndent(level--, parts[i]) : output += parts[i];
|
||||
}
|
||||
|
||||
// <?xml ... ?>
|
||||
else if (parts[i].search(/<\?/) > -1) {
|
||||
output += this._getIndent(level, parts[i]);
|
||||
}
|
||||
|
||||
// xmlns
|
||||
else if (this.splitNamespaces && (parts[i].search(/xmlns\:/) > -1 || parts[i].search(/xmlns\=/) > -1)) {
|
||||
output += this._getIndent(level, parts[i]);
|
||||
}
|
||||
|
||||
else {
|
||||
output += parts[i];
|
||||
}
|
||||
}
|
||||
|
||||
// remove leading newline
|
||||
if (output[0] == this.newLine) {
|
||||
output = output.slice(1);
|
||||
}
|
||||
|
||||
else if (output.substring(0, 1) == this.newLine) {
|
||||
output = output.slice(2);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
minify(xml: string, removeComments?: boolean): string {
|
||||
if (typeof removeComments === "undefined") {
|
||||
removeComments = false;
|
||||
}
|
||||
|
||||
xml = this._stripLineBreaks(xml); // all line breaks outside of CDATA elements
|
||||
xml = (removeComments) ? xml.replace(/\<![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\>/g, "") : xml;
|
||||
xml = xml.replace(/>\s{0,}</g, "><"); // insignificant whitespace between tags
|
||||
xml = xml.replace(/"\s+(?=[^\s]+=)/g, "\" "); // spaces between attributes
|
||||
xml = xml.replace(/"\s+(?=>)/g, "\""); // spaces between the last attribute and tag close (>)
|
||||
xml = xml.replace(/"\s+(?=\/>)/g, "\" "); // spaces between the last attribute and tag close (/>)
|
||||
xml = xml.replace(/[^ <>="]\s+[^ <>="]+=/g, (match: string) => { // spaces between the node name and the first attribute
|
||||
return match.replace(/\s+/g, " ");
|
||||
});
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
private _getIndent(level: number, trailingValue?: string): string {
|
||||
trailingValue = trailingValue || "";
|
||||
|
||||
return `${this.newLine}${this.indentPattern.repeat(level)}${trailingValue}`;
|
||||
}
|
||||
|
||||
private _stripLineBreaks(xml: string): string {
|
||||
let output: string = "";
|
||||
let inTag: boolean = false;
|
||||
let inTagName: boolean = false;
|
||||
let inCdata: boolean = false;
|
||||
let inAttribute: boolean = false;
|
||||
|
||||
for (let i = 0; i < xml.length; i++) {
|
||||
let char: string = xml.charAt(i);
|
||||
let prev: string = xml.charAt(i - 1);
|
||||
let next: string = xml.charAt(i + 1);
|
||||
|
||||
if (char == "!" && (xml.substr(i, 8) == "![CDATA[" || xml.substr(i, 3) == "!--")) {
|
||||
inCdata = true;
|
||||
}
|
||||
|
||||
else if (char == "]" && (xml.substr(i, 3) == "]]>")) {
|
||||
inCdata = false;
|
||||
}
|
||||
|
||||
else if (char == "-" && (xml.substr(i, 3) == "-->")) {
|
||||
inCdata = false;
|
||||
}
|
||||
|
||||
else if (char.search(/[\r\n]/g) > -1 && !inCdata) {
|
||||
if (/\r/.test(char) && /\S|\r|\n/.test(prev) && /\S|\r|\n/.test(xml.charAt(i + this.newLine.length))) {
|
||||
output += char;
|
||||
}
|
||||
|
||||
else if (/\n/.test(char) && /\S|\r|\n/.test(xml.charAt(i - this.newLine.length)) && /\S|\r|\n/.test(next)) {
|
||||
output += char;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
output += char;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
export interface IXmlFormatterOptions {
|
||||
preferSpaces?: boolean;
|
||||
tabSize?: number;
|
||||
newLine?: string;
|
||||
splitNamespaces?: boolean;
|
||||
}
|
||||
65
src/test/extension.test.ts
Normal file
65
src/test/extension.test.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import * as assert from "assert";
|
||||
import { FormattingOptions } from "vscode";
|
||||
|
||||
import { TestDataLoader } from "./test-utils/test-data-loader";
|
||||
|
||||
import { XmlFormatter, XmlFormattingOptions } from "../formatting";
|
||||
import { V2XmlFormatter } from "../formatting/formatters";
|
||||
|
||||
describe("V2XmlFormatter", () => {
|
||||
|
||||
const xmlFormatter = new V2XmlFormatter();
|
||||
|
||||
describe("#formatXml(xml, options)", () => {
|
||||
|
||||
const options = {
|
||||
editorOptions: {
|
||||
insertSpaces: true,
|
||||
tabSize: 4
|
||||
},
|
||||
newLine: "\r\n",
|
||||
removeCommentsOnMinify: false,
|
||||
splitAttributesOnFormat: false,
|
||||
splitXmlnsOnFormat: true
|
||||
};
|
||||
|
||||
it("should handle basic XML", () => {
|
||||
testFormatter(xmlFormatter, options, "basic");
|
||||
});
|
||||
|
||||
it("should handle unicode element names", () => {
|
||||
testFormatter(xmlFormatter, options, "unicode");
|
||||
});
|
||||
|
||||
it("should handle self-closing elements", () => {
|
||||
testFormatter(xmlFormatter, options, "self-closing");
|
||||
});
|
||||
|
||||
it("should handle text-only lines", () => {
|
||||
testFormatter(xmlFormatter, options, "text-only-line");
|
||||
});
|
||||
|
||||
it("should handle preformatted xml", () => {
|
||||
testFormatter(xmlFormatter, options, "preformatted");
|
||||
});
|
||||
|
||||
it ("should preserve line breaks between elements", () => {
|
||||
testFormatter(xmlFormatter, options, "preserve-breaks");
|
||||
});
|
||||
|
||||
it("should maintain comment formatting", () => {
|
||||
testFormatter(xmlFormatter, options, "maintain-comment-formatting");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function testFormatter(xmlFormatter: XmlFormatter, options: XmlFormattingOptions, fileLabel: string): void {
|
||||
const expectedFormattedXml = TestDataLoader.load(`${fileLabel}.formatted.xml`);
|
||||
const unformattedXml = TestDataLoader.load(`${fileLabel}.unformatted.xml`);
|
||||
|
||||
const actualFormattedXml = xmlFormatter.formatXml(unformattedXml, options);
|
||||
|
||||
assert.equal(actualFormattedXml, expectedFormattedXml, "Actual formatted XML does not match expected formatted XML.");
|
||||
}
|
||||
3
src/test/test-data/basic.formatted.xml
Normal file
3
src/test/test-data/basic.formatted.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
</root>
|
||||
1
src/test/test-data/basic.unformatted.xml
Normal file
1
src/test/test-data/basic.unformatted.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<root><element>text</element></root>
|
||||
13
src/test/test-data/maintain-comment-formatting.formatted.xml
Normal file
13
src/test/test-data/maintain-comment-formatting.formatted.xml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
<!--
|
||||
<Description>
|
||||
Any description.
|
||||
</Description>
|
||||
<Keyword UUID="fd8e6b13-9865-4d8c-9ecd-5ff08a0cf2e2"/>
|
||||
<Parameters>
|
||||
<Parameter Name="ParameterName1">The Parameter Name.</Parameter>
|
||||
<Parameter Name="ParameterName2" PossibleValues="Val1,Val2" DefaultValue="Val1"/>
|
||||
</Parameters>
|
||||
-->
|
||||
</root>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
<!--
|
||||
<Description>
|
||||
Any description.
|
||||
</Description>
|
||||
<Keyword UUID="fd8e6b13-9865-4d8c-9ecd-5ff08a0cf2e2"/>
|
||||
<Parameters>
|
||||
<Parameter Name="ParameterName1">The Parameter Name.</Parameter>
|
||||
<Parameter Name="ParameterName2" PossibleValues="Val1,Val2" DefaultValue="Val1"/>
|
||||
</Parameters>
|
||||
-->
|
||||
</root>
|
||||
9
src/test/test-data/preformatted.formatted.xml
Normal file
9
src/test/test-data/preformatted.formatted.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
<element>
|
||||
<element2>text</element2>
|
||||
</element2>
|
||||
</root>
|
||||
9
src/test/test-data/preformatted.unformatted.xml
Normal file
9
src/test/test-data/preformatted.unformatted.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
<element>
|
||||
<element2>text</element2>
|
||||
</element2>
|
||||
</root>
|
||||
12
src/test/test-data/preserve-breaks.formatted.xml
Normal file
12
src/test/test-data/preserve-breaks.formatted.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
|
||||
<element>
|
||||
<element2>text</element2>
|
||||
</element2>
|
||||
|
||||
</root>
|
||||
12
src/test/test-data/preserve-breaks.unformatted.xml
Normal file
12
src/test/test-data/preserve-breaks.unformatted.xml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<root>
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
|
||||
<element>text</element>
|
||||
<element>text</element>
|
||||
|
||||
<element>
|
||||
<element2>text</element2>
|
||||
</element2>
|
||||
|
||||
</root>
|
||||
3
src/test/test-data/self-closing.formatted.xml
Normal file
3
src/test/test-data/self-closing.formatted.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<Node>
|
||||
<Node name="testChild"/>
|
||||
</Node>
|
||||
1
src/test/test-data/self-closing.unformatted.xml
Normal file
1
src/test/test-data/self-closing.unformatted.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<Node><Node name="testChild"/></Node>
|
||||
6
src/test/test-data/text-only-line.formatted.xml
Normal file
6
src/test/test-data/text-only-line.formatted.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<Tag>
|
||||
<Tag2>
|
||||
Text1
|
||||
Text2
|
||||
</Tag2>
|
||||
</Tag>
|
||||
6
src/test/test-data/text-only-line.unformatted.xml
Normal file
6
src/test/test-data/text-only-line.unformatted.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<Tag>
|
||||
<Tag2>
|
||||
Text1
|
||||
Text2
|
||||
</Tag2>
|
||||
</Tag>
|
||||
4
src/test/test-data/unicode.formatted.xml
Normal file
4
src/test/test-data/unicode.formatted.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Имя>
|
||||
<element>text</element>
|
||||
</Имя>
|
||||
2
src/test/test-data/unicode.unformatted.xml
Normal file
2
src/test/test-data/unicode.unformatted.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Имя><element>text</element></Имя>
|
||||
7
src/test/test-utils/test-data-loader.ts
Normal file
7
src/test/test-utils/test-data-loader.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import * as fs from "fs";
|
||||
|
||||
export class TestDataLoader {
|
||||
static load(fileName: string): string {
|
||||
return fs.readFileSync(`${__dirname}/../../../src/test/test-data/${fileName}`, "UTF-8");
|
||||
}
|
||||
}
|
||||
1
src/tree-view/index.ts
Normal file
1
src/tree-view/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./xml-tree-data-provider";
|
||||
233
src/tree-view/xml-tree-data-provider.ts
Normal file
233
src/tree-view/xml-tree-data-provider.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import { window, workspace } from "vscode";
|
||||
import {
|
||||
Event, EventEmitter, ExtensionContext, Position, TextEditor, TreeDataProvider,
|
||||
TreeItem, TreeItemCollapsibleState
|
||||
} from "vscode";
|
||||
|
||||
import * as path from "path";
|
||||
import { DOMParser } from "xmldom";
|
||||
|
||||
import { Configuration, NativeCommands } from "../common";
|
||||
import * as constants from "../constants";
|
||||
|
||||
export class XmlTreeDataProvider implements TreeDataProvider<any> {
|
||||
private _onDidChangeTreeData: EventEmitter<any> = new EventEmitter<any>();
|
||||
private _xmlDocument: Document;
|
||||
|
||||
constructor(private _context: ExtensionContext) {
|
||||
window.onDidChangeActiveTextEditor(() => {
|
||||
this._refreshTree();
|
||||
});
|
||||
|
||||
workspace.onDidChangeTextDocument(() => {
|
||||
this._refreshTree();
|
||||
});
|
||||
|
||||
this._refreshTree();
|
||||
}
|
||||
|
||||
onDidChangeTreeData = this._onDidChangeTreeData.event;
|
||||
|
||||
get activeEditor(): TextEditor {
|
||||
return window.activeTextEditor || null;
|
||||
}
|
||||
|
||||
getTreeItem(element: Node): TreeItem | Thenable<TreeItem> {
|
||||
const enableMetadata = Configuration.enableXmlTreeViewMetadata;
|
||||
const enableSync = Configuration.enableXmlTreeViewCursorSync;
|
||||
|
||||
const treeItem = new TreeItem(element.localName);
|
||||
|
||||
if (!this._isElement(element)) {
|
||||
treeItem.label = `${element.localName} = "${element.nodeValue}"`;
|
||||
}
|
||||
|
||||
else if (enableMetadata) {
|
||||
const childAttributes = this._getChildAttributeArray(<Element>element);
|
||||
const childElements = this._getChildElementArray(<Element>element);
|
||||
const totalChildren = (childAttributes.length + childElements.length);
|
||||
|
||||
if (totalChildren > 0) {
|
||||
treeItem.label += " (";
|
||||
|
||||
if (childAttributes.length > 0) {
|
||||
treeItem.label += `attributes: ${childAttributes.length}, `;
|
||||
treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
}
|
||||
|
||||
if (childElements.length > 0) {
|
||||
treeItem.label += `children: ${childElements.length}, `;
|
||||
treeItem.collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
}
|
||||
|
||||
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 = {
|
||||
command: constants.nativeCommands.revealLine,
|
||||
title: "",
|
||||
arguments: [{
|
||||
lineNumber: (element as any).lineNumber - 1,
|
||||
at: "top"
|
||||
}]
|
||||
};
|
||||
|
||||
treeItem.iconPath = this._getIcon(element);
|
||||
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
getChildren(element?: Node): Node[] | Thenable<Node[]> {
|
||||
if (!this._xmlDocument) {
|
||||
this._refreshTree();
|
||||
}
|
||||
|
||||
if (this._isElement(element)) {
|
||||
return [].concat(this._getChildAttributeArray(<Element>element), this._getChildElementArray(<Element>element));
|
||||
}
|
||||
|
||||
else if (this._xmlDocument) {
|
||||
return [this._xmlDocument.lastChild];
|
||||
}
|
||||
|
||||
else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
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 [];
|
||||
}
|
||||
|
||||
const array = new Array<any>();
|
||||
|
||||
for (let i = 0; i < node.attributes.length; i++) {
|
||||
array.push(node.attributes[i]);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private _getChildElementArray(node: Element): any[] {
|
||||
if (!node.childNodes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const array = new Array<any>();
|
||||
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
const child = node.childNodes[i];
|
||||
|
||||
if (this._isElement(child)) {
|
||||
array.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
private _getIcon(element: Node): any {
|
||||
let type = "element";
|
||||
|
||||
if (!this._isElement(element)) {
|
||||
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 _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);
|
||||
}
|
||||
|
||||
private _refreshTree(): void {
|
||||
if (!this.activeEditor || this.activeEditor.document.languageId !== constants.languageIds.xml) {
|
||||
NativeCommands.setContext(constants.contextKeys.xmlTreeViewEnabled, false);
|
||||
|
||||
this._xmlDocument = null;
|
||||
this._onDidChangeTreeData.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
const enableTreeView = Configuration.enableXmlTreeView;
|
||||
|
||||
NativeCommands.setContext(constants.contextKeys.xmlTreeViewEnabled, enableTreeView);
|
||||
|
||||
const xml = this.activeEditor.document.getText();
|
||||
|
||||
try {
|
||||
this._xmlDocument = new DOMParser({
|
||||
errorHandler: () => {
|
||||
throw new Error("Invalid Document");
|
||||
},
|
||||
locator: {}
|
||||
}).parseFromString(xml, "text/xml");
|
||||
}
|
||||
|
||||
catch {
|
||||
this._xmlDocument = new DOMParser().parseFromString("<InvalidDocument />", "text/xml");
|
||||
}
|
||||
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import * as vsc from "vscode";
|
||||
|
||||
export class RangeUtil {
|
||||
static getRangeForDocument(document: vsc.TextDocument): vsc.Range {
|
||||
let lastLineIndex = (document.lineCount - 1);
|
||||
let range = new vsc.Range(new vsc.Position(0, 0), new vsc.Position(lastLineIndex, Number.MAX_VALUE));
|
||||
|
||||
range = document.validateRange(range);
|
||||
return range;
|
||||
}
|
||||
}
|
||||
102
src/xpath/commands/evaluateXPath.ts
Normal file
102
src/xpath/commands/evaluateXPath.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { window } from "vscode";
|
||||
import { TextEditor, TextEditorEdit, ViewColumn } from "vscode";
|
||||
|
||||
import { Configuration, ExtensionState } from "../../common";
|
||||
import * as constants from "../../constants";
|
||||
|
||||
import { EvaluatorResult, EvaluatorResultType, XPathEvaluator } from "../xpath-evaluator";
|
||||
|
||||
class HistoricQuery {
|
||||
constructor(uri: string, query: string) {
|
||||
this.uri = uri;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
uri: string;
|
||||
query: string;
|
||||
}
|
||||
|
||||
export async function evaluateXPath(editor: TextEditor, edit: TextEditorEdit): Promise<void> {
|
||||
// if there is no workspace, we will track queries in the global Memento
|
||||
const memento = ExtensionState.workspace || ExtensionState.global;
|
||||
|
||||
// get the xpath persistence setting
|
||||
const persistQueries = Configuration.persistXPathQuery;
|
||||
|
||||
// get the last query if there is one for this document
|
||||
// if not, try pulling the last query ran, regardless of document
|
||||
// NOTE: if the user has focus on the output channel when opening the xquery prompt, the channel is the "active" document
|
||||
const history = memento.get<HistoricQuery[]>(constants.stateKeys.xpathQueryHistory, new Array<HistoricQuery>());
|
||||
const globalLastQuery = memento.get<string>(constants.stateKeys.xPathQueryLast, "");
|
||||
|
||||
const lastQuery = history.find(x => {
|
||||
return (x.uri === editor.document.uri.toString());
|
||||
});
|
||||
|
||||
// set the inital display value and prompt the user
|
||||
let query = (lastQuery) ? lastQuery.query : globalLastQuery;
|
||||
|
||||
query = await window.showInputBox({
|
||||
placeHolder: "XPath Query",
|
||||
prompt: "Please enter an XPath query to evaluate.",
|
||||
value: query
|
||||
});
|
||||
|
||||
// showInputBox() will return undefined if the user dimissed the prompt
|
||||
if (!query) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ignoreDefaultNamespace = Configuration.ignoreDefaultNamespace;
|
||||
|
||||
// run the query
|
||||
const xml = editor.document.getText();
|
||||
let evalResult: EvaluatorResult;
|
||||
|
||||
try {
|
||||
evalResult = XPathEvaluator.evaluate(query, xml, ignoreDefaultNamespace);
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
window.showErrorMessage(`Something went wrong while evaluating the XPath: ${error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// show the results to the user
|
||||
const outputChannel = window.createOutputChannel("XPath Results");
|
||||
|
||||
outputChannel.clear();
|
||||
|
||||
outputChannel.appendLine(`XPath Query: ${query}`);
|
||||
outputChannel.append("\n");
|
||||
|
||||
if (evalResult.type === EvaluatorResultType.NODE_COLLECTION) {
|
||||
(evalResult.result as Node[]).forEach((node: any) => {
|
||||
outputChannel.appendLine(`[Line ${node.lineNumber}] ${node.localName}: ${node.textContent}`);
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
outputChannel.appendLine(`[Result]: ${evalResult.result}`);
|
||||
}
|
||||
|
||||
outputChannel.show(false);
|
||||
|
||||
if (persistQueries) {
|
||||
const historicQuery = new HistoricQuery(editor.document.uri.toString(), query);
|
||||
|
||||
const affectedIndex = history.findIndex(x => x.uri === historicQuery.uri);
|
||||
|
||||
if (affectedIndex === -1) {
|
||||
history.push(historicQuery);
|
||||
}
|
||||
|
||||
else {
|
||||
history[affectedIndex].query = query;
|
||||
}
|
||||
|
||||
memento.update(constants.stateKeys.xpathQueryHistory, history);
|
||||
memento.update(constants.stateKeys.xPathQueryLast, query);
|
||||
}
|
||||
}
|
||||
1
src/xpath/commands/index.ts
Normal file
1
src/xpath/commands/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./evaluateXPath";
|
||||
1
src/xpath/index.ts
Normal file
1
src/xpath/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./xpath-evaluator";
|
||||
59
src/xpath/xpath-evaluator.ts
Normal file
59
src/xpath/xpath-evaluator.ts
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import * as xpath from "xpath";
|
||||
import { SelectedValue, XPathSelect } from "xpath";
|
||||
import { DOMParser } from "xmldom";
|
||||
|
||||
export class EvaluatorResult {
|
||||
type: EvaluatorResultType;
|
||||
result: Node[] | number | string | boolean;
|
||||
}
|
||||
|
||||
export class EvaluatorResultType {
|
||||
static SCALAR_TYPE = 0;
|
||||
static NODE_COLLECTION = 1;
|
||||
}
|
||||
|
||||
export class XPathEvaluator {
|
||||
static evaluate(query: string, xml: string, ignoreDefaultNamespace: boolean): EvaluatorResult {
|
||||
if (ignoreDefaultNamespace) {
|
||||
xml = xml.replace(/xmlns=".+"/g, (match: string) => {
|
||||
return match.replace(/xmlns/g, "xmlns:default");
|
||||
});
|
||||
}
|
||||
|
||||
const nodes = new Array<Node>();
|
||||
const xdoc: Document = new DOMParser().parseFromString(xml, "text/xml");
|
||||
const resolver = (xpath as any).createNSResolver(xdoc);
|
||||
const xPathResult = xpath.evaluate(query, xdoc, resolver, 0, null);
|
||||
|
||||
const evaluatorResult = new EvaluatorResult();
|
||||
evaluatorResult.type = EvaluatorResultType.SCALAR_TYPE;
|
||||
|
||||
switch (xPathResult.resultType) {
|
||||
case xPathResult.NUMBER_TYPE:
|
||||
evaluatorResult.result = xPathResult.numberValue;
|
||||
break;
|
||||
case xPathResult.STRING_TYPE:
|
||||
evaluatorResult.result = xPathResult.stringValue;
|
||||
break;
|
||||
case xPathResult.BOOLEAN_TYPE:
|
||||
evaluatorResult.result = xPathResult.booleanValue;
|
||||
break;
|
||||
case xPathResult.UNORDERED_NODE_ITERATOR_TYPE:
|
||||
case xPathResult.ORDERED_NODE_ITERATOR_TYPE:
|
||||
evaluatorResult.result = xPathResult.booleanValue;
|
||||
|
||||
let node: Node;
|
||||
|
||||
while (node = xPathResult.iterateNext()) {
|
||||
nodes.push(node);
|
||||
}
|
||||
|
||||
evaluatorResult.result = nodes;
|
||||
evaluatorResult.type = EvaluatorResultType.NODE_COLLECTION;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return evaluatorResult;
|
||||
}
|
||||
}
|
||||
30
src/xquery-execution/child-process.ts
Normal file
30
src/xquery-execution/child-process.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
const child_process = require("child_process");
|
||||
|
||||
export class ChildProcess {
|
||||
static async spawn(executable: string, args: string[]): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
||||
let output = "";
|
||||
const handle = child_process.spawn(executable, args);
|
||||
|
||||
handle.stdout.on("data", (data: string) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
handle.stderr.on("data", (data: string) => {
|
||||
output += data;
|
||||
});
|
||||
|
||||
handle.on("close", (code: string) => {
|
||||
if (code === "0") {
|
||||
resolve();
|
||||
}
|
||||
|
||||
else {
|
||||
reject({ code: code, message: output });
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
131
src/xquery-execution/commands/executeXQuery.ts
Normal file
131
src/xquery-execution/commands/executeXQuery.ts
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import { window, workspace } from "vscode";
|
||||
import { Disposable, Range, TextEditor, TextEditorEdit, Uri } from "vscode";
|
||||
|
||||
import * as constants from "../../constants";
|
||||
|
||||
import { ChildProcess } from "../child-process";
|
||||
import { Configuration, NativeCommands } from "../../common";
|
||||
|
||||
export async function executeXQuery(editor: TextEditor, edit: TextEditorEdit): Promise<void> {
|
||||
// this disposable will be used for creating status bar messages
|
||||
let disposable: Disposable;
|
||||
|
||||
if (editor.document.languageId !== constants.languageIds.xquery) {
|
||||
window.showErrorMessage("This action can only be performed on an XQuery file.");
|
||||
return;
|
||||
}
|
||||
|
||||
const executable = Configuration.xqueryExecutionEngine;
|
||||
let args = Configuration.xqueryExecutionArguments || [];
|
||||
|
||||
if (!executable || executable === "") {
|
||||
const action = await window.showWarningMessage("An XQuery execution engine has not been defined.", "Define Now");
|
||||
|
||||
if (action === "Define Now") {
|
||||
NativeCommands.openGlobalSettings();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let inputFile: Uri;
|
||||
disposable = window.setStatusBarMessage("Searching for XML files in folder...");
|
||||
|
||||
const searchPattern = Configuration.xqueryExecutionInputSearchPattern;
|
||||
const inputLimit = Configuration.xqueryExecutionInputLimit;
|
||||
|
||||
const files = await workspace.findFiles(searchPattern, "", inputLimit);
|
||||
|
||||
disposable.dispose();
|
||||
|
||||
// user does not have a folder open - prompt for file name
|
||||
if (typeof files === "undefined") {
|
||||
window.showErrorMessage("You must have a folder opened in VS Code to use this feature.");
|
||||
return;
|
||||
}
|
||||
|
||||
// if there is only one XML file, default it
|
||||
// otherwise, prompt the user to select one from the open folder
|
||||
if (files.length > 1) {
|
||||
const qpItems = new Array<any>();
|
||||
|
||||
files.forEach((file) => {
|
||||
const filename = file.fsPath.replace("\\", "/");
|
||||
|
||||
qpItems.push({ // must implement vscode.QuickPickItem
|
||||
label: filename.substring(filename.lastIndexOf("/") + 1),
|
||||
description: file.fsPath,
|
||||
file: file
|
||||
});
|
||||
});
|
||||
|
||||
const selection = await window.showQuickPick(qpItems, { placeHolder: "Please select an input file." });
|
||||
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
inputFile = selection.file;
|
||||
}
|
||||
|
||||
else {
|
||||
inputFile = files[0];
|
||||
}
|
||||
|
||||
// prompt for output file name
|
||||
let outputPath: string = null;
|
||||
let outputPathPos = -1;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (i > 0) {
|
||||
if (args[i - 1].search(/out|result/)) {
|
||||
outputPath = args[i];
|
||||
outputPathPos = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (outputPath) {
|
||||
outputPath = await window.showInputBox({
|
||||
placeHolder: "ex. C:\\TEMP\XQueryOutput\\MyOutputFile.xml",
|
||||
prompt: "Please specify the output file path. Existing file behavior is determined by the execution engine you have specified.",
|
||||
value: outputPath
|
||||
});
|
||||
|
||||
args[outputPathPos] = outputPath;
|
||||
}
|
||||
|
||||
// call out to the execution engine
|
||||
disposable = window.setStatusBarMessage("Executing XQuery Script...");
|
||||
args = args.map<string>((value: string) => {
|
||||
return value
|
||||
.replace("$(script)", editor.document.uri.fsPath)
|
||||
.replace("$(input)", inputFile.fsPath)
|
||||
.replace("$(project)", workspace.rootPath);
|
||||
});
|
||||
|
||||
try {
|
||||
await ChildProcess.spawn(executable, args);
|
||||
}
|
||||
|
||||
catch (error) {
|
||||
if (error.message.search(/[Ll]ine:?\s*\d+/gm) > -1) {
|
||||
const match: RegExpExecArray = /[Ll]ine:?\s*\d+/gm.exec(error.message);
|
||||
const line: number = (Number.parseInt(match[0].replace(/([Ll]ine:?\s*)|\s/, "")) - 1);
|
||||
|
||||
const selection: string = await window.showErrorMessage(error.message, `Go to Line ${line}`);
|
||||
|
||||
if (selection === `Go to Line ${line}`) {
|
||||
editor.revealRange(new Range(line, 0, line, 0));
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
window.showErrorMessage(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
finally {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
1
src/xquery-execution/commands/index.ts
Normal file
1
src/xquery-execution/commands/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./executeXQuery";
|
||||
1
src/xquery-execution/index.ts
Normal file
1
src/xquery-execution/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export * from "./child-process";
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"removeComments": true,
|
||||
"target": "es6"
|
||||
},
|
||||
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "out",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es6"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"strictNullChecks": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
".vscode-test"
|
||||
]
|
||||
}
|
||||
117
tslint.json
Normal file
117
tslint.json
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
{
|
||||
"rules": {
|
||||
"arrow-return-shorthand": true,
|
||||
"callable-types": true,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"deprecation": {
|
||||
"severity": "warn"
|
||||
},
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"import-blacklist": [
|
||||
true,
|
||||
"rxjs",
|
||||
"rxjs/Rx"
|
||||
],
|
||||
"import-spacing": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces",
|
||||
4
|
||||
],
|
||||
"interface-over-type-literal": true,
|
||||
"label-position": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
165
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
{
|
||||
"order": [
|
||||
"static-field",
|
||||
"instance-field",
|
||||
"static-method",
|
||||
"instance-method"
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-super": true,
|
||||
"no-empty": false,
|
||||
"no-empty-interface": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": [
|
||||
true,
|
||||
"ignore-params"
|
||||
],
|
||||
"no-misused-new": true,
|
||||
"no-non-null-assertion": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-string-throw": true,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unnecessary-initializer": true,
|
||||
"no-unused-expression": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-whitespace"
|
||||
],
|
||||
"prefer-const": true,
|
||||
"quotemark": [
|
||||
true,
|
||||
"double"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"unified-signatures": true,
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
||||
2088
typings/node/node.d.ts
vendored
2088
typings/node/node.d.ts
vendored
File diff suppressed because it is too large
Load diff
3
typings/thenable/thenable.d.ts
vendored
3
typings/thenable/thenable.d.ts
vendored
|
|
@ -1,3 +0,0 @@
|
|||
declare class Thenable<T> extends Promise<T> {
|
||||
// shim for vscode's Thenable<T>
|
||||
}
|
||||
4
typings/xmldom/xmldom.d.ts
vendored
4
typings/xmldom/xmldom.d.ts
vendored
|
|
@ -1,4 +0,0 @@
|
|||
declare class XmlNode extends Node {
|
||||
lineNumber: number;
|
||||
columnNumber: number;
|
||||
}
|
||||
197
typings/xpath/xpath.d.ts
vendored
197
typings/xpath/xpath.d.ts
vendored
|
|
@ -1,197 +0,0 @@
|
|||
// Type definitions for xpath v0.0.7
|
||||
// Project: https://github.com/goto100/xpath
|
||||
// Definitions by: Andrew Bradley <https://github.com/cspotcode/>
|
||||
// Definitions: https://github.com/borisyankov/DefinitelyTyped
|
||||
|
||||
// Some documentation prose is copied from the XPath documentation at https://developer.mozilla.org.
|
||||
|
||||
declare module 'xpath' {
|
||||
|
||||
// select1 can return any of: `Node`, `boolean`, `string`, `number`.
|
||||
// select and selectWithResolver can return any of the above return types or `Array<Node>`.
|
||||
// For this reason, their return types are `any`.
|
||||
|
||||
interface SelectFn {
|
||||
/**
|
||||
* Evaluate an XPath expression against a DOM node. Returns the result as one of the following:
|
||||
* * Array<Node>
|
||||
* * Node
|
||||
* * boolean
|
||||
* * number
|
||||
* * string
|
||||
* @param xpathText
|
||||
* @param contextNode
|
||||
* @param single If true and the evaluation result is one or more Nodes, will return only the first Node instead of an Array<Node>
|
||||
*/
|
||||
(xpathText: string, contextNode: Node, single?: boolean): any;
|
||||
}
|
||||
|
||||
var select: SelectFn;
|
||||
|
||||
/**
|
||||
* Evaluate an xpath expression against a DOM node, returning the first result only.
|
||||
* Equivalent to `select(xpathText, contextNode, true)`
|
||||
* @param xpathText
|
||||
* @param contextNode
|
||||
*/
|
||||
function select1(xpathText: string, contextNode: Node): any;
|
||||
|
||||
/**
|
||||
* Evaluate an XPath expression against a DOM node using a given namespace resolver. Returns the result as one of the following:
|
||||
* * Array<Node>
|
||||
* * Node
|
||||
* * boolean
|
||||
* * number
|
||||
* * string
|
||||
* @param xpathText
|
||||
* @param contextNode
|
||||
* @param resolver
|
||||
* @param single If true and the evaluation result is one or more Nodes, will return only the first Node instead of an Array<Node>
|
||||
*/
|
||||
function selectWithResolver(xpathText: string, contextNode: Node, resolver: XPathNSResolver, single?: boolean): any;
|
||||
|
||||
/**
|
||||
* Evaluate an xpath expression against a DOM.
|
||||
* @param xpathText xpath expression as a string.
|
||||
* @param contextNode xpath expression is evaluated relative to this DOM node.
|
||||
* @param resolver XML namespace resolver
|
||||
* @param resultType
|
||||
* @param result If non-null, xpath *may* reuse this XPathResult object instead of creating a new one. However, it is not required to do so.
|
||||
* @return XPathResult object containing the result of the expression.
|
||||
*/
|
||||
function evaluate(xpathText: string, contextNode: Node, resolver: XPathNSResolver, resultType: number, result?: XPathResult): XPathResult;
|
||||
|
||||
/**
|
||||
* Creates a `select` function that uses the given namespace prefix to URI mappings when evaluating queries.
|
||||
* @param namespaceMappings an object mapping namespace prefixes to namespace URIs. Each key is a prefix; each value is a URI.
|
||||
* @return a function with the same signature as `xpath.select`
|
||||
*/
|
||||
function useNamespaces(namespaceMappings: NamespaceMap): typeof select;
|
||||
interface NamespaceMap {
|
||||
[namespacePrefix: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile an XPath expression into an XPathExpression which can be (repeatedly) evaluated against a DOM.
|
||||
* @param xpathText XPath expression as a string
|
||||
* @param namespaceURLMapper Namespace resolver
|
||||
* @return compiled expression
|
||||
*/
|
||||
function createExpression(xpathText: string, namespaceURLMapper: XPathNSResolver): XPathExpression;
|
||||
|
||||
/**
|
||||
* Create an XPathNSResolver that resolves based on the information available in the context of a DOM node.
|
||||
* @param node
|
||||
*/
|
||||
function createNSResolver(node: Node): XPathNSResolver;
|
||||
|
||||
/**
|
||||
* Result of evaluating an XPathExpression.
|
||||
*/
|
||||
class XPathResult {
|
||||
/**
|
||||
* A result set containing whatever type naturally results from evaluation of the expression. Note that if the result is a node-set then UNORDERED_NODE_ITERATOR_TYPE is always the resulting type.
|
||||
*/
|
||||
static ANY_TYPE: number;
|
||||
/**
|
||||
* A result containing a single number. This is useful for example, in an XPath expression using the count() function.
|
||||
*/
|
||||
static NUMBER_TYPE: number;
|
||||
/**
|
||||
* A result containing a single string.
|
||||
*/
|
||||
static STRING_TYPE: number;
|
||||
/**
|
||||
* A result containing a single boolean value. This is useful for example, in an XPath expression using the not() function.
|
||||
*/
|
||||
static BOOLEAN_TYPE: number;
|
||||
/**
|
||||
* A result node-set containing all the nodes matching the expression. The nodes may not necessarily be in the same order that they appear in the document.
|
||||
*/
|
||||
static UNORDERED_NODE_ITERATOR_TYPE: number;
|
||||
/**
|
||||
* A result node-set containing all the nodes matching the expression. The nodes in the result set are in the same order that they appear in the document.
|
||||
*/
|
||||
static ORDERED_NODE_ITERATOR_TYPE: number;
|
||||
/**
|
||||
* A result node-set containing snapshots of all the nodes matching the expression. The nodes may not necessarily be in the same order that they appear in the document.
|
||||
*/
|
||||
static UNORDERED_NODE_SNAPSHOT_TYPE: number;
|
||||
/**
|
||||
* A result node-set containing snapshots of all the nodes matching the expression. The nodes in the result set are in the same order that they appear in the document.
|
||||
*/
|
||||
static ORDERED_NODE_SNAPSHOT_TYPE: number;
|
||||
/**
|
||||
* A result node-set containing any single node that matches the expression. The node is not necessarily the first node in the document that matches the expression.
|
||||
*/
|
||||
static ANY_UNORDERED_NODE_TYPE: number;
|
||||
/**
|
||||
* A result node-set containing the first node in the document that matches the expression.
|
||||
*/
|
||||
static FIRST_ORDERED_NODE_TYPE: number;
|
||||
|
||||
/**
|
||||
* Type of this result. It is one of the enumerated result types.
|
||||
*/
|
||||
resultType: number;
|
||||
|
||||
/**
|
||||
* Returns the next node in this result, if this result is one of the _ITERATOR_ result types.
|
||||
*/
|
||||
iterateNext(): Node;
|
||||
|
||||
/**
|
||||
* returns the result node for a given index, if this result is one of the _SNAPSHOT_ result types.
|
||||
* @param index
|
||||
*/
|
||||
snapshotItem(index: number): Node;
|
||||
|
||||
/**
|
||||
* Number of nodes in this result, if this result is one of the _SNAPSHOT_ result types.
|
||||
*/
|
||||
snapshotLength: number;
|
||||
|
||||
/**
|
||||
* Value of this result, if it is a BOOLEAN_TYPE result.
|
||||
*/
|
||||
booleanValue: boolean;
|
||||
/**
|
||||
* Value of this result, if it is a NUMBER_TYPE result.
|
||||
*/
|
||||
numberValue: number;
|
||||
/**
|
||||
* Value of this result, if it is a STRING_TYPE result.
|
||||
*/
|
||||
stringValue: string;
|
||||
|
||||
/**
|
||||
* Value of this result, if it is a FIRST_ORDERED_NODE_TYPE result.
|
||||
*/
|
||||
singleNodeValue: Node;
|
||||
}
|
||||
|
||||
/**
|
||||
* A compiled XPath expression, ready to be (repeatedly) evaluated against a DOM node.
|
||||
*/
|
||||
interface XPathExpression {
|
||||
/**
|
||||
* evaluate this expression against a DOM node.
|
||||
* @param contextNode
|
||||
* @param resultType
|
||||
* @param result
|
||||
*/
|
||||
evaluate(contextNode: Node, resultType: number, result?: XPathResult): XPathResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object that can resolve XML namespace prefixes to namespace URIs.
|
||||
*/
|
||||
interface XPathNSResolver {
|
||||
/**
|
||||
* Given an XML namespace prefix, returns the corresponding XML namespace URI.
|
||||
* @param prefix XML namespace prefix
|
||||
* @return XML namespace URI
|
||||
*/
|
||||
lookupNamespaceURI(prefix: string): string;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue