Out With the Old
This commit is contained in:
parent
c828608867
commit
01b580768b
41 changed files with 0 additions and 4897 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue