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 23, 2022
1 parent 22d0cff commit 4215e6c
Show file tree
Hide file tree
Showing 51 changed files with 1,099 additions and 323 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,16 @@
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.AbstractCMCompletionResolver;
import org.eclipse.lemminx.extensions.contentmodel.participants.completion.AttributeNameCompletionResolver;
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.extensions.ICompletionRequest;
import org.eclipse.lemminx.services.extensions.ICompletionResponse;
import org.eclipse.lemminx.services.data.DataEntryField;
import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter;
import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolveParticipant;
import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest;
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 +51,26 @@
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());
completionResolvers.put(AttributeNameCompletionResolver.PARTICIPANT_ID, new AttributeNameCompletionResolver());
}

@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 +140,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 +168,7 @@ private static void fillWithPossibleElementDeclaration(DOMElement parentElement,

/**
* Fill with children element declarations
*
*
* @param element
* @param cmDocument
* @param cmElements
Expand Down Expand Up @@ -201,7 +219,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 +282,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 +320,29 @@ 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);
item.setDocumentation(documentation);
attributeDeclaration.getDefaultValue(), attributeDeclaration.getEnumerationValues(),
request.getSharedSettings());
if (request.isResolveDocumentationSupported()) {
addResolveData(request, item, AttributeNameCompletionResolver.PARTICIPANT_ID);
} else {
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 @@ -346,21 +374,26 @@ private void fillAttributeValuesWithCMAttributeDeclarations(CMElementDeclaration
Range fullRange = request.getReplaceRange();
cmAttribute.getEnumerationValues().forEach(value -> {
CompletionItem item = new CompletionItem();
item.setLabel(value);
String insertText = request.getInsertAttrValue(value);
item.setLabel(value);
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);

if (request.isResolveDocumentationSupported()) {
addResolveData(request, item, AttributeValueCompletionResolver.PARTICIPANT_ID);
} else {
item.setDocumentation(XMLGenerator.createMarkupContent(cmAttribute, value, cmElement,
request));
}
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 +437,18 @@ 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);
}

private void addResolveData(ICompletionRequest request, CompletionItem item, String participantId) {
DOMDocument document = request.getNode().getOwnerDocument();
JsonObject data = DataEntryField.createData(document.getDocumentURI(),
participantId);
data.addProperty(AbstractCMCompletionResolver.OFFSET_KEY,
request.getOffset());
item.setData(data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.eclipse.lemminx.extensions.contentmodel.participants.completion;

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.services.extensions.completion.ICompletionItemResolveParticipant;
import org.eclipse.lemminx.services.extensions.completion.ICompletionItemResolverRequest;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;

public abstract class AbstractCMCompletionResolver implements ICompletionItemResolveParticipant {

public static final String OFFSET_KEY = "OFFSET";

@Override
public CompletionItem resolveCompletionItem(ICompletionItemResolverRequest request, CancelChecker cancelChecker) {
CompletionItem toResolve = request.getUnresolved();

DOMDocument document = request.getDocument();

Integer offset = request.getDataPropertyAsInt(OFFSET_KEY);
if (document == null || offset == null) {
return toResolve;
}

DOMAttr attr = document.findAttrAt(offset);

DOMNode parentNode = document.findNodeAt(offset);
if (parentNode == null || !parentNode.isElement()) {
return toResolve;
}
DOMElement parentElement = (DOMElement) parentNode;

addDocumentationToCompletion(request, toResolve, parentElement, attr);
return toResolve;
}

/**
* Add the documentation from the xml model to the given completion request.
*
* @param request the completion resolve request
* @param toResolve the unresolved completion item to resolve
* @param parentElement the parent element to where completion was opened on
* @param attr the attribute that completion was opened on, may be null
*/
abstract void addDocumentationToCompletion(ICompletionItemResolverRequest request,
CompletionItem toResolve,
DOMElement parentElement, DOMAttr attr);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package org.eclipse.lemminx.extensions.contentmodel.participants.completion;

import java.util.Collection;

import org.eclipse.lemminx.dom.DOMAttr;
import org.eclipse.lemminx.dom.DOMElement;
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.ICompletionItemResolverRequest;
import org.eclipse.lemminx.uriresolver.CacheResourceDownloadingException;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.MarkupContent;

public class AttributeNameCompletionResolver extends AbstractCMCompletionResolver {

public static final String PARTICIPANT_ID = AttributeNameCompletionResolver.class.getName();

@Override
void addDocumentationToCompletion(ICompletionItemResolverRequest request, CompletionItem toResolve,
DOMElement parentElement, DOMAttr attr) {
String attributeName = request.getUnresolved().getFilterText();
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, request);
if (documentation != null) {
toResolve.setDocumentation(documentation);
return;
}
}
}
} catch (CacheResourceDownloadingException e) {
}

}

private MarkupContent getDocumentationForAttributeValue(CMElementDeclaration cmElement, String attributeName,
ICompletionItemResolverRequest request) {
CMAttributeDeclaration cmAttribute = cmElement.findCMAttribute(attributeName);
if (cmAttribute != null) {
return XMLGenerator.createMarkupContent(cmAttribute, cmElement,
request);
}
return null;
}

}
Loading

0 comments on commit 4215e6c

Please sign in to comment.