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": {
|
||||
"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": {
|
||||
|
@ -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<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() {
|
||||
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 {
|
||||
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<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 {
|
||||
|
||||
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<boolean>("removeCommentsOnMinify"),
|
||||
splitXmlnsOnFormat: this.workspaceConfiguration.get<boolean>("splitXmlnsOnFormat")
|
||||
});
|
||||
|
@ -77,8 +77,6 @@
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"prefer-const": true,
|
||||
|
Loading…
Reference in New Issue
Block a user