diff --git a/languages/xquery/xquery.json b/languages/xquery/xquery.json index 17f4dc4..1ce9409 100644 --- a/languages/xquery/xquery.json +++ b/languages/xquery/xquery.json @@ -1,10 +1,7 @@ { "comments": { - "lineComment": "//", - "blockComment": [ - "/*", - "*/" - ] + "lineComment": ["(:", ":)"], + "blockComment": [ "(:~", "~:)"] }, "brackets": [ [ @@ -20,4 +17,4 @@ ")" ] ] -} \ No newline at end of file +} diff --git a/languages/xquery/xquery.tmLanguage b/languages/xquery/xquery.tmLanguage index e4c5a42..24a2f1a 100644 --- a/languages/xquery/xquery.tmLanguage +++ b/languages/xquery/xquery.tmLanguage @@ -768,7 +768,7 @@ doublequotedString begin - (?<![-_a-zA-Z0-9:]>)\s*"(?!\s*</[-_a-zA-Z0-9:]) + (?<![-_a-zA-Z0-9:'"]>)\s*"(?![\w\s()']*</[-_a-zA-Z0-9:]) beginCaptures 0 @@ -929,7 +929,7 @@ singlequotedString begin - (?<![-_a-zA-Z0-9:]>)\s*'(?!\s*</[-_a-zA-Z0-9:]) + (?<![-_a-zA-Z0-9:'"]>)\s*'(?![\w\s()"]*</[-_a-zA-Z0-9:]) beginCaptures 0 diff --git a/package-lock.json b/package-lock.json index d971c4d..fe9ebfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "xml", - "version": "2.0.0-preview.2", + "version": "2.3.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9b5ef17..b9e8013 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "onCommand:xmlTools.evaluateXPath", "onCommand:xmlTools.executeXQuery", "onCommand:xmlTools.formatAsXml", + "onCommand:xmlTools.textToXml", + "onCommand:xmlTools.xmlToText", "onCommand:xmlTools.minifyXml", "onLanguage:xml", "onLanguage:xquery", @@ -55,6 +57,14 @@ "command": "xmlTools.formatAsXml", "title": "XML Tools: Format as XML" }, + { + "command": "xmlTools.textToXml", + "title": "XML Tools: Convert text to XML (<> -> <>)" + }, + { + "command": "xmlTools.xmlToText", + "title": "XML Tools: Convert XML to text (<> -> <>)" + }, { "command": "xmlTools.getCurrentXPath", "title": "XML Tools: Get Current XPath" @@ -183,6 +193,16 @@ } ], "languages": [ + { + "id": "xml", + "extensions": [ + ".config", + ".csproj", + ".xml", + ".xsd", + ".xsl" + ] + }, { "id": "xquery", "aliases": [ @@ -257,4 +277,4 @@ "xpath": "0.0.27", "xqlint": "^0.4.1" } -} +} \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts index 6261aee..07d969e 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,6 +2,8 @@ export namespace commands { export const evaluateXPath = "xmlTools.evaluateXPath"; export const executeXQuery = "xmlTools.executeXQuery"; export const formatAsXml = "xmlTools.formatAsXml"; + export const xmlToText = "xmlTools.xmlToText"; + export const textToXml = "xmlTools.textToXml"; export const getCurrentXPath = "xmlTools.getCurrentXPath"; export const minifyXml = "xmlTools.minifyXml"; } diff --git a/src/extension.ts b/src/extension.ts index ee975af..17b41b2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,12 +1,12 @@ import { commands, languages, window, workspace, ExtensionContext, Memento, - TextEditor, TextEditorSelectionChangeEvent, TextEditorSelectionChangeKind + TextEditor, TextEditorSelectionChangeEvent, TextEditorSelectionChangeKind, DiagnosticCollection } from "vscode"; import { createDocumentSelector, ExtensionState, Configuration } from "./common"; import { XQueryCompletionItemProvider } from "./completion"; import { XmlFormatterFactory, XmlFormattingEditProvider } from "./formatting"; -import { formatAsXml, minifyXml } from "./formatting/commands"; +import { formatAsXml, minifyXml, xmlToText, textToXml } from "./formatting/commands"; import { XQueryLinter } from "./linting"; import { XmlTreeDataProvider } from "./tree-view"; import { evaluateXPath, getCurrentXPath } from "./xpath/commands"; @@ -14,6 +14,8 @@ import { executeXQuery } from "./xquery-execution/commands"; import * as constants from "./constants"; +let diagnosticCollectionXQuery: DiagnosticCollection; + export function activate(context: ExtensionContext) { ExtensionState.configure(context); @@ -30,13 +32,17 @@ export function activate(context: ExtensionContext) { context.subscriptions.push( commands.registerTextEditorCommand(constants.commands.formatAsXml, formatAsXml), + commands.registerTextEditorCommand(constants.commands.xmlToText, xmlToText), + commands.registerTextEditorCommand(constants.commands.textToXml, textToXml), commands.registerTextEditorCommand(constants.commands.minifyXml, minifyXml), languages.registerDocumentFormattingEditProvider(xmlXsdDocSelector, xmlFormattingEditProvider), languages.registerDocumentRangeFormattingEditProvider(xmlXsdDocSelector, xmlFormattingEditProvider) ); /* Linting Features */ + diagnosticCollectionXQuery = languages.createDiagnosticCollection(constants.diagnosticCollections.xquery); context.subscriptions.push( + diagnosticCollectionXQuery, window.onDidChangeActiveTextEditor(_handleChangeActiveTextEditor), window.onDidChangeTextEditorSelection(_handleChangeTextEditorSelection) ); @@ -85,9 +91,7 @@ function _handleContextChange(editor: TextEditor): void { switch (editor.document.languageId) { case constants.languageIds.xquery: - languages - .createDiagnosticCollection(constants.diagnosticCollections.xquery) - .set(editor.document.uri, new XQueryLinter().lint(editor.document.getText())); + diagnosticCollectionXQuery.set(editor.document.uri, new XQueryLinter().lint(editor.document.getText())); break; } } diff --git a/src/formatting/commands/index.ts b/src/formatting/commands/index.ts index 3e12b40..3dc6ea2 100644 --- a/src/formatting/commands/index.ts +++ b/src/formatting/commands/index.ts @@ -1,2 +1,4 @@ export * from "./formatAsXml"; export * from "./minifyXml"; +export * from "./xmlToText"; +export * from "./textToXml"; diff --git a/src/formatting/commands/textToXml.ts b/src/formatting/commands/textToXml.ts new file mode 100644 index 0000000..de2884f --- /dev/null +++ b/src/formatting/commands/textToXml.ts @@ -0,0 +1,26 @@ +import { workspace } from "vscode"; +import { ProviderResult, Range, TextEdit, TextEditor, Selection } 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 textToXml(textEditor: TextEditor): void { + textEditor.edit(textEdit => { + const selections = textEditor.selections; + selections.forEach(selection => { + if (selection.isEmpty) { + selection = new Selection( + textEditor.document.positionAt(0), + textEditor.document.positionAt(textEditor.document.getText().length) + ); + } + const txt = textEditor.document.getText(new Range(selection.start, selection.end)); + const transformed = txt.replace(/</g, "<").replace(/>/g, ">"); + textEdit.replace(selection, transformed); + }); + }); +} diff --git a/src/formatting/commands/xmlToText.ts b/src/formatting/commands/xmlToText.ts new file mode 100644 index 0000000..f3d63ac --- /dev/null +++ b/src/formatting/commands/xmlToText.ts @@ -0,0 +1,26 @@ +import { workspace } from "vscode"; +import { ProviderResult, Range, TextEdit, TextEditor, Selection } 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 xmlToText(textEditor: TextEditor): void { + textEditor.edit(textEdit => { + const selections = textEditor.selections; + selections.forEach(selection => { + if (selection.isEmpty) { + selection = new Selection( + textEditor.document.positionAt(0), + textEditor.document.positionAt(textEditor.document.getText().length) + ); + } + const txt = textEditor.document.getText(new Range(selection.start, selection.end)); + const transformed = txt.replace(//g, ">"); + textEdit.replace(selection, transformed); + }); + }); +} diff --git a/src/formatting/formatters/v2-xml-formatter.ts b/src/formatting/formatters/v2-xml-formatter.ts index f7934d3..d28a731 100644 --- a/src/formatting/formatters/v2-xml-formatter.ts +++ b/src/formatting/formatters/v2-xml-formatter.ts @@ -20,7 +20,7 @@ export class V2XmlFormatter implements XmlFormatter { 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 + xml = xml.replace(/(?!="]\s+[^ <>="]+=(?![^<]*?\]\]>)/g, (match: string) => { // spaces between the node name and the first attribute return match.replace(/\s+/g, " "); }); @@ -29,7 +29,7 @@ export class V2XmlFormatter implements XmlFormatter { let output = ""; - let indentLevel = 0; + let indentLevel = options.initialIndentLevel || 0; let attributeQuote = ""; let lineBreakSpree = false; let lastWordCharacter: string | undefined; @@ -194,7 +194,7 @@ export class V2XmlFormatter implements XmlFormatter { && cc === "/" && pc !== " " && options.enforcePrettySelfClosingTagOnFormat) { - output += " /"; + output += " /"; } // exiting StartTag or StartTag.StartTagName, entering Text diff --git a/src/formatting/xml-formatting-edit-provider.ts b/src/formatting/xml-formatting-edit-provider.ts index 6a7f9f2..a64c05c 100644 --- a/src/formatting/xml-formatting-edit-provider.ts +++ b/src/formatting/xml-formatting-edit-provider.ts @@ -22,10 +22,43 @@ export class XmlFormattingEditProvider implements DocumentFormattingEditProvider } provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { - let xml = document.getText(range); + const allXml = document.getText(); + let selectedXml = document.getText(range); + const extFormattingOptions = XmlFormattingOptionsFactory.getXmlFormattingOptions(options, document); - xml = this.xmlFormatter.formatXml(xml, XmlFormattingOptionsFactory.getXmlFormattingOptions(options, document)); + const selectionStartOffset = document.offsetAt(range.start); + let tabCount = 0; + let spaceCount = 0; - return [ TextEdit.replace(range, xml) ]; + for (let i = (selectionStartOffset - 1); i >= 0; i--) { + const cc = allXml.charAt(i); + + if (/\t/.test(cc)) { + tabCount++; + } + + else if (/ /.test(cc)) { + spaceCount++; + } + + else { + break; + } + } + + if (options.insertSpaces) { + extFormattingOptions.initialIndentLevel = Math.ceil(spaceCount / (options.tabSize || 1)); + } + + else { + extFormattingOptions.initialIndentLevel = tabCount; + } + + selectedXml = this.xmlFormatter.formatXml(selectedXml, extFormattingOptions); + + // we need to remove the leading whitespace because the formatter will add an indent before the first element + selectedXml = selectedXml.replace(/^\s+/, ""); + + return [TextEdit.replace(range, selectedXml)]; } } diff --git a/src/formatting/xml-formatting-options.ts b/src/formatting/xml-formatting-options.ts index 06782a1..7c5185a 100644 --- a/src/formatting/xml-formatting-options.ts +++ b/src/formatting/xml-formatting-options.ts @@ -10,6 +10,7 @@ export interface XmlFormattingOptions { removeCommentsOnMinify: boolean; splitAttributesOnFormat: boolean; splitXmlnsOnFormat: boolean; + initialIndentLevel?: number; } export class XmlFormattingOptionsFactory { @@ -20,7 +21,8 @@ export class XmlFormattingOptionsFactory { newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n", removeCommentsOnMinify: Configuration.removeCommentsOnMinify(document.uri), splitAttributesOnFormat: Configuration.splitAttributesOnFormat(document.uri), - splitXmlnsOnFormat: Configuration.splitXmlnsOnFormat(document.uri) + splitXmlnsOnFormat: Configuration.splitXmlnsOnFormat(document.uri), + initialIndentLevel: 0 }; } } diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 8c3627b..675aa6b 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -92,6 +92,9 @@ describe("V2XmlFormatter", () => { testFormatter(xmlFormatter, options, "issue-200"); }); + it("should not remove spaces between the node name and the first attribute within CDATA", () => { + testFormatter(xmlFormatter, options, "issue-227"); + }); }); }); diff --git a/src/test/test-data/issue-227.formatted.xml b/src/test/test-data/issue-227.formatted.xml new file mode 100644 index 0000000..d4ead94 --- /dev/null +++ b/src/test/test-data/issue-227.formatted.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/src/test/test-data/issue-227.unformatted.xml b/src/test/test-data/issue-227.unformatted.xml new file mode 100644 index 0000000..b55e3f3 --- /dev/null +++ b/src/test/test-data/issue-227.unformatted.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file