Begin Implementing V2 Formatter
This commit is contained in:
parent
5526923772
commit
8f0bf58462
10
package.json
10
package.json
@ -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": {
|
||||||
|
@ -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;
|
|
||||||
|
174
src/formatting/formatters/v2-xml-formatter.ts
Normal file
174
src/formatting/formatters/v2-xml-formatter.ts
Normal 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
|
||||||
|
}
|
@ -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")
|
||||||
});
|
});
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user