From 09435b2b2aeba43bfdae88fa4ce5130031331da5 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 22 Jun 2022 11:54:44 -0400 Subject: [PATCH] completionItem/resolve support Collect model documentation for completion items in the resolve step Fixes #616, redhat-developer/vscode-xml#679 Signed-off-by: David Thompson --- .../lemminx/XMLTextDocumentService.java | 7 + .../contentmodel/ContentModelPlugin.java | 8 +- .../ContentModelCompletionParticipant.java | 62 ++- .../AttributeValueCompletionResolver.java | 108 ++++++ .../extensions/entities/EntitiesPlugin.java | 2 +- .../EntitiesCompletionParticipant.java | 12 +- .../FilePathCompletionParticipant.java | 4 +- .../prolog/PrologCompletionParticipant.java | 4 +- .../extensions/prolog/PrologModel.java | 12 +- .../extensions/prolog/PrologPlugin.java | 4 +- .../references/XMLReferencesPlugin.java | 2 +- .../XMLReferencesCompletionParticipant.java | 8 +- .../lemminx/extensions/xsd/XSDPlugin.java | 4 +- .../XSDCompletionParticipant.java | 6 +- .../extensions/xsi/XSISchemaModel.java | 2 +- .../extensions/xsi/XSISchemaPlugin.java | 6 +- .../XSICompletionParticipant.java | 4 +- .../lemminx/services/CompletionResponse.java | 2 +- .../ResolveCompletionItemRequest.java | 61 +++ .../lemminx/services/XMLCompletions.java | 357 ++++++++++-------- .../lemminx/services/XMLLanguageService.java | 9 +- .../services/extensions/IXMLExtension.java | 15 +- .../extensions/XMLExtensionsRegistry.java | 29 +- .../codeaction/IBaseCodeActionRequest.java | 8 +- .../completion/BaseCompletionRequest.java | 46 +++ .../CompletionParticipantAdapter.java | 3 +- .../completion/IBaseCompletionRequest.java | 33 ++ .../ICompletionItemResolveParticipant.java | 21 ++ .../ICompletionItemResolverRequest.java | 25 ++ .../ICompletionParticipant.java | 13 +- .../{ => completion}/ICompletionResponse.java | 10 +- .../ServerCapabilitiesConstants.java | 2 +- .../java/org/eclipse/lemminx/XMLAssert.java | 70 +++- .../XMLSchemaCompletionExtensionsTest.java | 19 +- .../ErrorParticipantLanguageServiceTest.java | 2 + .../HTMLCompletionExtensionsTest.java | 13 +- 36 files changed, 730 insertions(+), 263 deletions(-) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/completion/AttributeValueCompletionResolver.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/ResolveCompletionItemRequest.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/BaseCompletionRequest.java rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/{ => completion}/CompletionParticipantAdapter.java (89%) create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/IBaseCompletionRequest.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolveParticipant.java create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolverRequest.java rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/{ => completion}/ICompletionParticipant.java (86%) rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/{ => completion}/ICompletionResponse.java (90%) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java index 2b1a5867e3..bf925959eb 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/XMLTextDocumentService.java @@ -247,6 +247,13 @@ public CompletableFuture, CompletionList>> completio }); } + @Override + public CompletableFuture resolveCompletionItem(CompletionItem unresolved) { + return computeDOMAsync(unresolved.getData(), (xmlDocument, cancelChecker) -> { + return getXMLLanguageService().resolveCompletionItem(unresolved, xmlDocument, sharedSettings, cancelChecker); + }); + } + @Override public CompletableFuture hover(HoverParams params) { return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> { 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 0fbd561a07..571c250882 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 @@ -36,7 +36,6 @@ import org.eclipse.lemminx.extensions.contentmodel.settings.XMLValidationSettings; import org.eclipse.lemminx.services.IXMLDocumentProvider; import org.eclipse.lemminx.services.IXMLValidationService; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.services.extensions.IHoverParticipant; import org.eclipse.lemminx.services.extensions.ITypeDefinitionParticipant; @@ -45,6 +44,7 @@ import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant; import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant; import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.save.ISaveContext; import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager; @@ -151,7 +151,7 @@ private void updateSettings(ContentModelSettings settings, ISaveContext context) if (useCache != null) { contentModelManager.setUseCache(useCache); } - + // Download external resources XMLDownloadExternalResourcesSettings downloadExternalResources = settings.getDownloadExternalResources(); boolean downloadExternalResourcesEnabled = downloadExternalResources == null @@ -212,7 +212,7 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) { registry.registerDocumentLifecycleParticipant(documentTelemetryParticipant); formatterParticipant = new ContentModelFormatterParticipant(contentModelManager); registry.registerFormatterParticipant(formatterParticipant); - + // Register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); if (commandService != null) { @@ -242,7 +242,7 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterCodeLensParticipant(codeLensParticipant); registry.unregisterDocumentLifecycleParticipant(documentTelemetryParticipant); registry.unregisterFormatterParticipant(formatterParticipant); - + // Un-register custom commands to re-validate XML files IXMLCommandService commandService = registry.getCommandService(); if (commandService != null) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java index c5c92777ed..de3d377ae8 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Set; @@ -25,11 +26,14 @@ import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration; import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.extensions.contentmodel.participants.completion.AttributeValueCompletionResolver; import org.eclipse.lemminx.extensions.contentmodel.utils.XMLGenerator; import org.eclipse.lemminx.services.AttributeCompletionItem; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.data.DataEntryField; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolveParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.uriresolver.CacheResourceDownloadingException; import org.eclipse.lemminx.utils.StringUtils; import org.eclipse.lsp4j.CompletionItem; @@ -45,14 +49,25 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import com.google.gson.JsonObject; + /** * Extension to support XML completion based on content model (XML Schema * completion, etc) */ public class ContentModelCompletionParticipant extends CompletionParticipantAdapter { + private final HashMap completionResolvers; + + public ContentModelCompletionParticipant() { + completionResolvers = new HashMap<>(); + completionResolvers.put(AttributeValueCompletionResolver.PARTICIPANT_ID, + new AttributeValueCompletionResolver()); + } + @Override - public void onTagOpen(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws Exception { + public void onTagOpen(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) + throws Exception { try { DOMDocument document = request.getXMLDocument(); ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class); @@ -122,7 +137,7 @@ private boolean hasNamespace(String namespaceURI, Collection cmRootD /** * Fill with possible element declarations. - * + * * @param parentElement the parent DOM element * @param cmElement the content model element declaration * @param defaultPrefix @@ -150,7 +165,7 @@ private static void fillWithPossibleElementDeclaration(DOMElement parentElement, /** * Fill with children element declarations - * + * * @param element * @param cmDocument * @param cmElements @@ -201,7 +216,7 @@ private static void fillCompletionItem(Collection elements /** * Add completion item with all tag names of the node list. - * + * * @param list * @param tags * @param request @@ -264,7 +279,8 @@ private static boolean isGenerateEndTag(DOMNode node, int offset, String tagName } @Override - public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) + public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response, + CancelChecker cancelChecker) throws Exception { // otherwise, manage completion based on XML Schema, DTD. DOMElement parentElement = request.getNode().isElement() ? (DOMElement) request.getNode() : null; @@ -301,12 +317,15 @@ private void fillAttributesWithCMAttributeDeclarations(DOMElement parentElement, return; } for (CMAttributeDeclaration attributeDeclaration : attributes) { - String prefix = (parentElement != null ? parentElement.getPrefix(attributeDeclaration.getNamespace()) : null); + String prefix = (parentElement != null ? parentElement.getPrefix(attributeDeclaration.getNamespace()) + : null); String attrName = attributeDeclaration.getName(prefix); if (!parentElement.hasAttribute(attrName)) { CompletionItem item = new AttributeCompletionItem(attrName, canSupportSnippet, fullRange, generateValue, - attributeDeclaration.getDefaultValue(), attributeDeclaration.getEnumerationValues(), request.getSharedSettings()); - MarkupContent documentation = XMLGenerator.createMarkupContent(attributeDeclaration, elementDeclaration, request); + attributeDeclaration.getDefaultValue(), attributeDeclaration.getEnumerationValues(), + request.getSharedSettings()); + MarkupContent documentation = XMLGenerator.createMarkupContent(attributeDeclaration, elementDeclaration, + request); item.setDocumentation(documentation); response.addCompletionAttribute(item); } @@ -314,7 +333,8 @@ private void fillAttributesWithCMAttributeDeclarations(DOMElement parentElement, } @Override - public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) + public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response, + CancelChecker cancelChecker) throws Exception { DOMElement parentElement = request.getNode().isElement() ? (DOMElement) request.getNode() : null; if (parentElement == null) { @@ -352,15 +372,24 @@ private void fillAttributeValuesWithCMAttributeDeclarations(CMElementDeclaration item.setKind(CompletionItemKind.Value); item.setFilterText(insertText); item.setTextEdit(Either.forLeft(new TextEdit(fullRange, insertText))); - MarkupContent documentation = XMLGenerator.createMarkupContent(cmAttribute, value, cmElement, request); - item.setDocumentation(documentation); + + DOMDocument document = request.getNode().getOwnerDocument(); + JsonObject data = DataEntryField.createData(document.getDocumentURI(), + AttributeValueCompletionResolver.PARTICIPANT_ID); + try { + data.addProperty(AttributeValueCompletionResolver.OFFSET_KEY, + Integer.toString(document.offsetAt(request.getPosition()))); + } catch (BadLocationException e) { + } + item.setData(data); response.addCompletionItem(item); }); } } @Override - public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws Exception { + public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) + throws Exception { try { ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class); DOMElement parentElement = request.getParentElement(); @@ -404,4 +433,9 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons // XML Schema, DTD is loading, ignore this error } } + + @Override + public ICompletionItemResolveParticipant getResolveCompletionItemParticipant(String participantId) { + return completionResolvers.get(participantId); + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/completion/AttributeValueCompletionResolver.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/completion/AttributeValueCompletionResolver.java new file mode 100644 index 0000000000..21bdf35d8e --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/completion/AttributeValueCompletionResolver.java @@ -0,0 +1,108 @@ +/******************************************************************************* +* Copyright (c) 2022 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.completion; + +import java.util.Collection; + +import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.extensions.contentmodel.model.CMAttributeDeclaration; +import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument; +import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration; +import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager; +import org.eclipse.lemminx.extensions.contentmodel.utils.XMLGenerator; +import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolveParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolverRequest; +import org.eclipse.lemminx.uriresolver.CacheResourceDownloadingException; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +/** + * Resolves the documentation for the completion of the attribute value from the + * content model. + * + */ +public class AttributeValueCompletionResolver implements ICompletionItemResolveParticipant { + + public static final String PARTICIPANT_ID = AttributeValueCompletionResolver.class.getName(); + public static final String OFFSET_KEY = "OFFSET"; + + @Override + public CompletionItem resolveCompletionItem(ICompletionItemResolverRequest request, CancelChecker cancelChecker) { + + CompletionItem toResolve = request.getUnresolved(); + + DOMDocument document = request.getDocument(); + + String offsetString = request.getDataProperty(OFFSET_KEY); + Integer offset = null; + if (document == null || offsetString == null) { + return toResolve; + } + try { + offset = Integer.parseInt(offsetString); + } catch (NumberFormatException e) { + return toResolve; + } + + DOMAttr attr = document.findAttrAt(offset); + if (attr == null) { + return toResolve; + } + + DOMNode parentNode = document.findNodeAt(offset); + if (parentNode == null || !parentNode.isElement()) { + return toResolve; + } + DOMElement parentElement = (DOMElement) parentNode; + + collectDocumentationFromContentModel(request, toResolve, parentElement, attr); + return toResolve; + } + + private void collectDocumentationFromContentModel(ICompletionItemResolverRequest request, CompletionItem toResolve, + DOMElement parentElement, DOMAttr attr) { + String attributeName = attr.getName(); + String attributeValue = toResolve.getLabel(); + try { + ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class); + Collection cmDocuments = contentModelManager.findCMDocument(parentElement); + for (CMDocument cmDocument : cmDocuments) { + CMElementDeclaration cmElement = cmDocument.findCMElement(parentElement, + parentElement.getNamespaceURI()); + if (cmElement != null) { + MarkupContent documentation = getDocumentationForAttributeValue(cmElement, attributeName, + attributeValue, request); + if (documentation != null) { + toResolve.setDocumentation(documentation); + return; + } + } + } + } catch (CacheResourceDownloadingException e) { + } + } + + private MarkupContent getDocumentationForAttributeValue(CMElementDeclaration cmElement, String attributeName, + String attributeValue, ICompletionItemResolverRequest request) { + CMAttributeDeclaration cmAttribute = cmElement.findCMAttribute(attributeName); + if (cmAttribute != null) { + return XMLGenerator.createMarkupContent(cmAttribute, attributeValue, cmElement, + request); + } + return null; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/EntitiesPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/EntitiesPlugin.java index 10c165fedb..6453ee0366 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/EntitiesPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/EntitiesPlugin.java @@ -15,11 +15,11 @@ import org.eclipse.lemminx.extensions.entities.participants.EntitiesCompletionParticipant; import org.eclipse.lemminx.extensions.entities.participants.EntitiesDefinitionParticipant; import org.eclipse.lemminx.extensions.entities.participants.EntitiesHoverParticipant; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IDefinitionParticipant; import org.eclipse.lemminx.services.extensions.IHoverParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lsp4j.InitializeParams; /** diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesCompletionParticipant.java index de7fc9bc54..da51b4452e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/entities/participants/EntitiesCompletionParticipant.java @@ -23,9 +23,9 @@ import org.eclipse.lemminx.extensions.entities.EntitiesDocumentationUtils; import org.eclipse.lemminx.extensions.entities.EntitiesDocumentationUtils.EntityOriginType; import org.eclipse.lemminx.extensions.entities.EntitiesDocumentationUtils.PredefinedEntity; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.utils.XMLPositionUtility; import org.eclipse.lemminx.utils.XMLPositionUtility.EntityReferenceRange; import org.eclipse.lsp4j.CompletionItem; @@ -64,7 +64,7 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons /** * Collect local entities declared in the DOCTYPE. - * + * * @param document the DOM document. * @param entityRange the entity range. * @param markdown true if the documentation can be formatted as markdown and @@ -91,7 +91,7 @@ private static void collectLocalEntityProposals(DOMDocument document, Range enti /** * Collect external entities. - * + * * @param document the DOM document. * @param entityRange the entity range. * @param markdown true if the documentation can be formatted as markdown and @@ -118,12 +118,12 @@ private static void collectExternalEntityProposals(DOMDocument document, Range e /** * Collect predefined entities. - * + * * @param entityRange the entity range. * @param markdown true if the documentation can be formatted as markdown and * false otherwise. * @param response the completion response. - * + * * @see https://www.w3.org/TR/xml/#sec-predefined-ent */ private void collectPredefinedEntityProposals(Range entityRange, boolean markdown, ICompletionResponse response) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java index 9078661cd3..da5316a3ec 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/general/completion/FilePathCompletionParticipant.java @@ -27,9 +27,9 @@ import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lemminx.dom.DOMDocument; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.utils.CompletionSortTextHelper; import org.eclipse.lemminx.utils.FilesUtils; import org.eclipse.lemminx.utils.StringUtils; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologCompletionParticipant.java index 93124e925b..f9e672fa35 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologCompletionParticipant.java @@ -12,9 +12,9 @@ */ package org.eclipse.lemminx.extensions.prolog; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lsp4j.jsonrpc.CancelChecker; /** diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologModel.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologModel.java index 165ce3057f..9685b46994 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologModel.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologModel.java @@ -21,7 +21,7 @@ import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.services.AttributeCompletionItem; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lemminx.utils.StringUtils; import org.eclipse.lsp4j.CompletionItem; @@ -172,14 +172,14 @@ private static void createCompletionItemsForValues(Collection enumeratio /** * Returns the position the offset is in in relation to the attributes and their * order - * + * * example: - * + * * - * + * * This will return 2 since if you insert a new attribute there you can access * it from the list of attributes with this index. - * + * * @param completionOffset * @param element * @return @@ -213,7 +213,7 @@ private static int getAttributeCompletionPosition(int completionOffset, DOMNode /** * Returns true if the current attribute in the given position of the element's * list of attributes equals the provided attributeName - * + * * @param attributeName * @param element * @param position diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologPlugin.java index 28cc308ccf..ba48c3cb3b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/prolog/PrologPlugin.java @@ -12,15 +12,15 @@ */ package org.eclipse.lemminx.extensions.prolog; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lsp4j.InitializeParams; /** * Plugin to handle `xsi` attributes or a namespace with the value of: * "http://www.w3.org/2001/XMLSchema-instance" - * + * * Loaded by service loader in 'resources' folder. */ public class PrologPlugin implements IXMLExtension { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/XMLReferencesPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/XMLReferencesPlugin.java index 407a4939a3..e18ecc5b6e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/XMLReferencesPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/XMLReferencesPlugin.java @@ -14,10 +14,10 @@ import org.eclipse.lemminx.extensions.references.participants.XMLReferencesCompletionParticipant; import org.eclipse.lemminx.extensions.references.participants.XMLReferencesDefinitionParticipant; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IDefinitionParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lsp4j.InitializeParams; public class XMLReferencesPlugin implements IXMLExtension { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCompletionParticipant.java index 74cd1a4f4a..0e0281f438 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCompletionParticipant.java @@ -15,9 +15,9 @@ import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.extensions.references.XMLReferencesManager; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.utils.XMLPositionUtility; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; @@ -32,8 +32,8 @@ public class XMLReferencesCompletionParticipant extends CompletionParticipantAda @Override public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws Exception { int offset = request.getOffset(); - final DOMNode node = getNodeAt(request.getNode(), offset); - if (node != null) { + final DOMNode node = getNodeAt(request.getNode(), offset); + if (node != null) { XMLReferencesManager.getInstance().collect(node, n -> { DOMDocument doc = n.getOwnerDocument(); Range range = XMLPositionUtility.createRange(node.getStart(), node.getEnd(), doc); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java index 307810d015..f1d0d368ce 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/XSDPlugin.java @@ -24,7 +24,6 @@ import org.eclipse.lemminx.extensions.xsd.participants.XSDReferenceParticipant; import org.eclipse.lemminx.extensions.xsd.participants.XSDRenameParticipant; import org.eclipse.lemminx.extensions.xsd.participants.diagnostics.XSDDiagnosticsParticipant; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IDefinitionParticipant; import org.eclipse.lemminx.services.extensions.IDocumentLinkParticipant; import org.eclipse.lemminx.services.extensions.IHighlightingParticipant; @@ -33,6 +32,7 @@ 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.completion.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.save.ISaveContext; import org.eclipse.lemminx.utils.DOMUtils; @@ -113,7 +113,7 @@ public void stop(XMLExtensionsRegistry registry) { registry.unregisterRenameParticipant(renameParticipant); registry.unregisterDocumentLinkParticipant(documentLinkParticipant); } - + public ContentModelManager getContentModelManager() { return contentModelManager; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDCompletionParticipant.java index 1d16adf430..054ffa03fe 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDCompletionParticipant.java @@ -18,9 +18,9 @@ import org.eclipse.lemminx.extensions.xsd.DataType; import org.eclipse.lemminx.extensions.xsd.utils.XSDUtils; import org.eclipse.lemminx.extensions.xsd.utils.XSDUtils.BindingType; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.utils.DOMUtils; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; @@ -33,7 +33,7 @@ /** * XSD completion for - * + * *
    *
  • xs:/@type -> xs:complexType/@name
  • *
  • xs:/@base -> xs:complexType/@name
  • diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaModel.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaModel.java index 65ff7b8ba9..b8ed60b417 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaModel.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaModel.java @@ -22,8 +22,8 @@ import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.services.AttributeCompletionItem; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; import org.eclipse.lemminx.services.extensions.IHoverRequest; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lemminx.utils.StringUtils; import org.eclipse.lsp4j.CompletionItem; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaPlugin.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaPlugin.java index bfab5e7885..7dfb70a517 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaPlugin.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/XSISchemaPlugin.java @@ -15,17 +15,17 @@ import org.eclipse.lemminx.extensions.xsi.participants.XSICompletionParticipant; import org.eclipse.lemminx.extensions.xsi.participants.XSIFormatterParticipant; import org.eclipse.lemminx.extensions.xsi.participants.XSIHoverParticipant; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.IHoverParticipant; import org.eclipse.lemminx.services.extensions.IXMLExtension; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; import org.eclipse.lsp4j.InitializeParams; /** * Plugin to handle the xml prolog: {@code }. - * - * + * + * * Loaded by service loader in 'resources' folder. */ public class XSISchemaPlugin implements IXMLExtension { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/participants/XSICompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/participants/XSICompletionParticipant.java index bb39c60720..7a20701f72 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/participants/XSICompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsi/participants/XSICompletionParticipant.java @@ -13,9 +13,9 @@ package org.eclipse.lemminx.extensions.xsi.participants; import org.eclipse.lemminx.extensions.xsi.XSISchemaModel; -import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lsp4j.jsonrpc.CancelChecker; /** diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/CompletionResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/CompletionResponse.java index 7c593685cc..4f237a6984 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/CompletionResponse.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/CompletionResponse.java @@ -15,7 +15,7 @@ import java.util.ArrayList; import java.util.List; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/ResolveCompletionItemRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/ResolveCompletionItemRequest.java new file mode 100644 index 0000000000..81e3577e98 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/ResolveCompletionItemRequest.java @@ -0,0 +1,61 @@ +/******************************************************************************* +* Copyright (c) 2022 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.services; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.services.data.DataEntryField; +import org.eclipse.lemminx.services.extensions.IComponentProvider; +import org.eclipse.lemminx.services.extensions.completion.BaseCompletionRequest; +import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolverRequest; +import org.eclipse.lemminx.settings.SharedSettings; +import org.eclipse.lemminx.settings.XMLCompletionSettings; +import org.eclipse.lsp4j.CompletionItem; + +public class ResolveCompletionItemRequest extends BaseCompletionRequest implements ICompletionItemResolverRequest { + + private final CompletionItem unresolved; + + private final String participantId; + + public ResolveCompletionItemRequest(CompletionItem unresolved, DOMDocument document, + IComponentProvider componentProvider, SharedSettings sharedSettings) { + super(document, componentProvider, sharedSettings); + this.unresolved = unresolved; + this.participantId = DataEntryField.getParticipantId(unresolved.getData()); + } + + @Override + public CompletionItem getUnresolved() { + return unresolved; + } + + @Override + public String getParticipantId() { + return participantId; + } + + @Override + public String getDataProperty(String fieldName) { + return DataEntryField.getProperty(unresolved.getData(), fieldName); + } + + @Override + public boolean canSupportMarkupKind(String kind) { + XMLCompletionSettings completionSettings = getSharedSettings().getCompletionSettings(); + return completionSettings.getCompletionCapabilities() != null + && completionSettings.getCompletionCapabilities().getCompletionItem() != null + && completionSettings.getCompletionCapabilities().getCompletionItem().getDocumentationFormat() != null + && completionSettings.getCompletionCapabilities().getCompletionItem().getDocumentationFormat() + .contains(kind); + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java index 604bb54bf7..cdd7fce07b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java @@ -38,10 +38,11 @@ import org.eclipse.lemminx.dom.parser.ScannerState; import org.eclipse.lemminx.dom.parser.TokenType; import org.eclipse.lemminx.dom.parser.XMLScanner; -import org.eclipse.lemminx.services.extensions.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.ICompletionRequest; -import org.eclipse.lemminx.services.extensions.ICompletionResponse; import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry; +import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolveParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.services.snippets.IXMLSnippetContext; import org.eclipse.lemminx.settings.SharedSettings; import org.eclipse.lemminx.settings.XMLCompletionSettings; @@ -105,174 +106,177 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha while (token != TokenType.EOS && scanner.getTokenOffset() <= offset) { cancelChecker.checkCanceled(); switch (token) { - case StartTagOpen: - if (scanner.getTokenEnd() == offset) { - int endPos = scanNextForEndPos(offset, scanner, TokenType.StartTag); - collectTagSuggestions(offset, endPos, completionRequest, completionResponse, cancelChecker); - return completionResponse; - } - break; - case StartTag: - if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { - collectOpenTagSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), completionRequest, - completionResponse, cancelChecker); - return completionResponse; - } - currentTag = scanner.getTokenText(); - break; - case AttributeName: - if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { - collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), - completionRequest, completionResponse, cancelChecker); - return completionResponse; - } - break; - case DelimiterAssign: - if (scanner.getTokenEnd() == offset) { - collectAttributeValueSuggestions(offset, offset, completionRequest, completionResponse, - cancelChecker); - return completionResponse; - } - break; - case DTDStartDoctypeTag: - - DTDDeclParameter systemId = xmlDocument.getDoctype().getSystemIdNode(); - if (systemId == null) { + case StartTagOpen: + if (scanner.getTokenEnd() == offset) { + int endPos = scanNextForEndPos(offset, scanner, TokenType.StartTag); + collectTagSuggestions(offset, endPos, completionRequest, completionResponse, cancelChecker); + return completionResponse; + } break; - } - - if (DOMNode.isIncluded(systemId, offset)) { - /** - * Completion invoked within systemId parameter ie, completion offset is at | - * like so: - */ - collectDTDSystemIdSuggestions(systemId.getStart(), systemId.getEnd(), completionRequest, - completionResponse, cancelChecker); - return completionResponse; - } - break; - case AttributeValue: - if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { - collectAttributeValueSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), - completionRequest, completionResponse, cancelChecker); - return completionResponse; - } - break; - case Whitespace: - if (offset <= scanner.getTokenEnd()) { - switch (scanner.getScannerState()) { - case AfterOpeningStartTag: - int startPos = scanner.getTokenOffset(); - int endTagPos = scanNextForEndPos(offset, scanner, TokenType.StartTag); - collectTagSuggestions(startPos, endTagPos, completionRequest, completionResponse, + case StartTag: + if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { + collectOpenTagSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), + completionRequest, + completionResponse, cancelChecker); + return completionResponse; + } + currentTag = scanner.getTokenText(); + break; + case AttributeName: + if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { + collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), + completionRequest, completionResponse, cancelChecker); + return completionResponse; + } + break; + case DelimiterAssign: + if (scanner.getTokenEnd() == offset) { + collectAttributeValueSuggestions(offset, offset, completionRequest, completionResponse, cancelChecker); return completionResponse; - case WithinTag: - case AfterAttributeName: - collectAttributeNameSuggestions(scanner.getTokenEnd(), completionRequest, + } + break; + case DTDStartDoctypeTag: + + DTDDeclParameter systemId = xmlDocument.getDoctype().getSystemIdNode(); + if (systemId == null) { + break; + } + + if (DOMNode.isIncluded(systemId, offset)) { + /** + * Completion invoked within systemId parameter ie, completion offset is at | + * like so: + */ + collectDTDSystemIdSuggestions(systemId.getStart(), systemId.getEnd(), completionRequest, completionResponse, cancelChecker); return completionResponse; - case BeforeAttributeValue: - collectAttributeValueSuggestions(scanner.getTokenEnd(), offset, completionRequest, - completionResponse, cancelChecker); + } + break; + case AttributeValue: + if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { + collectAttributeValueSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), + completionRequest, completionResponse, cancelChecker); return completionResponse; - case AfterOpeningEndTag: - collectCloseTagSuggestions(scanner.getTokenOffset() - 1, false, offset, completionRequest, + } + break; + case Whitespace: + if (offset <= scanner.getTokenEnd()) { + switch (scanner.getScannerState()) { + case AfterOpeningStartTag: + int startPos = scanner.getTokenOffset(); + int endTagPos = scanNextForEndPos(offset, scanner, TokenType.StartTag); + collectTagSuggestions(startPos, endTagPos, completionRequest, completionResponse, + cancelChecker); + return completionResponse; + case WithinTag: + case AfterAttributeName: + collectAttributeNameSuggestions(scanner.getTokenEnd(), completionRequest, + completionResponse, cancelChecker); + return completionResponse; + case BeforeAttributeValue: + collectAttributeValueSuggestions(scanner.getTokenEnd(), offset, completionRequest, + completionResponse, cancelChecker); + return completionResponse; + case AfterOpeningEndTag: + collectCloseTagSuggestions(scanner.getTokenOffset() - 1, false, offset, + completionRequest, + completionResponse, cancelChecker); + return completionResponse; + case WithinContent: + collectInsideContent(completionRequest, completionResponse, cancelChecker); + return completionResponse; + default: + } + } + break; + case EndTagOpen: + if (offset <= scanner.getTokenEnd()) { + int afterOpenBracket = scanner.getTokenOffset() + 1; + int endOffset = scanNextForEndPos(offset, scanner, TokenType.EndTag); + collectCloseTagSuggestions(afterOpenBracket, false, endOffset, completionRequest, completionResponse, cancelChecker); return completionResponse; - case WithinContent: - collectInsideContent(completionRequest, completionResponse, cancelChecker); - return completionResponse; - default: } - } - break; - case EndTagOpen: - if (offset <= scanner.getTokenEnd()) { - int afterOpenBracket = scanner.getTokenOffset() + 1; - int endOffset = scanNextForEndPos(offset, scanner, TokenType.EndTag); - collectCloseTagSuggestions(afterOpenBracket, false, endOffset, completionRequest, - completionResponse, cancelChecker); - return completionResponse; - } - break; - case EndTag: - if (offset <= scanner.getTokenEnd()) { - int start = scanner.getTokenOffset() - 1; - while (start >= 0) { - char ch = text.charAt(start); - if (ch == '/') { - collectCloseTagSuggestions(start, false, scanner.getTokenEnd(), completionRequest, - completionResponse, cancelChecker); + break; + case EndTag: + if (offset <= scanner.getTokenEnd()) { + int start = scanner.getTokenOffset() - 1; + while (start >= 0) { + char ch = text.charAt(start); + if (ch == '/') { + collectCloseTagSuggestions(start, false, scanner.getTokenEnd(), completionRequest, + completionResponse, cancelChecker); + return completionResponse; + } else if (!isWhitespace(ch)) { + break; + } + start--; + } + } + break; + case StartTagClose: + if (offset <= scanner.getTokenEnd()) { + if (currentTag != null && currentTag.length() > 0) { + collectInsideContent(completionRequest, completionResponse, cancelChecker); return completionResponse; - } else if (!isWhitespace(ch)) { - break; } - start--; } - } - break; - case StartTagClose: - if (offset <= scanner.getTokenEnd()) { - if (currentTag != null && currentTag.length() > 0) { - collectInsideContent(completionRequest, completionResponse, cancelChecker); - return completionResponse; + break; + case StartTagSelfClose: + if (offset <= scanner.getTokenEnd()) { + if (currentTag != null && currentTag.length() > 0 + && xmlDocument.getText().charAt(offset - 1) == '>') { // if the actual character + // typed + // was + // '>' + collectInsideContent(completionRequest, completionResponse, cancelChecker); + return completionResponse; + } } - } - break; - case StartTagSelfClose: - if (offset <= scanner.getTokenEnd()) { - if (currentTag != null && currentTag.length() > 0 - && xmlDocument.getText().charAt(offset - 1) == '>') { // if the actual character typed - // was - // '>' - collectInsideContent(completionRequest, completionResponse, cancelChecker); - return completionResponse; + break; + case EndTagClose: + if (offset <= scanner.getTokenEnd()) { + if (currentTag != null && currentTag.length() > 0) { + collectInsideContent(completionRequest, completionResponse, cancelChecker); + return completionResponse; + } } - } - break; - case EndTagClose: - if (offset <= scanner.getTokenEnd()) { - if (currentTag != null && currentTag.length() > 0) { + break; + case Content: + if (completionRequest.getXMLDocument().isDTD() + || completionRequest.getXMLDocument().isWithinInternalDTD(offset)) { + if (scanner.getTokenOffset() <= offset) { + return completionResponse; + } + break; + } + if (offset <= scanner.getTokenEnd()) { collectInsideContent(completionRequest, completionResponse, cancelChecker); return completionResponse; } - } - break; - case Content: - if (completionRequest.getXMLDocument().isDTD() - || completionRequest.getXMLDocument().isWithinInternalDTD(offset)) { + break; + // DTD + case DTDAttlistAttributeName: + case DTDAttlistAttributeType: + case DTDAttlistAttributeValue: + case DTDStartAttlist: + case DTDStartElement: + case DTDStartEntity: + case DTDEndTag: + case DTDStartInternalSubset: + case DTDEndInternalSubset: { if (scanner.getTokenOffset() <= offset) { return completionResponse; } break; } - if (offset <= scanner.getTokenEnd()) { - collectInsideContent(completionRequest, completionResponse, cancelChecker); - return completionResponse; - } - break; - // DTD - case DTDAttlistAttributeName: - case DTDAttlistAttributeType: - case DTDAttlistAttributeValue: - case DTDStartAttlist: - case DTDStartElement: - case DTDStartEntity: - case DTDEndTag: - case DTDStartInternalSubset: - case DTDEndInternalSubset: { - if (scanner.getTokenOffset() <= offset) { - return completionResponse; - } - break; - } - default: - if (offset <= scanner.getTokenEnd()) { - return completionResponse; - } - break; + default: + if (offset <= scanner.getTokenEnd()) { + return completionResponse; + } + break; } token = scanner.scan(); } @@ -282,6 +286,32 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha } } + public CompletionItem resolveCompletionItem(CompletionItem unresolved, DOMDocument xmlDocument, + SharedSettings sharedSettings, CancelChecker cancelChecker) { + ResolveCompletionItemRequest request = new ResolveCompletionItemRequest(unresolved, xmlDocument, extensionsRegistry, + sharedSettings); + String participantId = request.getParticipantId(); + if (StringUtils.isEmpty(participantId)) { + return null; + } + for (ICompletionParticipant completionParticipant : extensionsRegistry.getCompletionParticipants()) { + try { + cancelChecker.checkCanceled(); + ICompletionItemResolveParticipant resolveCompletionItemParticipant = completionParticipant + .getResolveCompletionItemParticipant(participantId); + if (resolveCompletionItemParticipant != null) { + return resolveCompletionItemParticipant.resolveCompletionItem(request, cancelChecker); + } + } catch (CancellationException e) { + throw e; + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error while processing resolve completion item for the participant '" + + completionParticipant.getClass().getName() + "'.", e); + } + } + return null; + } + /** * Collect snippets suggestions. * @@ -784,22 +814,22 @@ private void collectInsideContent(CompletionRequest request, CompletionResponse private static Range getTextRangeInsideContent(DOMNode node) { switch (node.getNodeType()) { - case Node.ELEMENT_NODE: - Node firstChild = node.getFirstChild(); - if (firstChild == null) { - // ex : | - DOMElement element = (DOMElement) node; - return XMLPositionUtility.createRange(element.getStartTagCloseOffset() + 1, - element.getStartTagCloseOffset() + 1, element.getOwnerDocument()); - } - if (firstChild.getNodeType() == Node.TEXT_NODE) { - // ex : abcd| - return XMLPositionUtility.selectText((DOMText) firstChild); - } - return null; - case Node.TEXT_NODE: - // ex : | - return XMLPositionUtility.selectText((DOMText) node); + case Node.ELEMENT_NODE: + Node firstChild = node.getFirstChild(); + if (firstChild == null) { + // ex : | + DOMElement element = (DOMElement) node; + return XMLPositionUtility.createRange(element.getStartTagCloseOffset() + 1, + element.getStartTagCloseOffset() + 1, element.getOwnerDocument()); + } + if (firstChild.getNodeType() == Node.TEXT_NODE) { + // ex : abcd| + return XMLPositionUtility.selectText((DOMText) firstChild); + } + return null; + case Node.TEXT_NODE: + // ex : | + return XMLPositionUtility.selectText((DOMText) node); } // should never occur return null; @@ -1068,4 +1098,5 @@ private SnippetRegistry getSnippetRegistry() { } return snippetRegistry; } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java index 6f407cb60d..493dd01a16 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLanguageService.java @@ -35,6 +35,7 @@ import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionContext; import org.eclipse.lsp4j.CodeLens; +import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionList; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DocumentHighlight; @@ -105,7 +106,7 @@ public XMLLanguageService() { this.linkedEditing = new XMLLinkedEditing(); } - @Override + @Override public String formatFull(String text, String uri, SharedSettings sharedSettings, CancelChecker cancelChecker) { DOMDocument xmlDocument = DOMParser.getInstance().parse(new TextDocument(text, uri), null); List edits = this.format(xmlDocument, null, sharedSettings); @@ -152,6 +153,11 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha return completions.doComplete(xmlDocument, position, settings, cancelChecker); } + public CompletionItem resolveCompletionItem(CompletionItem unresolved, DOMDocument xmlDocument, + SharedSettings sharedSettings, CancelChecker cancelChecker) { + return completions.resolveCompletionItem(unresolved, xmlDocument, sharedSettings, cancelChecker); + } + public Hover doHover(DOMDocument xmlDocument, Position position, SharedSettings sharedSettings) { return doHover(xmlDocument, position, sharedSettings, NULL_CHECKER); } @@ -291,4 +297,5 @@ public LinkedEditingRanges findLinkedEditingRanges(DOMDocument xmlDocument, Posi CancelChecker cancelChecker) { return linkedEditing.findLinkedEditingRanges(xmlDocument, position, cancelChecker); } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IXMLExtension.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IXMLExtension.java index ebcdf9b334..a36aeace5b 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IXMLExtension.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IXMLExtension.java @@ -12,6 +12,7 @@ */ package org.eclipse.lemminx.services.extensions; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.save.ISaveContext; import org.eclipse.lsp4j.InitializeParams; @@ -26,8 +27,8 @@ public interface IXMLExtension { * Start method to register participants like {@link ICompletionParticipant}, * {@link IHoverParticipant}, {@link IDiagnosticsParticipant} in the given * registry. - * @param params - * + * @param params + * * @param registry * @param settings */ @@ -37,19 +38,19 @@ public interface IXMLExtension { * Stop method to un-register participants like {@link ICompletionParticipant}, * {@link IHoverParticipant}, {@link IDiagnosticsParticipant} in the given * registry. - * + * * @param registry */ void stop(XMLExtensionsRegistry registry); /** - * Called when the Settings or a Document have been saved. + * Called when the Settings or a Document have been saved. * context.getType() can be used to determine what type was saved. - * doSave is called on extension start up with a settings context to + * doSave is called on extension start up with a settings context to * provide the xml settings to the extension. - * @param context + * @param context */ default void doSave(ISaveContext context) { - + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java index dfc5b2ba1b..c61aa6c23a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/XMLExtensionsRegistry.java @@ -29,6 +29,7 @@ import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionParticipant; import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant; import org.eclipse.lemminx.services.extensions.commands.IXMLCommandService; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; import org.eclipse.lemminx.services.extensions.save.ISaveContext; @@ -209,7 +210,7 @@ public Collection getSymbolsProviderParticipants() /** * Return the registered workspace service participants. - * + * * @return the registered workspace service participants. * @since 0.14.2 */ @@ -220,7 +221,7 @@ public Collection getWorkspaceServiceParticipants( /** * Return the registered document lifecycle participants. - * + * * @return the registered document lifecycle participants. * @since 0.18.0 */ @@ -396,7 +397,7 @@ public void unregisterSymbolsProviderParticipant(ISymbolsProviderParticipant sym /** * Register a new workspace service participant - * + * * @param workspaceServiceParticipant the participant to register * @since 0.14.2 */ @@ -406,7 +407,7 @@ public void registerWorkspaceServiceParticipant(IWorkspaceServiceParticipant wor /** * Unregister a new workspace service participant. - * + * * @param workspaceServiceParticipant the participant to unregister * @since 0.14.2 */ @@ -416,7 +417,7 @@ public void unregisterWorkspaceServiceParticipant(IWorkspaceServiceParticipant w /** * Register a new document lifecycle participant - * + * * @param documentLifecycleParticipant the participant to register * @since 0.18.0 */ @@ -426,7 +427,7 @@ public void registerDocumentLifecycleParticipant(IDocumentLifecycleParticipant d /** * Unregister a new document lifecycle participant. - * + * * @param documentLifecycleParticipant the participant to unregister * @since 0.18.0 */ @@ -436,7 +437,7 @@ public void unregisterDocumentLifecycleParticipant(IDocumentLifecycleParticipant /** * Returns the XML Document provider and null otherwise. - * + * * @return the XML Document provider and null otherwise. */ public IXMLDocumentProvider getDocumentProvider() { @@ -445,7 +446,7 @@ public IXMLDocumentProvider getDocumentProvider() { /** * Set the XML Document provider - * + * * @param documentProvider XML Document provider */ public void setDocumentProvider(IXMLDocumentProvider documentProvider) { @@ -458,7 +459,7 @@ public URIResolverExtensionManager getResolverExtensionManager() { /** * Returns the notification service - * + * * @return the notification service */ public IXMLNotificationService getNotificationService() { @@ -467,7 +468,7 @@ public IXMLNotificationService getNotificationService() { /** * Sets the notification service - * + * * @param notificationService the new notification service */ public void setNotificationService(IXMLNotificationService notificationService) { @@ -476,7 +477,7 @@ public void setNotificationService(IXMLNotificationService notificationService) /** * Returns the XML document validation service - * + * * @return the validation service */ public IXMLValidationService getValidationService() { @@ -485,7 +486,7 @@ public IXMLValidationService getValidationService() { /** * Sets the XML document validation service - * + * * @param validationService */ public void setValidationService(IXMLValidationService validationService) { @@ -494,7 +495,7 @@ public void setValidationService(IXMLValidationService validationService) { /** * Returns the LS command service - * + * * @return the command service */ public IXMLCommandService getCommandService() { @@ -503,7 +504,7 @@ public IXMLCommandService getCommandService() { /** * Sets the LS command service - * + * * @param commandService */ public void setCommandService(IXMLCommandService commandService) { diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/codeaction/IBaseCodeActionRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/codeaction/IBaseCodeActionRequest.java index 3ada5c4f16..7c63e1bfe5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/codeaction/IBaseCodeActionRequest.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/codeaction/IBaseCodeActionRequest.java @@ -17,7 +17,7 @@ /** * Base API for textDocument/codeAction and codeAction/request. - * + * * @author Angelo ZERR * */ @@ -25,15 +25,15 @@ public interface IBaseCodeActionRequest extends IComponentProvider { /** * Returns the DOM document. - * + * * @return the DOM document */ DOMDocument getDocument(); /** * Returns the shared settings. - * - * @return the shared settings.o + * + * @return the shared settings. */ SharedSettings getSharedSettings(); } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/BaseCompletionRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/BaseCompletionRequest.java new file mode 100644 index 0000000000..917591b74e --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/BaseCompletionRequest.java @@ -0,0 +1,46 @@ +/******************************************************************************* +* Copyright (c) 2022 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.services.extensions.completion; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.services.extensions.IComponentProvider; +import org.eclipse.lemminx.settings.SharedSettings; + +public class BaseCompletionRequest implements IBaseCompletionRequest { + + private final DOMDocument document; + private final IComponentProvider componentProvider; + private final SharedSettings sharedSettings; + + public BaseCompletionRequest(DOMDocument document, IComponentProvider componentProvider, + SharedSettings sharedSettings) { + this.document = document; + this.componentProvider = componentProvider; + this.sharedSettings = sharedSettings; + } + + @Override + public T getComponent(Class clazz) { + return componentProvider.getComponent(clazz); + } + + @Override + public DOMDocument getDocument() { + return document; + } + + @Override + public SharedSettings getSharedSettings() { + return sharedSettings; + } + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/CompletionParticipantAdapter.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/CompletionParticipantAdapter.java similarity index 89% rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/CompletionParticipantAdapter.java rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/CompletionParticipantAdapter.java index 718e400738..4463e349d5 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/CompletionParticipantAdapter.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/CompletionParticipantAdapter.java @@ -10,9 +10,10 @@ * Contributors: * Angelo Zerr - initial API and implementation */ -package org.eclipse.lemminx.services.extensions; +package org.eclipse.lemminx.services.extensions.completion; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.eclipse.lemminx.services.extensions.ICompletionRequest; /** * Completion participant adapter. diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/IBaseCompletionRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/IBaseCompletionRequest.java new file mode 100644 index 0000000000..5e27229970 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/IBaseCompletionRequest.java @@ -0,0 +1,33 @@ +/******************************************************************************* +* Copyright (c) 2022 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.services.extensions.completion; + +import org.eclipse.lemminx.dom.DOMDocument; +import org.eclipse.lemminx.services.extensions.IComponentProvider; +import org.eclipse.lemminx.settings.SharedSettings; + +public interface IBaseCompletionRequest extends IComponentProvider { + + /** + * Returns the DOM document. + * + * @return the DOM document + */ + DOMDocument getDocument(); + + /** + * Returns the shared settings. + * + * @return the shared settings. + */ + SharedSettings getSharedSettings(); +} \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolveParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolveParticipant.java new file mode 100644 index 0000000000..e2ed48c1de --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolveParticipant.java @@ -0,0 +1,21 @@ +/******************************************************************************* +* Copyright (c) 2022 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.services.extensions.completion; + +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.jsonrpc.CancelChecker; + +public interface ICompletionItemResolveParticipant { + + CompletionItem resolveCompletionItem(ICompletionItemResolverRequest request, CancelChecker cancelChecker); + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolverRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolverRequest.java new file mode 100644 index 0000000000..3f77db16b8 --- /dev/null +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionItemResolverRequest.java @@ -0,0 +1,25 @@ +/******************************************************************************* +* Copyright (c) 2022 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.services.extensions.completion; + +import org.eclipse.lemminx.services.extensions.ISharedSettingsRequest; +import org.eclipse.lsp4j.CompletionItem; + +public interface ICompletionItemResolverRequest extends IBaseCompletionRequest, ISharedSettingsRequest { + + CompletionItem getUnresolved(); + + String getParticipantId(); + + String getDataProperty(String fieldName); + +} diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ICompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionParticipant.java similarity index 86% rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ICompletionParticipant.java rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionParticipant.java index cd034b542d..3b09037609 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ICompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionParticipant.java @@ -10,9 +10,10 @@ * Contributors: * Angelo Zerr - initial API and implementation */ -package org.eclipse.lemminx.services.extensions; +package org.eclipse.lemminx.services.extensions.completion; import org.eclipse.lsp4j.jsonrpc.CancelChecker; +import org.eclipse.lemminx.services.extensions.ICompletionRequest; /** * Completion participant API. @@ -30,7 +31,7 @@ void onAttributeName(boolean generateValue, ICompletionRequest request, IComplet /** * Collects and stores attribute value completion items within the provided completion * response response - * + * * @param valuePrefix the attribute value before the offset in which completion was invoked * @param request the completion request * @param response the completion response @@ -38,12 +39,12 @@ void onAttributeName(boolean generateValue, ICompletionRequest request, IComplet */ void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws Exception; - + /** * Collects and stores systemId completion items within the provided completion * response response - * + * * @param valuePrefix the systemId value before the offset in which completion was invoked * @param request the completion request * @param response the completion response @@ -52,4 +53,8 @@ void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletio void onDTDSystemId(String valuePrefix, ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker) throws Exception; + default ICompletionItemResolveParticipant getResolveCompletionItemParticipant(String participantId) { + return null; + } + } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ICompletionResponse.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionResponse.java similarity index 90% rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ICompletionResponse.java rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionResponse.java index 28d4534a0b..f0a95e2fb4 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ICompletionResponse.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/completion/ICompletionResponse.java @@ -10,7 +10,7 @@ * Contributors: * Angelo Zerr - initial API and implementation */ -package org.eclipse.lemminx.services.extensions; +package org.eclipse.lemminx.services.extensions.completion; import org.eclipse.lsp4j.CompletionItem; @@ -22,7 +22,7 @@ public interface ICompletionResponse { /** * Add completion item and mark as coming from grammar. - * + * * @param completionItem * @param comingFromGrammar */ @@ -30,7 +30,7 @@ public interface ICompletionResponse { /** * Add completion item. - * + * * @param completionItem */ void addCompletionItem(CompletionItem completionItem); @@ -39,7 +39,7 @@ public interface ICompletionResponse { /** * Add completion attribute. - * + * * @param item */ void addCompletionAttribute(CompletionItem item); @@ -47,7 +47,7 @@ public interface ICompletionResponse { /** * Returns true if there are completion items coming from grammar * and false otherwise. - * + * * @return true if there are completion items coming from grammar * and false otherwise. */ diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java index 01889aaf69..21a8c78d0f 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/ServerCapabilitiesConstants.java @@ -78,7 +78,7 @@ private ServerCapabilitiesConstants() { public static final String WORKSPACE_WATCHED_FILES_ID = UUID.randomUUID().toString(); public static final String LINKED_EDITING_RANGE_ID = UUID.randomUUID().toString(); - public static final CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(false, + public static final CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(true, Arrays.asList(".", ":", "<", "\"", "=", "/", "\\", "?", "\'", "&")); public static final TextDocumentSyncKind DEFAULT_SYNC_OPTION = TextDocumentSyncKind.Full; public static final DocumentLinkOptions DEFAULT_LINK_OPTIONS = new DocumentLinkOptions(true); diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java index 4a792bc1b4..30922c0e36 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/XMLAssert.java @@ -326,7 +326,7 @@ public static CompletionItem c(String label, TextEdit textEdit, String filterTex /** * Creates a completion item with a list of additional text edits - * + * * @since 0.20.0 * @param label * @param textEdit @@ -401,6 +401,74 @@ public static void testTagCompletion(String value, AutoCloseTagResponse expected assertEquals(expected.range, actual.range); } + // ------------------- Completion Item resolve assert + + public static void testCompletionItemResolveFor(String value, String catalogPath, String fileURI, + Integer expectedCount, CompletionItem... expectedItems) throws BadLocationException { + testCompletionItemResolveFor(new XMLLanguageService(), value, catalogPath, (_a) -> { + }, fileURI, expectedCount, new SharedSettings(), expectedItems); + } + + public static void testCompletionItemResolveFor(XMLLanguageService xmlLanguageService, String value, + String catalogPath, + Consumer customConfiguration, String fileURI, Integer expectedCount, + SharedSettings sharedSettings, CompletionItem... expectedItems) throws BadLocationException { + int offset = value.indexOf('|'); + value = value.substring(0, offset) + value.substring(offset + 1); + + TextDocument document = new TextDocument(value, fileURI != null ? fileURI : "test://test/test.xml"); + Position position = document.positionAt(offset); + DOMDocument htmlDoc = DOMParser.getInstance().parse(document, xmlLanguageService.getResolverExtensionManager()); + xmlLanguageService.setDocumentProvider((uri) -> htmlDoc); + + ContentModelSettings cmSettings = new ContentModelSettings(); + cmSettings.setUseCache(false); + // Configure XML catalog for XML schema + if (catalogPath != null) { + cmSettings.setCatalogs(new String[] { catalogPath }); + } + xmlLanguageService.doSave(new SettingsSaveContext(cmSettings)); + xmlLanguageService.initializeIfNeeded(); + + if (customConfiguration != null) { + customConfiguration.accept(xmlLanguageService); + } + + CompletionList list = xmlLanguageService.doComplete(htmlDoc, position, sharedSettings); + + // no duplicate labels + List labels = list.getItems().stream().map(i -> i.getLabel()).sorted().collect(Collectors.toList()); + String previous = null; + for (String label : labels) { + if (expectedCount != null) { + continue; + } + assertNotEquals(previous, label, () -> { + return "Duplicate label " + label + " in " + labels.stream().collect(Collectors.joining(",")) + "}"; + }); + previous = label; + } + if (expectedCount != null) { + assertEquals(expectedCount.intValue(), list.getItems().size()); + } + + CompletionList resolved = new CompletionList( + list.getItems().stream() // + .map((item) -> { + return (CompletionItem) xmlLanguageService.resolveCompletionItem(item, htmlDoc, + sharedSettings, + () -> { + }); + }) // + .collect(Collectors.toList())); + + if (expectedItems != null) { + for (CompletionItem item : expectedItems) { + assertCompletion(resolved, item, expectedCount); + } + } + } + // ------------------- Diagnostics assert public static void testDiagnosticsFor(String xml, Diagnostic... expected) { diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java index 3c003e50e3..77720fba1d 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java @@ -299,7 +299,20 @@ public void completionOnAttributeValueWithUnionAndEnumeration() throws BadLocati " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n" + // " xsi:noNamespaceSchemaLocation=\"xsd/dressSize.xsd\"\r\n" + // " size=\"|\" />"; - XMLAssert.testCompletionFor(xml, null, "src/test/resources/dress.xml", 4, // + XMLAssert.testCompletionItemResolveFor(xml, null, "src/test/resources/dress.xml", 4, // + c("small", te(3, 7, 3, 7, "small"), "small"), // + c("medium", te(3, 7, 3, 7, "medium"), "medium"), // + c("large", te(3, 7, 3, 7, "large"), "large"), // + c("x-large", te(3, 7, 3, 7, "x-large"), "x-large")); + } + + @Test + public void completionOnAttributeValueWithUnionAndEnumerationResolve() throws BadLocationException { + String xml = ""; + XMLAssert.testCompletionItemResolveFor(xml, null, "src/test/resources/dress.xml", 4, // c("small", te(3, 7, 3, 7, "small"), "small", // "Small documentation" + // System.lineSeparator() + // @@ -535,7 +548,7 @@ public void completionWithXMLSchemaContentChanged() throws Exception { /** * @see https://github.com/eclipse/lemminx/issues/214 - * + * * @throws BadLocationException * @throws MalformedURIException */ @@ -595,7 +608,7 @@ public void issue214WithMarkdown() throws BadLocationException, MalformedURIExce /** * @see https://github.com/eclipse/lemminx/issues/311 - * + * * @throws BadLocationException */ @Test diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/ErrorParticipantLanguageServiceTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/ErrorParticipantLanguageServiceTest.java index 610f5eb171..97753d37d7 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/ErrorParticipantLanguageServiceTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/ErrorParticipantLanguageServiceTest.java @@ -54,6 +54,8 @@ import org.eclipse.lemminx.services.extensions.codeaction.ICodeActionRequest; import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant; import org.eclipse.lemminx.services.extensions.codelens.ICodeLensRequest; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lemminx.services.extensions.diagnostics.IDiagnosticsParticipant; import org.eclipse.lemminx.services.extensions.format.IFormatterParticipant; import org.eclipse.lemminx.settings.SharedSettings; diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/HTMLCompletionExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/HTMLCompletionExtensionsTest.java index 5abb96b0a9..4aa6d00659 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/HTMLCompletionExtensionsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/services/extensions/HTMLCompletionExtensionsTest.java @@ -18,6 +18,9 @@ import org.eclipse.lemminx.XMLAssert; import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lemminx.services.XMLLanguageService; +import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter; +import org.eclipse.lemminx.services.extensions.completion.ICompletionParticipant; +import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse; import org.eclipse.lsp4j.CompletionItem; import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.InsertTextFormat; @@ -95,20 +98,20 @@ public void testHTMLAttributeNameCompletion() throws BadLocationException { /* * testCompletionFor("|", // c("Test replace range", "replacement text", r(0, 7, 0, 7), null)); testCompletionFor(" |", //