From 5330c655963e91ea2d5f51a0d70fba5486ff68e3 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 3 Jun 2021 10:24:22 +0200 Subject: [PATCH] Feature request: buttons to associate XML documents with schema files Fixes #395 Signed-off-by: azerr --- .../eclipse/lemminx/XMLWorkspaceService.java | 2 +- .../contentmodel/ContentModelPlugin.java | 11 +++ .../commands/AssociateGrammarCommand.java | 53 ++++++++++ .../XMLValidationAllFilesCommand.java | 4 +- .../commands/XMLValidationFileCommand.java | 5 +- .../ContentModelCodeLensParticipant.java | 74 ++++++++++++++ .../participants/DTDErrorCode.java | 1 - .../NoGrammarConstraintsCodeAction.java | 99 ++++++++++++------- .../AbstractDOMDocumentCommandHandler.java | 17 ++-- .../commands/IXMLCommandService.java | 4 +- .../commands/CommandServiceTest.java | 2 +- 11 files changed, 220 insertions(+), 52 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/AssociateGrammarCommand.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCodeLensParticipant.java diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java index b9651d72a3..38720a3f0d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLWorkspaceService.java @@ -63,7 +63,7 @@ public CompletableFuture executeCommand(ExecuteCommandParams params) { } return CompletableFutures.computeAsync(cancelChecker -> { try { - return handler.executeCommand(params, cancelChecker); + return handler.executeCommand(params, xmlLanguageServer.getSharedSettings(), cancelChecker); } catch (Exception e) { if (e instanceof ResponseErrorException) { throw (ResponseErrorException) e; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java index 40f6f1f913..6f95811ab8 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/ContentModelPlugin.java @@ -15,10 +15,12 @@ import java.util.Objects; import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.commands.AssociateGrammarCommand; import org.eclipse.lemminx.extensions.contentmodel.commands.XMLValidationAllFilesCommand; import org.eclipse.lemminx.extensions.contentmodel.commands.XMLValidationFileCommand; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCodeActionParticipant; +import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCodeLensParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelCompletionParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelDocumentLinkParticipant; import org.eclipse.lemminx.extensions.contentmodel.participants.ContentModelHoverParticipant; @@ -36,6 +38,7 @@ import org.eclipse.lemminx.services.extensions.ITypeDefinitionParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant; import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.save.ISaveContext; @@ -68,6 +71,8 @@ public class ContentModelPlugin implements IXMLExtension { private ContentModelSymbolsProviderParticipant symbolsProviderParticipant; + private final ICodeLensParticipant codeLensParticipant; + ContentModelManager contentModelManager; private ContentModelSettings cmSettings; @@ -80,6 +85,7 @@ public ContentModelPlugin() { diagnosticsParticipant = new ContentModelDiagnosticsParticipant(this); codeActionParticipant = new ContentModelCodeActionParticipant(); typeDefinitionParticipant = new ContentModelTypeDefinitionParticipant(); + codeLensParticipant = new ContentModelCodeLensParticipant(); } @Override @@ -176,6 +182,7 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerTypeDefinitionParticipant(typeDefinitionParticipant); symbolsProviderParticipant = new ContentModelSymbolsProviderParticipant(contentModelManager); registry.registerSymbolsProviderParticipant(symbolsProviderParticipant); + registry.registerCodeLensParticipant(codeLensParticipant); // Register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); @@ -186,6 +193,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { new XMLValidationFileCommand(contentModelManager, documentProvider, validationService)); commandService.registerCommand(XMLValidationAllFilesCommand.COMMAND_ID, new XMLValidationAllFilesCommand(contentModelManager, documentProvider, validationService)); + commandService.registerCommand(AssociateGrammarCommand.COMMAND_ID, + new AssociateGrammarCommand(documentProvider)); } } @@ -198,12 +207,14 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterDocumentLinkParticipant(documentLinkParticipant); registry.unregisterTypeDefinitionParticipant(typeDefinitionParticipant); registry.unregisterSymbolsProviderParticipant(symbolsProviderParticipant); + registry.unregisterCodeLensParticipant(codeLensParticipant); // Un-register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); if (commandService != null) { commandService.unregisterCommand(XMLValidationFileCommand.COMMAND_ID); commandService.unregisterCommand(XMLValidationAllFilesCommand.COMMAND_ID); + commandService.unregisterCommand(AssociateGrammarCommand.COMMAND_ID); } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/AssociateGrammarCommand.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/AssociateGrammarCommand.java new file mode 100644 index 0000000000..caf24612dc --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/AssociateGrammarCommand.java @@ -0,0 +1,53 @@ +package org.eclipse.lemminx.extensions.contentmodel.commands; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.NoGrammarConstraintsCodeAction; +import org.eclipse.lemminx.services.IXMLDocumentProvider; +import org.eclipse.lemminx.services.extensions.commands.AbstractDOMDocumentCommandHandler; +import org.eclipse.lemminx.services.extensions.commands.ArgumentsUtils; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lsp4j.ExecuteCommandParams; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +public class AssociateGrammarCommand extends AbstractDOMDocumentCommandHandler { + + public static final String COMMAND_ID = "xml.associate.grammar.insert"; + + public AssociateGrammarCommand(IXMLDocumentProvider documentProvider) { + super(documentProvider); + } + + public enum GrammarBindingType { + + XSD("xsd"), // + DTD("dtd"), // + XML_MODEL("xml-model"); + + private String name; + + private GrammarBindingType(String name) { + this.name = name != null ? name : name(); + } + + public String getName() { + return name; + } + } + + @Override + protected Object executeCommand(DOMDocument document, ExecuteCommandParams params, SharedSettings sharedSettings, + CancelChecker cancelChecker) throws Exception { + String grammarURI = ArgumentsUtils.getArgAt(params, 1, String.class); + String bindingType = ArgumentsUtils.getArgAt(params, 2, String.class); + + if (GrammarBindingType.XSD.getName().equals(bindingType)) { + return NoGrammarConstraintsCodeAction.createXSINoNamespaceSchemaLocationEdit(grammarURI, document); + } else if (GrammarBindingType.DTD.getName().equals(bindingType)) { + return NoGrammarConstraintsCodeAction.createDocTypeEdit(grammarURI, document, sharedSettings); + } else if (GrammarBindingType.XML_MODEL.getName().equals(bindingType)) { + return NoGrammarConstraintsCodeAction.createXmlModelEdit(grammarURI, document, sharedSettings); + } + return null; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationAllFilesCommand.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationAllFilesCommand.java index ef05dd92a1..0a8d39900a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationAllFilesCommand.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationAllFilesCommand.java @@ -18,6 +18,7 @@ import org.eclipse.lemminx.services.IXMLDocumentProvider; import org.eclipse.lemminx.services.IXMLValidationService; import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService.IDelegateCommandHandler; +import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.jsonrpc.CancelChecker; @@ -52,7 +53,8 @@ public XMLValidationAllFilesCommand(ContentModelManager contentModelManager, IXM } @Override - public Object executeCommand(ExecuteCommandParams params, CancelChecker cancelChecker) throws Exception { + public Object executeCommand(ExecuteCommandParams params, SharedSettings sharedSettings, + CancelChecker cancelChecker) throws Exception { // 1. clear the Xerces grammar pool // (used by the Xerces validation) and the content model documents cache (used // by the XML completion/hover based on the grammar) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java index b6d4d56698..06c07d2cc2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/commands/XMLValidationFileCommand.java @@ -16,6 +16,7 @@ import org.eclipse.lemminx.services.IXMLDocumentProvider; import org.eclipse.lemminx.services.IXMLValidationService; import org.eclipse.lemminx.services.extensions.commands.AbstractDOMDocumentCommandHandler; +import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.jsonrpc.CancelChecker; @@ -48,8 +49,8 @@ public XMLValidationFileCommand(ContentModelManager contentModelManager, IXMLDoc } @Override - protected Object executeCommand(DOMDocument document, ExecuteCommandParams params, CancelChecker cancelChecker) - throws Exception { + protected Object executeCommand(DOMDocument document, ExecuteCommandParams params, SharedSettings sharedSettings, + CancelChecker cancelChecker) throws Exception { // 1. remove the referenced grammar in the XML file from the Xerces grammar pool // (used by the Xerces validation) and the content model documents cache (used // by the XML completion/hover based on the grammar) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCodeLensParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCodeLensParticipant.java new file mode 100644 index 0000000000..ec0eeee61d --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCodeLensParticipant.java @@ -0,0 +1,74 @@ +/******************************************************************************* +* Copyright (c) 2021 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ + +package org.eclipse.lemminx.extensions.contentmodel.participants; + +import java.util.Arrays; +import java.util.List; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.extensions.contentmodel.commands.AssociateGrammarCommand; +import org.eclipse.lemminx.extensions.contentmodel.commands.AssociateGrammarCommand.GrammarBindingType; +import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant; +import org.eclipse.lemminx.services.extensions.codelens.ICodeLensRequest; +import org.eclipse.lemminx.utils.XMLPositionUtility; +import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.Command; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +/** + * When XML file is not associated to a grammar (XSD, DTD), this class generates + * several CodeLenses on the root of the DOM Document: + * + *
    + *
  • [Bind with XSD] : click on this Codelens open a folder dialog to select + * the XSD to bind.
  • + *
  • [Bind with DTD] : click on this Codelens open a folder dialog to select + * the DTD to bind.
  • + *
  • [Bind with xml-model] : click on this Codelens open a folder dialog to + * select the XSD, DTD to bind.
  • + *
