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": {
 | 
					                "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…
	
	Add table
		
		Reference in a new issue