Begin Implementing V2 Formatter
This commit is contained in:
		
							parent
							
								
									5526923772
								
							
						
					
					
						commit
						8f0bf58462
					
				
					 5 changed files with 210 additions and 57 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		
		Reference in a new issue