From 8f0bf58462d2713ce4ea16d3c5a487770087b6c3 Mon Sep 17 00:00:00 2001 From: Josh Johnson Date: Sun, 28 Jan 2018 01:19:06 -0500 Subject: [PATCH] Begin Implementing V2 Formatter --- package.json | 10 +- src/extension.ts | 42 +++-- src/formatting/formatters/v2-xml-formatter.ts | 174 ++++++++++++++++++ .../xml-formatting-edit-provider.ts | 39 +--- tslint.json | 2 - 5 files changed, 210 insertions(+), 57 deletions(-) create mode 100644 src/formatting/formatters/v2-xml-formatter.ts diff --git a/package.json b/package.json index daf103d..794e3ef 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ }, "xmlTools.xmlFormatterImplementation": { "type": "string", - "default": "classic", + "default": "v2", "description": "Supported XML Formatters: classic", "scope": "resource" }, @@ -122,6 +122,14 @@ "key": "ctrl+shift+alt+b", "command": "xmlTools.formatXml" } + ], + "languages": [ + { + "id": "xquery", + "aliases": ["XQuery", "xquery"], + "extensions": [".xq",".xql",".xqm",".xqy",".xquery"], + "configuration": "./languages/xquery/xquery.json" + } ] }, "scripts": { diff --git a/src/extension.ts b/src/extension.ts index f1ea7ae..6c9decf 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,29 +1,33 @@ -import { workspace } from "vscode"; +import { languages, workspace } from "vscode"; import { ExtensionContext, WorkspaceConfiguration } from "vscode"; +import { XmlFormatter } from "./formatting/xml-formatter"; +import { XmlFormattingEditProvider } from "./formatting/xml-formatting-edit-provider"; +import { ClassicXmlFormatter } from "./formatting/formatters/classic-xml-formatter"; +import { V2XmlFormatter } from "./formatting/formatters/v2-xml-formatter"; + import * as constants from "./constants"; -const onActivateHandlers: OnActivateHandler[] = []; -const onDeactivateHandlers: OnDeactivateHandler[] = []; - export function activate(context: ExtensionContext) { - const workspaceConfiguration = workspace.getConfiguration(constants.extensionPrefix); + const config = workspace.getConfiguration(constants.extensionPrefix); - onActivateHandlers.forEach(x => x(context, workspaceConfiguration)); + /* Formatting Features */ + const xmlFormatterImplementationSetting = config.get("xmlFormatterImplementation"); + let xmlFormatterImplementation: XmlFormatter; + + switch (xmlFormatterImplementationSetting) { + case "v2": xmlFormatterImplementation = new V2XmlFormatter(); break; + case "classic": default: xmlFormatterImplementation = new ClassicXmlFormatter(); break; + } + + const xmlFormattingEditProvider = new XmlFormattingEditProvider(config, xmlFormatterImplementation); + + context.subscriptions.push( + languages.registerDocumentFormattingEditProvider("xml", xmlFormattingEditProvider), + languages.registerDocumentRangeFormattingEditProvider("xml", xmlFormattingEditProvider) + ); } export function deactivate() { - onDeactivateHandlers.forEach(x => x()); + // do nothing } - -export function onActivate(handler: OnActivateHandler): void { - onActivateHandlers.push(handler); -} - -export function onDeactivate(handler: OnDeactivateHandler): void { - onDeactivateHandlers.push(handler); -} - -export type OnActivateHandler = (context: ExtensionContext, config: WorkspaceConfiguration) => void; - -export type OnDeactivateHandler = () => void; diff --git a/src/formatting/formatters/v2-xml-formatter.ts b/src/formatting/formatters/v2-xml-formatter.ts new file mode 100644 index 0000000..5ce20fa --- /dev/null +++ b/src/formatting/formatters/v2-xml-formatter.ts @@ -0,0 +1,174 @@ +import { XmlFormatter } from "../xml-formatter"; +import { XmlFormattingOptions } from "../xml-formatting-options"; +import { ClassicXmlFormatter } from "./classic-xml-formatter"; + +/* tslint:disable no-use-before-declare */ +export class V2XmlFormatter implements XmlFormatter { + formatXml(xml: string, options: XmlFormattingOptions): string { + xml = this.minifyXml(xml, options); + + 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) { + 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 + if (pc === "/") { + indentLevel--; + } + + output += ">"; + + lastNonTextLocation = location; + location = Location.Text; + } + + // entering EndTag + else if (location === Location.Text && cc === "<" && nc === "/") { + indentLevel--; + + // 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. text) + 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); + } +} + +enum Location { + Attribute, + AttributeValue, + CData, + Comment, + EndTag, + SpecialTag, + StartTag, + StartTagName, + Text +} diff --git a/src/formatting/xml-formatting-edit-provider.ts b/src/formatting/xml-formatting-edit-provider.ts index 6ed9646..b744a01 100644 --- a/src/formatting/xml-formatting-edit-provider.ts +++ b/src/formatting/xml-formatting-edit-provider.ts @@ -1,43 +1,12 @@ -import { commands, languages, workspace } from "vscode"; +import { workspace } from "vscode"; import { - CancellationToken, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, ExtensionContext, - FormattingOptions, ProviderResult, Range, TextDocument, TextEdit, TextEditor, WorkspaceConfiguration + CancellationToken, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, EndOfLine, + FormattingOptions, ProviderResult, Range, TextDocument, TextEdit, WorkspaceConfiguration } from "vscode"; import * as constants from "../constants"; -import * as extension from "../extension"; import { XmlFormatter } from "./xml-formatter"; -import { ClassicXmlFormatter } from "./formatters/classic-xml-formatter"; - -extension.onActivate((context: ExtensionContext, config: WorkspaceConfiguration) => { - const xmlFormatterImplementationSetting = config.get("xmlFormatterImplementation"); - let xmlFormatterImplementation: XmlFormatter; - - switch (xmlFormatterImplementationSetting) { - case "classic": - default: xmlFormatterImplementation = new ClassicXmlFormatter(); break; - } - - // tslint:disable-next-line:no-use-before-declare - const xmlFormattingEditProvider = new XmlFormattingEditProvider(config, xmlFormatterImplementation); - - const formatAsXmlCommand = commands.registerTextEditorCommand("xmlTools.formatAsXml", (textEditor) => { - // TODO: implement command - }); - - const minifyXmlCommand = commands.registerTextEditorCommand("xmlTools.minifyXml", (textEditor: TextEditor) => { - // TODO: implement command - }); - - context.subscriptions.push( - formatAsXmlCommand, - minifyXmlCommand, - languages.registerDocumentFormattingEditProvider("xml", xmlFormattingEditProvider), - languages.registerDocumentRangeFormattingEditProvider("xml", xmlFormattingEditProvider) - ); -}); - export class XmlFormattingEditProvider implements DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider { constructor( @@ -60,7 +29,7 @@ export class XmlFormattingEditProvider implements DocumentFormattingEditProvider xml = this.xmlFormatter.formatXml(xml, { editorOptions: options, - newLine: document.eol.toString(), + newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n", removeCommentsOnMinify: this.workspaceConfiguration.get("removeCommentsOnMinify"), splitXmlnsOnFormat: this.workspaceConfiguration.get("splitXmlnsOnFormat") }); diff --git a/tslint.json b/tslint.json index 34d7b7b..38f64fa 100644 --- a/tslint.json +++ b/tslint.json @@ -77,8 +77,6 @@ "one-line": [ true, "check-open-brace", - "check-catch", - "check-else", "check-whitespace" ], "prefer-const": true,