+ * + *

+ * Once the LSP client select the DTD, XSD, it should call the + * {@link AssociateGrammarCommand} to generate the proper syntax for binding. + *

+ * + */ +public class ContentModelCodeLensParticipant implements ICodeLensParticipant { + + private static final String COMMAND_ID = "xml.associate.grammar.selectFile"; + + @Override + public void doCodeLens(ICodeLensRequest request, List lenses, CancelChecker cancelChecker) { + DOMDocument document = request.getDocument(); + DOMElement documentElement = document.getDocumentElement(); + if (documentElement == null || document.hasGrammar()) { + return; + } + String documentURI = document.getDocumentURI(); + Range range = XMLPositionUtility.selectRootStartTag(document); + + lenses.add(createAssociateLens(documentURI, "Bind with XSD", GrammarBindingType.XSD.getName(), range)); + lenses.add(createAssociateLens(documentURI, "Bind with DTD", GrammarBindingType.DTD.getName(), range)); + lenses.add( + createAssociateLens(documentURI, "Bind with xml-model", GrammarBindingType.XML_MODEL.getName(), range)); + } + + private static CodeLens createAssociateLens(String documentURI, String title, String bindingType, Range range) { + Command command = new Command(title, COMMAND_ID, Arrays.asList(documentURI, bindingType)); + return new CodeLens(range, command, null); + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java index 9ca2845b4e..64cfe221b2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/DTDErrorCode.java @@ -20,7 +20,6 @@ import org.apache.xerces.xni.XMLLocator; import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lemminx.dom.DOMDocument; -import org.eclipse.lemminx.dom.DOMDocumentType; import org.eclipse.lemminx.dom.DOMElement; import org.eclipse.lemminx.dom.DOMRange; import org.eclipse.lemminx.extensions.contentmodel.participants.codeactions.ElementDeclUnterminatedCodeAction; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java index ce5ced722b..30e690f564 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/NoGrammarConstraintsCodeAction.java @@ -55,9 +55,6 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen } FileContentGeneratorManager generator = componentProvider.getComponent(FileContentGeneratorManager.class); - String delimiter = document.lineDelimiter(0); - int beforeTagOffset = documentElement.getStartTagOpenOffset(); - int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length(); // ---------- XSD @@ -67,23 +64,18 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen // xsi:noNamespaceSchemaLocation // Create code action to create the XSD file with the generated XSD content - String insertText = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" - + document.getTextDocument().lineDelimiter(0); - insertText += " xsi:noNamespaceSchemaLocation=\"" + schemaFileName + "\""; + TextDocumentEdit noNamespaceSchemaLocationEdit = createXSINoNamespaceSchemaLocationEdit(schemaFileName, + document); CodeAction noNamespaceSchemaLocationAction = createGrammarFileAndBindIt( "Generate '" + schemaFileName + "' and bind with xsi:noNamespaceSchemaLocation", schemaURI, - schemaTemplate, insertText, afterTagOffset, document, diagnostic); + schemaTemplate, noNamespaceSchemaLocationEdit, diagnostic); codeActions.add(noNamespaceSchemaLocationAction); // xml-model - XMLBuilder xsdWithXmlModel = new XMLBuilder(sharedSettings, null, delimiter); - xsdWithXmlModel.startPrologOrPI("xml-model"); - xsdWithXmlModel.addSingleAttribute("href", schemaFileName, true); - xsdWithXmlModel.endPrologOrPI(); - xsdWithXmlModel.linefeed(); + TextDocumentEdit xsdWithXMLModelEdit = createXmlModelEdit(schemaFileName, document, sharedSettings); CodeAction xsdWithXmlModelAction = createGrammarFileAndBindIt( "Generate '" + schemaFileName + "' and bind with xml-model", schemaURI, schemaTemplate, - xsdWithXmlModel.toString(), beforeTagOffset, document, diagnostic); + xsdWithXMLModelEdit, diagnostic); codeActions.add(xsdWithXmlModelAction); // ---------- DTD @@ -93,28 +85,17 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen String dtdTemplate = generator.generate(document, sharedSettings, new DTDGeneratorSettings()); // - XMLBuilder docType = new XMLBuilder(sharedSettings, null, delimiter); - docType.startDoctype(); - docType.addParameter(documentElement.getLocalName()); - docType.addContent(" SYSTEM \""); - docType.addContent(dtdFileName); - docType.addContent("\""); - docType.endDoctype(); - docType.linefeed(); + TextDocumentEdit dtdWithDocType = createDocTypeEdit(dtdFileName, document, sharedSettings); CodeAction docTypeAction = createGrammarFileAndBindIt( - "Generate '" + dtdFileName + "' and bind with DOCTYPE", dtdURI, dtdTemplate, docType.toString(), - beforeTagOffset, document, diagnostic); + "Generate '" + dtdFileName + "' and bind with DOCTYPE", dtdURI, dtdTemplate, dtdWithDocType, + diagnostic); codeActions.add(docTypeAction); // xml-model - XMLBuilder dtdWithXmlModel = new XMLBuilder(sharedSettings, null, delimiter); - dtdWithXmlModel.startPrologOrPI("xml-model"); - dtdWithXmlModel.addSingleAttribute("href", dtdFileName, true); - dtdWithXmlModel.endPrologOrPI(); - dtdWithXmlModel.linefeed(); + TextDocumentEdit dtdWithXMLModelEdit = createXmlModelEdit(dtdFileName, document, sharedSettings); CodeAction dtdWithXmlModelAction = createGrammarFileAndBindIt( - "Generate '" + dtdFileName + "' and bind with xml-model", dtdURI, dtdTemplate, - dtdWithXmlModel.toString(), beforeTagOffset, document, diagnostic); + "Generate '" + dtdFileName + "' and bind with xml-model", dtdURI, dtdTemplate, dtdWithXMLModelEdit, + diagnostic); codeActions.add(dtdWithXmlModelAction); } catch (BadLocationException e) { @@ -153,18 +134,60 @@ static String getFileName(String schemaURI) { return new File(schemaURI).getName(); } - private static CodeAction createGrammarFileAndBindIt(String title, String grammarURI, String grammarContent, - String insertText, int insertOffset, DOMDocument document, Diagnostic diagnostic) - throws BadLocationException { - Position position = document.positionAt(insertOffset); - TextDocumentEdit insertEdit = CodeActionFactory.insertEdit(insertText, position, document.getTextDocument()); - return createGrammarFileAndBindIt(title, grammarURI, grammarContent, insertEdit, diagnostic); - } - private static CodeAction createGrammarFileAndBindIt(String title, String grammarURI, String grammarContent, TextDocumentEdit boundEdit, Diagnostic diagnostic) { CodeAction codeAction = CodeActionFactory.createFile(title, grammarURI, grammarContent, diagnostic); codeAction.getEdit().getDocumentChanges().add(Either.forLeft(boundEdit)); return codeAction; } + + public static TextDocumentEdit createXSINoNamespaceSchemaLocationEdit(String schemaFileName, DOMDocument document) + throws BadLocationException { + String delimiter = document.getTextDocument().lineDelimiter(0); + DOMElement documentElement = document.getDocumentElement(); + int beforeTagOffset = documentElement.getStartTagOpenOffset(); + int afterTagOffset = beforeTagOffset + 1 + documentElement.getTagName().length(); + + String insertText = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + delimiter; + insertText += " xsi:noNamespaceSchemaLocation=\"" + schemaFileName + "\""; + Position position = document.positionAt(afterTagOffset); + return CodeActionFactory.insertEdit(insertText, position, document.getTextDocument()); + } + + public static TextDocumentEdit createXmlModelEdit(String schemaFileName, DOMDocument document, + SharedSettings sharedSettings) throws BadLocationException { + String delimiter = document.getTextDocument().lineDelimiter(0); + DOMElement documentElement = document.getDocumentElement(); + int beforeTagOffset = documentElement.getStartTagOpenOffset(); + + XMLBuilder xsdWithXmlModel = new XMLBuilder(sharedSettings, null, delimiter); + xsdWithXmlModel.startPrologOrPI("xml-model"); + xsdWithXmlModel.addSingleAttribute("href", schemaFileName, true); + xsdWithXmlModel.endPrologOrPI(); + xsdWithXmlModel.linefeed(); + + String insertText = xsdWithXmlModel.toString(); + Position position = document.positionAt(beforeTagOffset); + return CodeActionFactory.insertEdit(insertText, position, document.getTextDocument()); + } + + public static TextDocumentEdit createDocTypeEdit(String dtdFileName, DOMDocument document, + SharedSettings sharedSettings) throws BadLocationException { + String delimiter = document.getTextDocument().lineDelimiter(0); + DOMElement documentElement = document.getDocumentElement(); + int beforeTagOffset = documentElement.getStartTagOpenOffset(); + + XMLBuilder docType = new XMLBuilder(sharedSettings, null, delimiter); + docType.startDoctype(); + docType.addParameter(documentElement.getLocalName()); + docType.addContent(" SYSTEM \""); + docType.addContent(dtdFileName); + docType.addContent("\""); + docType.endDoctype(); + docType.linefeed(); + + String insertText = docType.toString(); + Position position = document.positionAt(beforeTagOffset); + return CodeActionFactory.insertEdit(insertText, position, document.getTextDocument()); + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/AbstractDOMDocumentCommandHandler.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/AbstractDOMDocumentCommandHandler.java index 140a47af31..a031fed6d8 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/AbstractDOMDocumentCommandHandler.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/AbstractDOMDocumentCommandHandler.java @@ -14,6 +14,7 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.services.IXMLDocumentProvider; import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService.IDelegateCommandHandler; +import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.eclipse.lsp4j.jsonrpc.CancelChecker; @@ -34,7 +35,8 @@ public AbstractDOMDocumentCommandHandler(IXMLDocumentProvider documentProvider) } @Override - public final Object executeCommand(ExecuteCommandParams params, CancelChecker cancelChecker) throws Exception { + public final Object executeCommand(ExecuteCommandParams params, SharedSettings sharedSettings, + CancelChecker cancelChecker) throws Exception { TextDocumentIdentifier identifier = ArgumentsUtils.getArgAt(params, 0, TextDocumentIdentifier.class); String uri = identifier.getUri(); DOMDocument document = documentProvider.getDocument(uri); @@ -42,23 +44,24 @@ public final Object executeCommand(ExecuteCommandParams params, CancelChecker ca throw new UnsupportedOperationException(String .format("Command '%s' cannot find the DOM document with the URI '%s'.", params.getCommand(), uri)); } - return executeCommand(document, params, cancelChecker); + return executeCommand(document, params, sharedSettings, cancelChecker); } /** * Executes a command * - * @param document the DOM document retrieve by the - * {@link TextDocumentIdentifier} argument. + * @param document the DOM document retrieve by the + * {@link TextDocumentIdentifier} argument. * - * @param params command execution parameters - * @param cancelChecker check if cancel has been requested + * @param params command execution parameters + * @param sharedSettings the shared settings + * @param cancelChecker check if cancel has been requested * @return the result of the command * @throws Exception the unhandled exception will be wrapped in * org.eclipse.lsp4j.jsonrpc.ResponseErrorException * and be wired back to the JSON-RPC protocol caller */ protected abstract Object executeCommand(DOMDocument document, ExecuteCommandParams params, - CancelChecker cancelChecker) throws Exception; + SharedSettings sharedSettings, CancelChecker cancelChecker) throws Exception; } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/IXMLCommandService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/IXMLCommandService.java index 3940660de7..6e95461683 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/IXMLCommandService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/commands/IXMLCommandService.java @@ -14,6 +14,7 @@ import java.util.concurrent.CompletableFuture; +import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.jsonrpc.CancelChecker; @@ -37,13 +38,14 @@ public interface IDelegateCommandHandler { * Executes a command * * @param params command execution parameters + * @param sharedSettings the shared settings. * @param cancelChecker check if cancel has been requested * @return the result of the command * @throws Exception the unhandled exception will be wrapped in * org.eclipse.lsp4j.jsonrpc.ResponseErrorException * and be wired back to the JSON-RPC protocol caller */ - Object executeCommand(ExecuteCommandParams params, CancelChecker cancelChecker) throws Exception; + Object executeCommand(ExecuteCommandParams params, SharedSettings sharedSettings, CancelChecker cancelChecker) throws Exception; } /** diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/commands/CommandServiceTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/commands/CommandServiceTest.java index 14fe5d13bb..fa9dea9997 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/commands/CommandServiceTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/commands/CommandServiceTest.java @@ -36,7 +36,7 @@ public CommandServiceTest() { } private void registerCommand(String command) { - languageServer.registerCommand(command, (params, cancelChecker) -> { + languageServer.registerCommand(command, (params, sharedSettings, cancelChecker) -> { return params.getCommand() + (params.getArguments().isEmpty() ? "" : ": " + params.getArguments().stream().map(a -> a.toString()).collect(Collectors.joining(" "))); });