Begin Implementing V2 Formatter

This commit is contained in:
Josh Johnson 2018-01-28 01:19:06 -05:00
parent 5526923772
commit 8f0bf58462
5 changed files with 210 additions and 57 deletions

View File

@ -88,7 +88,7 @@
}, },
"xmlTools.xmlFormatterImplementation": { "xmlTools.xmlFormatterImplementation": {
"type": "string", "type": "string",
"default": "classic", "default": "v2",
"description": "Supported XML Formatters: classic", "description": "Supported XML Formatters: classic",
"scope": "resource" "scope": "resource"
}, },
@ -122,6 +122,14 @@
"key": "ctrl+shift+alt+b", "key": "ctrl+shift+alt+b",
"command": "xmlTools.formatXml" "command": "xmlTools.formatXml"
} }
],
"languages": [
{
"id": "xquery",
"aliases": ["XQuery", "xquery"],
"extensions": [".xq",".xql",".xqm",".xqy",".xquery"],
"configuration": "./languages/xquery/xquery.json"
}
] ]
}, },
"scripts": { "scripts": {

View File

@ -1,29 +1,33 @@
import { workspace } from "vscode"; import { languages, workspace } from "vscode";
import { ExtensionContext, WorkspaceConfiguration } 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"; import * as constants from "./constants";
const onActivateHandlers: OnActivateHandler[] = [];
const onDeactivateHandlers: OnDeactivateHandler[] = [];
export function activate(context: ExtensionContext) { 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<string>("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() { 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;

View File

@ -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. <element>text</element>)
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
}

View File

@ -1,43 +1,12 @@
import { commands, languages, workspace } from "vscode"; import { workspace } from "vscode";
import { import {
CancellationToken, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, ExtensionContext, CancellationToken, DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider, EndOfLine,
FormattingOptions, ProviderResult, Range, TextDocument, TextEdit, TextEditor, WorkspaceConfiguration FormattingOptions, ProviderResult, Range, TextDocument, TextEdit, WorkspaceConfiguration
} from "vscode"; } from "vscode";
import * as constants from "../constants"; import * as constants from "../constants";
import * as extension from "../extension";
import { XmlFormatter } from "./xml-formatter"; import { XmlFormatter } from "./xml-formatter";
import { ClassicXmlFormatter } from "./formatters/classic-xml-formatter";
extension.onActivate((context: ExtensionContext, config: WorkspaceConfiguration) => {
const xmlFormatterImplementationSetting = config.get<string>("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 { export class XmlFormattingEditProvider implements DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider {
constructor( constructor(
@ -60,7 +29,7 @@ export class XmlFormattingEditProvider implements DocumentFormattingEditProvider
xml = this.xmlFormatter.formatXml(xml, { xml = this.xmlFormatter.formatXml(xml, {
editorOptions: options, editorOptions: options,
newLine: document.eol.toString(), newLine: (document.eol === EndOfLine.CRLF) ? "\r\n" : "\n",
removeCommentsOnMinify: this.workspaceConfiguration.get<boolean>("removeCommentsOnMinify"), removeCommentsOnMinify: this.workspaceConfiguration.get<boolean>("removeCommentsOnMinify"),
splitXmlnsOnFormat: this.workspaceConfiguration.get<boolean>("splitXmlnsOnFormat") splitXmlnsOnFormat: this.workspaceConfiguration.get<boolean>("splitXmlnsOnFormat")
}); });

View File

@ -77,8 +77,6 @@
"one-line": [ "one-line": [
true, true,
"check-open-brace", "check-open-brace",
"check-catch",
"check-else",
"check-whitespace" "check-whitespace"
], ],
"prefer-const": true, "prefer-const": true,