Skip to content

Commit

Permalink
completionItem/resolve support
Browse files Browse the repository at this point in the history
Collect model documentation for completion items in the resolve step

Fixes eclipse#616, redhat-developer/vscode-xml#679

Signed-off-by: David Thompson <[email protected]>
  • Loading branch information
datho7561 committed Jun 22, 2022
1 parent 22d0cff commit 09435b2
Show file tree
Hide file tree
Showing 36 changed files with 730 additions and 263 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,13 @@ public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completio
});
}

@Override
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem unresolved) {
return computeDOMAsync(unresolved.getData(), (xmlDocument, cancelChecker) -> {
return getXMLLanguageService().resolveCompletionItem(unresolved, xmlDocument, sharedSettings, cancelChecker);
});
}

@Override
public CompletableFuture<Hover> hover(HoverParams params) {
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

Expand All @@ -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;
Expand All @@ -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<String, ICompletionItemResolveParticipant> 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);
Expand Down Expand Up @@ -122,7 +137,7 @@ private boolean hasNamespace(String namespaceURI, Collection<CMDocument> cmRootD

/**
* Fill with possible element declarations.
*
*
* @param parentElement the parent DOM element
* @param cmElement the content model element declaration
* @param defaultPrefix
Expand Down Expand Up @@ -150,7 +165,7 @@ private static void fillWithPossibleElementDeclaration(DOMElement parentElement,

/**
* Fill with children element declarations
*
*
* @param element
* @param cmDocument
* @param cmElements
Expand Down Expand Up @@ -201,7 +216,7 @@ private static void fillCompletionItem(Collection<CMElementDeclaration> elements

/**
* Add completion item with all tag names of the node list.
*
*
* @param list
* @param tags
* @param request
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -301,20 +317,24 @@ 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);
}
}
}

@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) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<CMDocument> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 09435b2

Please sign in to comment.