From fa8f51b17ec3409c89e4d6f23679aaac4a8d540e Mon Sep 17 00:00:00 2001
From: azerr
Date: Wed, 4 Jan 2023 10:32:39 +0100
Subject: [PATCH] CodeLens, References, Rename support for XML references
Signed-off-by: azerr
---
.../lemminx/XMLTextDocumentService.java | 17 +-
.../org/eclipse/lemminx/dom/DOMElement.java | 11 +-
.../java/org/eclipse/lemminx/dom/DOMNode.java | 11 +
.../references/XMLReferencesPlugin.java | 24 ++
.../XMLReferencesCodeLensParticipant.java | 128 ++++++
.../XMLReferencesCompletionParticipant.java | 101 +++--
.../XMLReferencesDefinitionParticipant.java | 34 +-
.../XMLReferencesHighlightingParticipant.java | 57 ++-
...erencesLinkedEditingRangesParticipant.java | 78 ++++
.../XMLReferencesReferenceParticipant.java | 66 ++++
.../XMLReferencesRenameParticipant.java | 153 +++++++
.../IXMLReferenceCollector.java} | 21 +-
.../references/search/SearchEngine.java | 305 ++++++++++++++
.../references/search/SearchNode.java | 147 +++++++
.../references/search/SearchNodeFactory.java | 134 +++++++
.../references/search/SearchQuery.java | 108 +++++
.../references/search/SearchQueryFactory.java | 179 +++++++++
.../settings/XMLReferenceExpression.java | 10 +
.../utils/XMLReferencesSearchContext.java | 77 ----
.../references/utils/XMLReferencesUtils.java | 372 ------------------
.../grammar/rng/RNGRenameParticipant.java | 95 +++--
.../participants/XSDRenameParticipant.java | 111 ++++--
.../services/LinkedEditingRangesRequest.java | 31 ++
.../services/PrepareRenameRequest.java | 25 ++
.../lemminx/services/XMLLanguageService.java | 16 +-
.../lemminx/services/XMLLinkedEditing.java | 55 ++-
.../lemminx/services/XMLPrepareRename.java | 91 +++++
.../eclipse/lemminx/services/XMLRename.java | 22 +-
.../ILinkedEditingRangesParticipant.java | 37 ++
.../ILinkedEditingRangesRequest.java | 19 +
.../extensions/IPrepareRenameRequest.java | 20 +
.../extensions/IRenameParticipant.java | 8 +-
.../services/extensions/IRenameRequest.java | 2 +-
.../extensions/XMLExtensionsRegistry.java | 17 +
.../ServerCapabilitiesConstants.java | 4 +-
.../capabilities/XMLCapabilityManager.java | 3 +-
.../eclipse/lemminx/utils/StringUtils.java | 29 +-
.../java/org/eclipse/lemminx/XMLAssert.java | 36 +-
.../XMLReferencesCodeLensExtensionsTest.java | 125 ++++++
...XMLReferencesCompletionExtensionsTest.java | 285 +++++++++++---
...XMLReferencesDefinitionExtensionsTest.java | 104 +++--
...LReferencesHighlightingExtensionsTest.java | 163 +++++---
...ncesLinkedEditingRangesExtensionsTest.java | 199 ++++++++++
.../XMLReferencesReferenceExtensionsTest.java | 97 +++++
.../XMLReferencesRenameExtensionsTest.java | 150 +++++++
.../XMLReferencesSettingsForTest.java | 100 +++++
.../ErrorParticipantLanguageServiceTest.java | 19 +-
47 files changed, 3048 insertions(+), 848 deletions(-)
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCodeLensParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesLinkedEditingRangesParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesReferenceParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesRenameParticipant.java
rename org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/{utils/IXMLReferenceTosCollector.java => search/IXMLReferenceCollector.java} (50%)
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchEngine.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQuery.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQueryFactory.java
delete mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesSearchContext.java
delete mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesUtils.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/LinkedEditingRangesRequest.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/PrepareRenameRequest.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLPrepareRename.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesParticipant.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesRequest.java
create mode 100644 org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IPrepareRenameRequest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCodeLensExtensionsTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesLinkedEditingRangesExtensionsTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesReferenceExtensionsTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesRenameExtensionsTest.java
create mode 100644 org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesSettingsForTest.java
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 7e4db3f20c..8bed29a902 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
@@ -89,7 +89,10 @@
import org.eclipse.lsp4j.LinkedEditingRanges;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
+import org.eclipse.lsp4j.PrepareRenameParams;
+import org.eclipse.lsp4j.PrepareRenameResult;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
+import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ReferenceParams;
import org.eclipse.lsp4j.RenameParams;
import org.eclipse.lsp4j.SelectionRange;
@@ -349,10 +352,18 @@ public CompletableFuture> rangeFormatting(DocumentRange
});
}
+ @Override
+ public CompletableFuture> prepareRename(PrepareRenameParams params) {
+ return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
+ return getXMLLanguageService().prepareRename(xmlDocument, params.getPosition(), cancelChecker);
+ });
+ }
+
@Override
public CompletableFuture rename(RenameParams params) {
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
- return getXMLLanguageService().doRename(xmlDocument, params.getPosition(), params.getNewName());
+ return getXMLLanguageService().doRename(xmlDocument, params.getPosition(), params.getNewName(),
+ cancelChecker);
});
}
@@ -563,14 +574,14 @@ public CompletableFuture> documentColor(DocumentColorPara
return getXMLLanguageService().findDocumentColors(xmlDocument, cancelChecker);
});
}
-
+
@Override
public CompletableFuture> colorPresentation(ColorPresentationParams params) {
return computeDOMAsync(params.getTextDocument(), (xmlDocument, cancelChecker) -> {
return getXMLLanguageService().getColorPresentations(xmlDocument, params, cancelChecker);
});
}
-
+
@Override
public void didSave(DidSaveTextDocumentParams params) {
computeAsync((monitor) -> {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java
index 9705bc9d4c..f0351c635a 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java
@@ -631,15 +631,6 @@ public DOMText findTextAt(int offset) {
text.parent = this;
return text;
}
- DOMNode node = super.findNodeAt(offset);
- if (node != null) {
- if (node.isText()) {
- return (DOMText) node;
- }
- if (node.isElement() && node != this) {
- return ((DOMElement) node).findTextAt(offset);
- }
- }
- return null;
+ return findTextAt(this, offset);
}
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java
index 0e1f49b1d3..e9e6b8af3c 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMNode.java
@@ -289,6 +289,17 @@ public static DOMAttr findAttrAt(DOMNode node, int offset) {
return null;
}
+ public static DOMText findTextAt(DOMNode node, int offset) {
+ if (node != null && node.hasChildNodes()) {
+ for (DOMNode child : node.getChildren()) {
+ if (child.isText() && isIncluded(child, offset)) {
+ return (DOMText) child;
+ }
+ }
+ }
+ return null;
+ }
+
public static DOMNode findNodeOrAttrAt(DOMDocument document, int offset) {
DOMNode node = document.findNodeAt(offset);
if (node != null) {
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 a42a653dd5..a58968c387 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
@@ -11,14 +11,22 @@
*******************************************************************************/
package org.eclipse.lemminx.extensions.references;
+import org.eclipse.lemminx.extensions.references.participants.XMLReferencesCodeLensParticipant;
import org.eclipse.lemminx.extensions.references.participants.XMLReferencesCompletionParticipant;
import org.eclipse.lemminx.extensions.references.participants.XMLReferencesDefinitionParticipant;
import org.eclipse.lemminx.extensions.references.participants.XMLReferencesHighlightingParticipant;
+import org.eclipse.lemminx.extensions.references.participants.XMLReferencesLinkedEditingRangesParticipant;
+import org.eclipse.lemminx.extensions.references.participants.XMLReferencesReferenceParticipant;
+import org.eclipse.lemminx.extensions.references.participants.XMLReferencesRenameParticipant;
import org.eclipse.lemminx.extensions.references.settings.XMLReferencesSettings;
import org.eclipse.lemminx.services.extensions.IDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.IHighlightingParticipant;
+import org.eclipse.lemminx.services.extensions.ILinkedEditingRangesParticipant;
+import org.eclipse.lemminx.services.extensions.IReferenceParticipant;
+import org.eclipse.lemminx.services.extensions.IRenameParticipant;
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.save.ISaveContext;
import org.eclipse.lsp4j.InitializeParams;
@@ -66,14 +74,22 @@ public class XMLReferencesPlugin implements IXMLExtension {
private final ICompletionParticipant completionParticipant;
private final IDefinitionParticipant definitionParticipant;
+ private final IReferenceParticipant referenceParticipant;
+ private final ICodeLensParticipant codeLensParticipant;
private final IHighlightingParticipant highlightingParticipant;
+ private final IRenameParticipant renameParticipant;
+ private final ILinkedEditingRangesParticipant linkedEditingRangesParticipant;
private XMLReferencesSettings referencesSettings;
public XMLReferencesPlugin() {
completionParticipant = new XMLReferencesCompletionParticipant(this);
definitionParticipant = new XMLReferencesDefinitionParticipant(this);
+ referenceParticipant = new XMLReferencesReferenceParticipant(this);
+ codeLensParticipant = new XMLReferencesCodeLensParticipant(this);
highlightingParticipant = new XMLReferencesHighlightingParticipant(this);
+ renameParticipant = new XMLReferencesRenameParticipant(this);
+ linkedEditingRangesParticipant = new XMLReferencesLinkedEditingRangesParticipant(this);
}
@Override
@@ -99,14 +115,22 @@ private void updateSettings(XMLReferencesSettings settings, ISaveContext context
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
registry.registerCompletionParticipant(completionParticipant);
registry.registerDefinitionParticipant(definitionParticipant);
+ registry.registerReferenceParticipant(referenceParticipant);
+ registry.registerCodeLensParticipant(codeLensParticipant);
registry.registerHighlightingParticipant(highlightingParticipant);
+ registry.registerRenameParticipant(renameParticipant);
+ registry.registerLinkedEditingRangesParticipants(linkedEditingRangesParticipant);
}
@Override
public void stop(XMLExtensionsRegistry registry) {
registry.unregisterCompletionParticipant(completionParticipant);
registry.unregisterDefinitionParticipant(definitionParticipant);
+ registry.unregisterReferenceParticipant(referenceParticipant);
+ registry.unregisterCodeLensParticipant(codeLensParticipant);
registry.unregisterHighlightingParticipant(highlightingParticipant);
+ registry.unregisterRenameParticipant(renameParticipant);
+ registry.unregisterLinkedEditingRangesParticipants(linkedEditingRangesParticipant);
}
public XMLReferencesSettings getReferencesSettings() {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCodeLensParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCodeLensParticipant.java
new file mode 100644
index 0000000000..9e4c0eaecf
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesCodeLensParticipant.java
@@ -0,0 +1,128 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.participants;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.lemminx.client.CodeLensKind;
+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.references.XMLReferencesPlugin;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchNode;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery.Direction;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
+import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
+import org.eclipse.lemminx.services.extensions.codelens.ICodeLensParticipant;
+import org.eclipse.lemminx.services.extensions.codelens.ICodeLensRequest;
+import org.eclipse.lemminx.services.extensions.codelens.ReferenceCommand;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.CodeLens;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * XML references codelens support.
+ *
+ */
+public class XMLReferencesCodeLensParticipant implements ICodeLensParticipant {
+
+ private static class Link {
+
+ public final List froms;
+ public final List tos;
+
+ public Link() {
+ froms = new ArrayList<>();
+ tos = new ArrayList<>();
+ }
+
+ public void addTo(SearchNode to) {
+ tos.add(to);
+ }
+
+ public void addFrom(SearchNode from) {
+ froms.add(from);
+ }
+ }
+
+ private final XMLReferencesPlugin plugin;
+
+ public XMLReferencesCodeLensParticipant(XMLReferencesPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void doCodeLens(ICodeLensRequest request, List lenses, CancelChecker cancelChecker) {
+ DOMDocument document = request.getDocument();
+ SearchQuery query = SearchQueryFactory.createQuery(document,
+ plugin.getReferencesSettings(), Direction.BOTH);
+ if (query != null) {
+ final Map linksMap = new HashMap<>();
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> {
+ Link link = linksMap.get(expression);
+ if (link == null) {
+ link = new Link();
+ linksMap.put(expression, link);
+ }
+ if (fromSearchNode != null) {
+ link.addFrom(fromSearchNode);
+ }
+ if (toSearchNode != null) {
+ link.addTo(toSearchNode);
+ }
+ }, cancelChecker);
+ if (!linksMap.isEmpty()) {
+ boolean supportedByClient = request.isSupportedByClient(CodeLensKind.References);
+ Map cache = new HashMap<>();
+ Collection links = linksMap.values();
+ for (Link link : links) {
+ for (SearchNode to : link.tos) {
+ // Increment references count Codelens for the given target element
+ DOMNode toNode = to.getNode();
+ DOMElement toElement = toNode.isAttribute() ? ((DOMAttr) toNode).getOwnerElement()
+ : toNode.getParentElement();
+ if (toElement != null) {
+ for (SearchNode from : link.froms) {
+ if (from.matchesValue(to)) {
+
+ CodeLens codeLens = cache.get(toElement);
+ if (codeLens == null) {
+ Range range = XMLPositionUtility.createRange(toNode);
+ codeLens = new CodeLens(range);
+ codeLens.setCommand(
+ new ReferenceCommand(document.getDocumentURI(), range.getStart(),
+ supportedByClient));
+ cache.put(toElement, codeLens);
+ lenses.add(codeLens);
+ } else {
+ ((ReferenceCommand) codeLens.getCommand()).increment();
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
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 e33c0f67f3..47bbb9a39d 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
@@ -11,15 +11,16 @@
*******************************************************************************/
package org.eclipse.lemminx.extensions.references.participants;
-import org.eclipse.lemminx.dom.DOMElement;
-import org.eclipse.lemminx.dom.DOMNode;
+import java.util.concurrent.atomic.AtomicReference;
+
import org.eclipse.lemminx.extensions.references.XMLReferencesPlugin;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
-import org.eclipse.lemminx.extensions.references.utils.XMLReferencesSearchContext;
-import org.eclipse.lemminx.extensions.references.utils.XMLReferencesUtils;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
import org.eclipse.lemminx.services.extensions.completion.CompletionParticipantAdapter;
import org.eclipse.lemminx.services.extensions.completion.ICompletionRequest;
import org.eclipse.lemminx.services.extensions.completion.ICompletionResponse;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.Range;
@@ -44,52 +45,70 @@ public XMLReferencesCompletionParticipant(XMLReferencesPlugin plugin) {
@Override
public void onXMLContent(ICompletionRequest request, ICompletionResponse response, CancelChecker cancelChecker)
throws Exception {
- DOMNode fromNode = request.getNode();
- if (fromNode.isElement()) {
- fromNode = ((DOMElement) fromNode).findTextAt(request.getOffset());
- }
- searchToNodes(fromNode, request, response);
+ searchToNodes(request, response, cancelChecker);
}
@Override
public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response,
CancelChecker cancelChecker) throws Exception {
- DOMNode node = request.getNode();
- DOMNode fromNode = node.findAttrAt(request.getOffset());
- searchToNodes(fromNode, request, response);
+ searchToNodes(request, response, cancelChecker);
}
- private void searchToNodes(DOMNode fromNode, ICompletionRequest request, ICompletionResponse response) {
- XMLReferencesSearchContext searchContext = XMLReferencesUtils.findExpressionsWhichMatchFrom(fromNode,
+ private void searchToNodes(ICompletionRequest request, ICompletionResponse response,
+ CancelChecker cancelChecker) {
+ // Create the from query for the node which needs to perform the search.
+ SearchQuery query = SearchQueryFactory.createFromQuery(request.getNode(), request.getOffset(),
plugin.getReferencesSettings());
- if (searchContext != null) {
- XMLReferencesUtils.searchToNodes(fromNode, searchContext, false, true,
- (toNamespacePrefix, toNode, expression) -> {
- CompletionItem item = new CompletionItem();
- String value = createReferenceValue(toNode, toNamespacePrefix, expression);
- String insertText = request.getInsertAttrValue(value);
- item.setLabel(value);
- item.setKind(CompletionItemKind.Value);
- item.setFilterText(insertText);
- Range fullRange = request.getReplaceRange();
- item.setTextEdit(Either.forLeft(new TextEdit(fullRange, insertText)));
- response.addCompletionItem(item);
- });
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return;
}
- }
+ query.setMatchNode(false);
+ query.setSearchInIncludedFiles(true);
- private static String createReferenceValue(DOMNode toNode, String toNamespacePrefix,
- XMLReferenceExpression expression) {
- StringBuilder value = new StringBuilder();
- if (expression.getPrefix() != null) {
- value.append(expression.getPrefix());
- }
- if (toNamespacePrefix != null) {
- value.append(toNamespacePrefix);
- value.append(":");
- }
- value.append(XMLReferencesUtils.getNodeValue(toNode));
- return value.toString();
+ AtomicReference replaceRange = new AtomicReference<>(null);
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> {
+ CompletionItem item = new CompletionItem();
+ String value = toSearchNode.getValue(fromSearchNode.getPrefix());
+ String insertText = request.getInsertAttrValue(value);
+ item.setLabel(value);
+ item.setKind(CompletionItemKind.Value);
+ item.setFilterText(insertText);
+ Range fullRange = replaceRange.get();
+ if (fullRange == null) {
+ replaceRange.set(XMLPositionUtility.createRange(fromSearchNode));
+ fullRange = replaceRange.get();
+ }
+ item.setTextEdit(Either.forLeft(new TextEdit(fullRange, insertText)));
+ response.addCompletionItem(item);
+ }, cancelChecker);
+
+// XMLReferencesSearchContext searchContext = XMLReferencesUtils.findExpressionsWhichMatchFrom(request.getNode(),
+// request.getOffset(),
+// plugin.getReferencesSettings());
+// if (searchContext != null) {
+// AtomicReference replaceRangex = new AtomicReference<>(null);
+// XMLReferencesUtils.searchToNodes(searchContext, false, true,
+// (fromSearchNode, toSearchNode) -> {
+// CompletionItem item = new CompletionItem();
+// String value = toSearchNode.getValue();
+// String insertText = request.getInsertAttrValue(value);
+// item.setLabel(value);
+// item.setKind(CompletionItemKind.Value);
+// item.setFilterText(insertText);
+// Range fullRange = replaceRange.get();
+// if (fullRange == null) {
+// replaceRange.set(XMLPositionUtility.createRange(fromSearchNode));
+// fullRange = replaceRange.get();
+// }
+// item.setTextEdit(Either.forLeft(new TextEdit(fullRange, insertText)));
+// response.addCompletionItem(item);
+// }, cancelChecker);
+// }
}
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesDefinitionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesDefinitionParticipant.java
index 53f4a61869..b232b0b33f 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesDefinitionParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesDefinitionParticipant.java
@@ -14,10 +14,10 @@
import java.util.List;
import org.eclipse.lemminx.dom.DOMDocument;
-import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.extensions.references.XMLReferencesPlugin;
-import org.eclipse.lemminx.extensions.references.utils.XMLReferencesSearchContext;
-import org.eclipse.lemminx.extensions.references.utils.XMLReferencesUtils;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
import org.eclipse.lemminx.services.extensions.AbstractDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.IDefinitionRequest;
import org.eclipse.lemminx.utils.XMLPositionUtility;
@@ -46,18 +46,26 @@ protected boolean match(DOMDocument document) {
@Override
protected void doFindDefinition(IDefinitionRequest request, List locations,
CancelChecker cancelChecker) {
- DOMNode fromNode = request.getNode();
- XMLReferencesSearchContext searchContext = XMLReferencesUtils.findExpressionsWhichMatchFrom(fromNode,
+ // Create the from query for the node which needs to perform the search.
+ SearchQuery query = SearchQueryFactory.createFromQuery(request.getNode(), request.getOffset(),
plugin.getReferencesSettings());
- if (searchContext != null) {
- XMLReferencesUtils.searchToNodes(fromNode, searchContext, true, true,
- (toNamespacePrefix, toNode, expression) -> {
- LocationLink location = XMLPositionUtility.createLocationLink(
- XMLReferencesUtils.getNodeRange(fromNode),
- XMLReferencesUtils.getNodeRange(toNode));
- locations.add(location);
- });
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return;
}
+ query.setMatchNode(true);
+ query.setSearchInIncludedFiles(true);
+
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> {
+ LocationLink location = XMLPositionUtility.createLocationLink(
+ XMLPositionUtility.createRange(fromSearchNode),
+ toSearchNode);
+ locations.add(location);
+ }, cancelChecker);
}
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesHighlightingParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesHighlightingParticipant.java
index b64134f350..158651255a 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesHighlightingParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesHighlightingParticipant.java
@@ -12,11 +12,14 @@
package org.eclipse.lemminx.extensions.references.participants;
import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.extensions.references.XMLReferencesPlugin;
-import org.eclipse.lemminx.extensions.references.utils.XMLReferencesSearchContext;
-import org.eclipse.lemminx.extensions.references.utils.XMLReferencesUtils;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery.Direction;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
import org.eclipse.lemminx.services.extensions.IHighlightingParticipant;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.DocumentHighlight;
@@ -41,24 +44,44 @@ public XMLReferencesHighlightingParticipant(XMLReferencesPlugin plugin) {
@Override
public void findDocumentHighlights(DOMNode node, Position position, int offset, List highlights,
CancelChecker cancelChecker) {
- DOMNode fromNode = node;
- if (fromNode.isElement()) {
- fromNode = fromNode.findAttrAt(offset);
+ // Create the from query for the node which needs to perform the search.
+ SearchQuery query = SearchQueryFactory.createQuery(node, offset, plugin.getReferencesSettings());
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return;
}
- XMLReferencesSearchContext searchContext = XMLReferencesUtils.findExpressionsWhichMatchFrom(fromNode,
- plugin.getReferencesSettings());
- if (searchContext != null) {
- highlights
- .add(new DocumentHighlight(
- XMLPositionUtility.createRange(XMLReferencesUtils.getNodeRange(fromNode)),
- DocumentHighlightKind.Read));
- XMLReferencesUtils.searchToNodes(fromNode, searchContext, true, false,
- (toNamespacePrefix, toNode, expression) -> {
+
+ query.setMatchNode(true);
+ query.setSearchInIncludedFiles(false);
+
+ AtomicBoolean added = new AtomicBoolean(false);
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> {
+ if (!added.get()) {
+ if (query.getDirection() == Direction.FROM_2_TO) {
+ highlights.add(new DocumentHighlight(
+ XMLPositionUtility.createRange(fromSearchNode),
+ DocumentHighlightKind.Read));
+ } else {
+ highlights.add(new DocumentHighlight(
+ XMLPositionUtility.createRange(toSearchNode),
+ DocumentHighlightKind.Write));
+ }
+ added.set(true);
+ }
+ if (query.getDirection() == Direction.FROM_2_TO) {
highlights.add(new DocumentHighlight(
- XMLPositionUtility.createRange(XMLReferencesUtils.getNodeRange(toNode)),
+ XMLPositionUtility.createRange(toSearchNode),
DocumentHighlightKind.Write));
- });
- }
+ } else {
+ highlights.add(new DocumentHighlight(
+ XMLPositionUtility.createRange(fromSearchNode),
+ DocumentHighlightKind.Read));
+ }
+ }, cancelChecker);
}
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesLinkedEditingRangesParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesLinkedEditingRangesParticipant.java
new file mode 100644
index 0000000000..bd91c85aaf
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesLinkedEditingRangesParticipant.java
@@ -0,0 +1,78 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.participants;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.lemminx.extensions.references.XMLReferencesPlugin;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchNode;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
+import org.eclipse.lemminx.services.extensions.ILinkedEditingRangesParticipant;
+import org.eclipse.lemminx.services.extensions.ILinkedEditingRangesRequest;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * XML references linked edting ranges.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class XMLReferencesLinkedEditingRangesParticipant implements ILinkedEditingRangesParticipant {
+
+ private final XMLReferencesPlugin plugin;
+
+ public XMLReferencesLinkedEditingRangesParticipant(XMLReferencesPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void findLinkedEditingRanges(ILinkedEditingRangesRequest request, List ranges,
+ CancelChecker cancelChecker) {
+ final AtomicBoolean added = new AtomicBoolean(false);
+ SearchQuery query = SearchQueryFactory.createToQuery(request.getNode(), request.getOffset(),
+ plugin.getReferencesSettings());
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return;
+ }
+ query.setMatchNode(true);
+ query.setSearchInIncludedFiles(true);
+
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> {
+ if (!added.get()) {
+ ranges.add(XMLPositionUtility.createRange(toSearchNode));
+ added.set(true);
+ }
+ ranges.add(createRange(fromSearchNode));
+ },
+ cancelChecker);
+
+ }
+
+ private static Range createRange(SearchNode node) {
+ Range range = XMLPositionUtility.createRange(node);
+ String prefix = node.getPrefix();
+ if (prefix != null) {
+ range.getStart().setCharacter(range.getStart().getCharacter() + prefix.length());
+ }
+ return range;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesReferenceParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesReferenceParticipant.java
new file mode 100644
index 0000000000..d003533400
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesReferenceParticipant.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.participants;
+
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.extensions.references.XMLReferencesPlugin;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
+import org.eclipse.lemminx.services.extensions.AbstractReferenceParticipant;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.ReferenceContext;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * XML references reference support.
+ *
+ */
+public class XMLReferencesReferenceParticipant extends AbstractReferenceParticipant {
+
+ private final XMLReferencesPlugin plugin;
+
+ public XMLReferencesReferenceParticipant(XMLReferencesPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ protected boolean match(DOMDocument document) {
+ return true;
+ }
+
+ @Override
+ protected void findReferences(DOMNode node, Position position, int offset, ReferenceContext context,
+ List locations, CancelChecker cancelChecker) {
+ SearchQuery query = SearchQueryFactory.createToQuery(node, offset, plugin.getReferencesSettings());
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return;
+ }
+ query.setMatchNode(true);
+ query.setSearchInIncludedFiles(true);
+
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> locations
+ .add(XMLPositionUtility.createLocation(fromSearchNode)),
+ cancelChecker);
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesRenameParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesRenameParticipant.java
new file mode 100644
index 0000000000..7972756a04
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/participants/XMLReferencesRenameParticipant.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.participants;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMAttr;
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.dom.DOMRange;
+import org.eclipse.lemminx.extensions.references.XMLReferencesPlugin;
+import org.eclipse.lemminx.extensions.references.search.SearchEngine;
+import org.eclipse.lemminx.extensions.references.search.SearchNode;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery;
+import org.eclipse.lemminx.extensions.references.search.SearchQueryFactory;
+import org.eclipse.lemminx.services.extensions.IPrepareRenameRequest;
+import org.eclipse.lemminx.services.extensions.IRenameParticipant;
+import org.eclipse.lemminx.services.extensions.IRenameRequest;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.PrepareRenameResult;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+
+/**
+ * XML references rename support.
+ *
+ */
+public class XMLReferencesRenameParticipant implements IRenameParticipant {
+
+ private final XMLReferencesPlugin plugin;
+
+ public XMLReferencesRenameParticipant(XMLReferencesPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Either prepareRename(IPrepareRenameRequest request,
+ CancelChecker cancelChecker) {
+ // FIXME: when rename from 'from' node will be avalaible, we need just replace with SearchQueryFactory.createQuery
+ SearchQuery query = SearchQueryFactory.createToQuery(request.getNode(), request.getOffset(),
+ plugin.getReferencesSettings());
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return null;
+ }
+ SearchNode searchNode = query.getSearchNode();
+ if (searchNode == null) {
+ return null;
+ }
+ Range range = XMLPositionUtility.createRange(searchNode);
+ String placeholder = searchNode.getValue(null);
+ return Either.forRight(new PrepareRenameResult(range, placeholder));
+ }
+
+ @Override
+ public void doRename(IRenameRequest request, List locations, CancelChecker cancelChecker) {
+ locations.addAll(getRenameTextEdits(request, cancelChecker));
+ }
+
+ private List getRenameTextEdits(IRenameRequest request, CancelChecker cancelChecker) {
+ SearchQuery query = SearchQueryFactory.createToQuery(request.getNode(), request.getOffset(),
+ plugin.getReferencesSettings());
+ if (query == null) {
+ // The query cannot be created because:
+ // - the node is neither a text nor an attribute
+ // - it doesn't exists some expressions for the DOM document of the node.
+ // - there are none expressions which matches the node.
+ return Collections.emptyList();
+ }
+ query.setMatchNode(true);
+ query.setSearchInIncludedFiles(true);
+
+ List locations = new ArrayList<>();
+ SearchEngine.getInstance().search(query,
+ (fromSearchNode, toSearchNode, expression) -> locations
+ .add(XMLPositionUtility.createLocation(fromSearchNode)),
+ cancelChecker);
+ if (locations.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ String newText = request.getNewText();
+ DOMNode node = query.getNode();
+ return renameAttributeValueTextEdits(node, newText, locations);
+ }
+
+ private List renameAttributeValueTextEdits(DOMNode node, String newText,
+ List locations) {
+ DOMRange attrValue = getNodeRange(node);
+ List textEdits = new ArrayList<>();
+ Range range = XMLPositionUtility.createRange(attrValue);
+
+ // make range not cover " on both ends
+ reduceRangeFromBothEnds(range, 1);
+
+ textEdits.add(new TextEdit(range, newText));
+
+ for (Location location : locations) {
+ Range textEditRange = location.getRange();
+ reduceRangeFromBothEnds(textEditRange, 1);
+
+ TextEdit textEdit = new TextEdit(textEditRange, newText);
+ textEdits.add(textEdit);
+ }
+
+ return textEdits;
+ }
+
+ private void reduceRangeFromBothEnds(Range range, int reduce) {
+ increaseStartRange(range, reduce);
+ decreaseEndRange(range, reduce);
+ }
+
+ private void increaseStartRange(Range range, int increase) {
+ int startCharacter = range.getStart().getCharacter();
+ range.getStart().setCharacter(startCharacter + increase);
+ }
+
+ private void decreaseEndRange(Range range, int decrease) {
+ int endCharacter = range.getEnd().getCharacter();
+ range.getEnd().setCharacter(endCharacter - decrease);
+ }
+
+ /**
+ * Returns the range of the given DOM node.
+ *
+ * @param node the DOM node.
+ *
+ * @return the range of the given DOM node.
+ */
+ public static DOMRange getNodeRange(DOMNode node) {
+ if (node.isAttribute()) {
+ return ((DOMAttr) node).getNodeAttrValue();
+ }
+ return node;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/IXMLReferenceTosCollector.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/IXMLReferenceCollector.java
similarity index 50%
rename from org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/IXMLReferenceTosCollector.java
rename to org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/IXMLReferenceCollector.java
index b4b848830a..4a8494b339 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/IXMLReferenceTosCollector.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/IXMLReferenceCollector.java
@@ -1,5 +1,5 @@
/*******************************************************************************
-* Copyright (c) 2022 Red Hat Inc. and others.
+* Copyright (c) 2023 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
@@ -9,28 +9,27 @@
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
-package org.eclipse.lemminx.extensions.references.utils;
+package org.eclipse.lemminx.extensions.references.search;
-import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
/**
- * API to collect to attribute which matches
+ * API to collect from attribute which matches
* {@link XMLReferenceExpression#matchTo(org.w3c.dom.Node)}
*
* @author Angelo ZERR
*
*/
@FunctionalInterface
-public interface IXMLReferenceTosCollector {
+public interface IXMLReferenceCollector {
/**
- * Collect the given to attribute which matches the given expression.
+ * Collect the from / to search node which matches the given expression.
*
- * @param namespacePrefix namespace prefix.
- * @param toNode the to attribute, text node to collect.
- * @param expression the reference expression which matches the to
- * node.
+ * @param fromSearchNode the from attribute, text node to collect.
+ * @param toSearchNode the to attribute, text node to collect.
+ * @param expression the reference expression which matches the from / to
+ * node.
*/
- void collect(String namespacePrefix, DOMNode toNode, XMLReferenceExpression expression);
+ void collect(SearchNode fromSearchNode, SearchNode toSearchNode, XMLReferenceExpression expression);
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchEngine.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchEngine.java
new file mode 100644
index 0000000000..1f7981abd9
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchEngine.java
@@ -0,0 +1,305 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.search;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+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.dom.DOMText;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery.Direction;
+import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
+import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
+import org.eclipse.lemminx.utils.DOMUtils;
+import org.eclipse.lemminx.utils.URIUtils;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+
+/**
+ * XML references search engine to collect attribute , text which matches XML
+ * references expression {@link XMLReferenceExpression}.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class SearchEngine {
+
+ private static final SearchEngine INSTANCE = new SearchEngine();
+
+ public static SearchEngine getInstance() {
+ return INSTANCE;
+ }
+
+ private static enum MatchNodeStatus {
+ FROM, TO, NONE;
+ }
+
+ private static final String INCLUDE_TAG = "include";
+
+ private static final String HREF_ATTR = "href";
+
+ /**
+ * Perform the XML references search by using the given search query.
+ *
+ * @param query the search query.
+ * @param collector the collector used to collect attribute, text nodes.
+ *
+ * @param cancelChecker the cancel checker.
+ */
+ public final void search(SearchQuery query, IXMLReferenceCollector collector, CancelChecker cancelChecker) {
+ DOMDocument document = query.getNode().getOwnerDocument();
+ Set visitedURIs = query.isSearchInIncludedFiles() ? new HashSet<>() : null;
+ searchInDocument(document, query, collector, visitedURIs, cancelChecker);
+ }
+
+ /**
+ * Perform the search in the given DOM document.
+ *
+ * @param document the DOM document.
+ * @param query the search query.
+ * @param collector the collector used to collect attribute, text nodes.
+ * @param visitedURIs visited URis used to avoid document loading recursion
+ * when document contains some xi:include.
+ * @param cancelChecker the cancel checker.
+ */
+ private void searchInDocument(DOMDocument document, SearchQuery query, IXMLReferenceCollector collector,
+ Set visitedURIs, CancelChecker cancelChecker) {
+
+ // Perform the search by using the DOM document
+ Set externalURIsForDocument = query.isSearchInIncludedFiles() ? new HashSet<>() : null;
+ searchInNode(document, query, collector, externalURIsForDocument, cancelChecker);
+
+ if (externalURIsForDocument != null && !externalURIsForDocument.isEmpty()) {
+ // The search for the document has collected some external document, URIs,
+ // perform the search for each of them.
+ URIResolverExtensionManager resolverExtensionManager = document.getResolverExtensionManager();
+ for (String externalURI : externalURIsForDocument) {
+ String baseURI = document.getDocumentURI();
+ String resourceURI = resolverExtensionManager.resolve(baseURI, null, externalURI);
+ if (URIUtils.isFileResource(resourceURI) && canPerformSearch(resourceURI, visitedURIs)) {
+ // The search was never done for the exterlam document, perform the search by
+ // using this external document.
+ if (visitedURIs != null) {
+ visitedURIs.add(baseURI);
+ }
+ DOMDocument externalDocument = DOMUtils.loadDocument(resourceURI,
+ document.getResolverExtensionManager());
+ if (externalDocument != null) {
+ searchInDocument(externalDocument, query, collector, visitedURIs, cancelChecker);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns true if the search can be performed for the given document URI and
+ * false otherwise.
+ *
+ * @param documentURI the document URI.
+ * @param visitedURIs the visited URIs.
+ *
+ * @return true if the search can be performed for the given document URI and
+ * false otherwise.
+ */
+ private boolean canPerformSearch(String documentURI, Set visitedURIs) {
+ return visitedURIs == null || !visitedURIs.contains(documentURI);
+ }
+
+ /**
+ * Perform the search in the given DOM node.
+ *
+ * @param node the DOM node.
+ * @param query the search query.
+ * @param collector the collector used to collect attribute, text nodes.
+ * @param externalURIs the collected external URIs (xi:include/@href)
+ * @param cancelChecker the cancel checker.
+ */
+ private void searchInNode(DOMNode node, SearchQuery query, IXMLReferenceCollector collector,
+ Set externalURIs, CancelChecker cancelChecker) {
+ // Stop the search if required
+ if (cancelChecker != null) {
+ cancelChecker.checkCanceled();
+ }
+
+ if (node.isElement()) {
+ // Search in the attributes of the element
+ DOMElement element = (DOMElement) node;
+ searchInAttributes(element, query, collector, cancelChecker);
+ if (externalURIs != null) {
+ if (isInclude(element)) {
+ // collect xi:include
+ String includedFile = element.getAttribute(HREF_ATTR);
+ if (includedFile != null) {
+ externalURIs.add(includedFile);
+ }
+ }
+ }
+
+ } else if (node.isText()) {
+ // Search in the text
+ searchInText((DOMText) node, query, collector, cancelChecker);
+ }
+ if (node.hasChildNodes()) {
+ // Search in the children
+ for (DOMNode child : node.getChildren()) {
+ searchInNode(child, query, collector, externalURIs, cancelChecker);
+ }
+ }
+ }
+
+ /**
+ * Perform the search in the given DOM text node.
+ *
+ * @param text the DOM text node.
+ * @param query the search query.
+ * @param collector the collector used to collect attribute, text nodes.
+ * @param cancelChecker the cancel checker.
+ */
+ private void searchInText(DOMText text, SearchQuery query, IXMLReferenceCollector collector,
+ CancelChecker cancelChecker) {
+ if (query.isSearchInText()) {
+ if (cancelChecker != null) {
+ cancelChecker.checkCanceled();
+ }
+ collectNodes(text, query, collector);
+ }
+ }
+
+ /**
+ * Perform the search in the attributes of the given DOM element node.
+ *
+ * @param element the DOM element node.
+ * @param query the search query.
+ * @param collector the collector used to collect attribute, text nodes.
+ * @param cancelChecker the cancel checker.
+ */
+ private void searchInAttributes(DOMElement element, SearchQuery query, IXMLReferenceCollector collector,
+ CancelChecker cancelChecker) {
+ if (query.isSearchInAttribute()) {
+ // Search to reference in attribute nodes
+ if (element.hasAttributes()) {
+ NamedNodeMap toAttributes = element.getAttributes();
+ if (toAttributes != null) {
+ for (int j = 0; j < toAttributes.getLength(); j++) {
+ DOMAttr toAttr = (DOMAttr) toAttributes.item(j);
+ if (cancelChecker != null) {
+ cancelChecker.checkCanceled();
+ }
+ collectNodes(toAttr, query, collector);
+ }
+ }
+ }
+ }
+ }
+
+ private void collectNodes(DOMNode node, SearchQuery query, IXMLReferenceCollector collector) {
+ Direction direction = query.getDirection();
+ // Loop for reference expressions of the query
+ for (XMLReferenceExpression expression : query.getExpressions()) {
+ MatchNodeStatus status = matchNode(node, expression, direction);
+ if (status != MatchNodeStatus.NONE) {
+ // The DOM node matches the XPath (from / to) declared in the reference expression
+ // get the search nodes for this attribute / text node
+ List searchNodes = findSearchNodes(node, expression, status, direction);
+ for (SearchNode searchNode : searchNodes) {
+ // Collect the current search node
+ collect(query, searchNode, expression, collector);
+ }
+ }
+ }
+ }
+
+ private void collect(SearchQuery query, SearchNode searchNode, XMLReferenceExpression expression,
+ IXMLReferenceCollector collector) {
+ SearchNode requestedNode = query.getSearchNode();
+ if (query.isMatchNode()) {
+ if (!requestedNode.matchesValue(searchNode)) {
+ return;
+ }
+ }
+ Direction direction = query.getDirection();
+ switch (direction) {
+ case FROM_2_TO:
+ collector.collect(requestedNode, searchNode, expression);
+ break;
+ case TO_2_FROM:
+ collector.collect(searchNode, requestedNode, expression);
+ break;
+ default:
+ Direction nodeDirection = searchNode.getDirection();
+ if (nodeDirection == Direction.FROM_2_TO) {
+ collector.collect(searchNode, requestedNode, expression);
+ } else {
+ collector.collect(requestedNode, searchNode, expression);
+ }
+ }
+ }
+
+ private List findSearchNodes(DOMNode node, XMLReferenceExpression expression, MatchNodeStatus status,
+ Direction direction) {
+ switch (direction) {
+ case FROM_2_TO:
+ return SearchNodeFactory.findSearchNodes(node, null, false, direction);
+ case TO_2_FROM:
+ return SearchNodeFactory.findSearchNodes(node, expression.getPrefix(), expression.isMultiple(),
+ direction);
+ default:
+ if (status == MatchNodeStatus.FROM) {
+ return SearchNodeFactory.findSearchNodes(node, expression.getPrefix(), expression.isMultiple(),
+ Direction.FROM_2_TO);
+ }
+ return SearchNodeFactory.findSearchNodes(node, null, false, Direction.TO_2_FROM);
+ }
+ }
+
+ private MatchNodeStatus matchNode(DOMNode node, XMLReferenceExpression expression, Direction direction) {
+ switch (direction) {
+ case FROM_2_TO:
+ if (expression.matchTo(node)) {
+ return MatchNodeStatus.TO;
+ }
+ return MatchNodeStatus.NONE;
+ case TO_2_FROM:
+ if (expression.matchFrom(node)) {
+ return MatchNodeStatus.FROM;
+ }
+ return MatchNodeStatus.NONE;
+ default:
+ if (expression.matchFrom(node)) {
+ return MatchNodeStatus.FROM;
+ }
+ if (expression.matchTo(node)) {
+ return MatchNodeStatus.TO;
+ }
+ return MatchNodeStatus.NONE;
+ }
+ }
+
+ /**
+ * Returns true if the given element is an include element (ex : xi:include) and
+ * false otherwise.
+ *
+ * @param element the DOM element.
+ *
+ * @return true if the given element is an include element (ex : xi:include) and
+ * false otherwise.
+ */
+ private static boolean isInclude(Element element) {
+ return element != null && INCLUDE_TAG.equals(element.getLocalName());
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java
new file mode 100644
index 0000000000..ba431eac3a
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNode.java
@@ -0,0 +1,147 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.search;
+
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.dom.DOMRange;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery.Direction;
+
+/**
+ * Search node result.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class SearchNode implements DOMRange {
+
+ private final int start;
+
+ private final int end;
+
+ private final DOMNode node;
+
+ private final String prefix;
+
+ private final Direction direction;
+
+ public SearchNode(int start, int end, DOMNode node, String prefix, Direction direction) {
+ this.start = start;
+ this.end = end;
+ this.node = node;
+ this.prefix = prefix;
+ this.direction = direction;
+ }
+
+ public String getValue(String prefix) {
+ StringBuilder value = new StringBuilder();
+ if (prefix != null) {
+ value.append(prefix);
+ }
+ String text = getOwnerDocument().getText();
+ for (int i = getStart(); i < getEnd(); i++) {
+ value.append(text.charAt(i));
+ }
+ return value.toString();
+ }
+
+ public String getPrefix() {
+ return prefix;
+ }
+
+ public boolean matchesValue(SearchNode toSearchNode) {
+ int fromStart = getStart();
+ int fromEnd = getEnd();
+ String fromText = getOwnerDocument().getText();
+ if (direction == Direction.FROM_2_TO) {
+ int adjust = adjustWithPrefix(this);
+ if (adjust == -1) {
+ return false;
+ }
+ fromStart = fromStart + adjust;
+ }
+ int toStart = toSearchNode.getStart();
+ int toEnd = toSearchNode.getEnd();
+ String toText = toSearchNode.getOwnerDocument().getText();
+ if (direction == Direction.TO_2_FROM) {
+ int adjust = adjustWithPrefix(toSearchNode);
+ if (adjust == -1) {
+ return false;
+ }
+ toStart = toStart + adjust;
+ }
+
+ int length = fromEnd - fromStart;
+
+ if (length != toEnd - toStart) {
+ return false;
+ }
+ for (int i = 0; i < length; i++) {
+ if (fromText.charAt(i + fromStart) != toText.charAt(i + toStart)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int adjustWithPrefix(SearchNode node) {
+ String prefix = node.getPrefix();
+ if (prefix == null) {
+ return 0;
+ }
+ int start = node.getStart();
+ int end = node.getEnd();
+ String text = node.getOwnerDocument().getText();
+ if (prefix.length() > (end - start)) {
+ return -1;
+ }
+ // check the from search node starts with prefix (ex : '#')
+ for (int i = 0; i < prefix.length(); i++) {
+ if (text.charAt(start + i) != prefix.charAt(i)) {
+ return -1;
+ }
+ }
+ return prefix.length();
+ }
+
+ public DOMNode getNode() {
+ return node;
+ }
+
+ @Override
+ public int getStart() {
+ return start;
+ }
+
+ @Override
+ public int getEnd() {
+ return end;
+ }
+
+ @Override
+ public DOMDocument getOwnerDocument() {
+ return node.getOwnerDocument();
+ }
+
+ public Direction getDirection() {
+ return direction;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ String text = node.getOwnerDocument().getText();
+ result.append(text.substring(start, end));
+ result.append(direction == Direction.FROM_2_TO ? " -->" : " <--");
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java
new file mode 100644
index 0000000000..b39946824e
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchNodeFactory.java
@@ -0,0 +1,134 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.search;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.eclipse.lemminx.dom.DOMAttr;
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.dom.DOMRange;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery.Direction;
+import org.eclipse.lemminx.utils.StringUtils;
+
+/**
+ * Search node factory.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class SearchNodeFactory {
+
+ private static final Predicate NAME_PREDICATE = ch -> {
+ return !Character.isWhitespace(ch);
+ };
+
+ public static List findSearchNodes(DOMNode node, String prefix, boolean multiple,
+ Direction direction) {
+ int startNode = getStartNode(node);
+ if (startNode == -1) {
+ return Collections.emptyList();
+ }
+ int endNode = getEndNode(node);
+ if (endNode == -1) {
+ return Collections.emptyList();
+ }
+
+ if (multiple) {
+ String text = node.getOwnerDocument().getText();
+ List searchNodes = new ArrayList<>();
+ int itemStart = -1;
+ for (int j = startNode; j < endNode; j++) {
+ char c = text.charAt(j);
+ if (itemStart == -1) {
+ if (!Character.isWhitespace(c)) {
+ itemStart = j;
+ }
+ } else if (Character.isWhitespace(c)) {
+ searchNodes.add(new SearchNode(itemStart, j, node, prefix, direction));
+ itemStart = -1;
+ }
+ }
+ if (itemStart != -1) {
+ searchNodes.add(new SearchNode(itemStart, endNode, node, prefix, direction));
+ }
+ return searchNodes;
+
+ }
+ return Arrays.asList(new SearchNode(startNode, endNode, node, prefix, direction));
+ }
+
+ public static SearchNode getSearchNodeAt(DOMNode node, int offset, String prefix, boolean multiple,
+ Direction direction) {
+ int startNode = getStartNode(node);
+ if (startNode == -1) {
+ return null;
+ }
+ int endNode = getEndNode(node);
+ if (endNode == -1) {
+ return null;
+ }
+ if (multiple) {
+ String text = node.getOwnerDocument().getText();
+ if (offset != startNode) {
+ int left = StringUtils.findStartWord(text, offset, startNode, NAME_PREDICATE);
+ if (left != -1) {
+ startNode = left;
+ } else {
+ left = StringUtils.findStartWord(text, offset - 1, startNode, NAME_PREDICATE);
+ if (left != -1) {
+ startNode = left;
+ } else {
+ startNode = offset;
+ }
+ }
+ }
+ if (offset != endNode - 1) {
+ int right = StringUtils.findEndWord(text, offset, endNode, NAME_PREDICATE);
+ if (right != -1) {
+ endNode = right;
+ } else {
+ endNode = offset;
+ }
+ }
+ }
+ return new SearchNode(startNode, endNode, node, prefix, direction);
+ }
+
+ private static int getStartNode(DOMNode node) {
+ DOMRange range = getDOMRange(node);
+ if (range == null) {
+ return -1;
+ }
+ return range.getStart() + (node.isAttribute() ? 1 : 0);
+ }
+
+ private static int getEndNode(DOMNode node) {
+ DOMRange range = getDOMRange(node);
+ if (range == null) {
+ return -1;
+ }
+ return range.getEnd() - (node.isAttribute() ? 1 : 0);
+ }
+
+ private static DOMRange getDOMRange(DOMNode node) {
+ if (node.isAttribute()) {
+ DOMAttr attr = (DOMAttr) node;
+ return attr.getNodeAttrValue();
+ }
+ return node;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQuery.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQuery.java
new file mode 100644
index 0000000000..dba2aa4149
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQuery.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.search;
+
+import java.util.List;
+
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
+
+/**
+ * XML references search Query.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class SearchQuery {
+
+ public static enum Direction {
+ FROM_2_TO, TO_2_FROM, BOTH;
+ }
+
+ private final DOMNode node;
+ private final List expressions;
+ private final Direction direction;
+ private boolean searchInAttribute;
+ private boolean searchInText;
+ private boolean matchNode;
+ private boolean searchInIncludedFiles;
+ private SearchNode searchNode;
+
+ public SearchQuery(DOMNode node, int offset, List expressions, Direction direction) {
+ this.node = node;
+ this.expressions = expressions;
+ this.direction = direction;
+ searchInAttribute = false;
+ searchInText = false;
+ boolean hasMultiple = false;
+ String prefix = null;
+ for (XMLReferenceExpression expression : expressions) {
+ if (!searchInAttribute) {
+ searchInAttribute = direction == Direction.FROM_2_TO ? expression.isFromSearchInAttribute()
+ : expression.isToSearchInAttribute();
+ }
+ if (!searchInText) {
+ searchInText = direction == Direction.FROM_2_TO ? expression.isFromSearchInText()
+ : expression.isToSearchInText();
+ }
+ if (!hasMultiple) {
+ hasMultiple = expression.isMultiple();
+ }
+ if (prefix == null) {
+ prefix = expression.getPrefix();
+ }
+ }
+ if (offset != -1) {
+ searchNode = SearchNodeFactory.getSearchNodeAt(node, offset, prefix, hasMultiple, direction);
+ }
+ }
+
+ public DOMNode getNode() {
+ return node;
+ }
+
+ public List getExpressions() {
+ return expressions;
+ }
+
+ public boolean isMatchNode() {
+ return matchNode;
+ }
+
+ public boolean isSearchInIncludedFiles() {
+ return searchInIncludedFiles;
+ }
+
+ public void setSearchInIncludedFiles(boolean searchInIncludedFiles) {
+ this.searchInIncludedFiles = searchInIncludedFiles;
+ }
+
+ public void setMatchNode(boolean matchNode) {
+ this.matchNode = matchNode;
+ }
+
+ public boolean isSearchInAttribute() {
+ return searchInAttribute;
+ }
+
+ public boolean isSearchInText() {
+ return searchInText;
+ }
+
+ public SearchNode getSearchNode() {
+ return searchNode;
+ }
+
+ public Direction getDirection() {
+ return direction;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQueryFactory.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQueryFactory.java
new file mode 100644
index 0000000000..c66dcde575
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/search/SearchQueryFactory.java
@@ -0,0 +1,179 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references.search;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.dom.DOMText;
+import org.eclipse.lemminx.extensions.references.search.SearchQuery.Direction;
+import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
+import org.eclipse.lemminx.extensions.references.settings.XMLReferences;
+import org.eclipse.lemminx.extensions.references.settings.XMLReferencesSettings;
+
+/**
+ * XML references search Query factory.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public class SearchQueryFactory {
+
+ public static SearchQuery createQuery(DOMNode node, int offset, XMLReferencesSettings settings) {
+ DOMNode adjustedNode = findAttrOrTextNode(node, offset);
+ if (adjustedNode == null) {
+ return null;
+ }
+ SearchQuery query = internalCreateQuery(adjustedNode, offset, settings, Direction.FROM_2_TO);
+ if (query != null) {
+ return query;
+ }
+ return internalCreateQuery(adjustedNode, offset, settings, Direction.TO_2_FROM);
+ }
+
+ public static SearchQuery createFromQuery(DOMNode node, int offset, XMLReferencesSettings settings) {
+ return createQuery(node, offset, settings, Direction.FROM_2_TO);
+ }
+
+ public static SearchQuery createToQuery(DOMNode node, int offset, XMLReferencesSettings settings) {
+ return createQuery(node, offset, settings, Direction.TO_2_FROM);
+ }
+
+ public static SearchQuery createQuery(DOMNode node, XMLReferencesSettings settings,
+ Direction direction) {
+ return createQuery(node, -1, settings, direction);
+ }
+
+ public static SearchQuery createQuery(DOMNode node, int offset, XMLReferencesSettings settings,
+ Direction direction) {
+ DOMNode adjustedNode = findAttrOrTextNode(node, offset);
+ if (adjustedNode == null) {
+ return null;
+ }
+ return internalCreateQuery(adjustedNode, offset, settings, direction);
+ }
+
+ private static SearchQuery internalCreateQuery(DOMNode adjustedNode, int offset, XMLReferencesSettings settings,
+ Direction direction) {
+ List expressions = findExpressions(adjustedNode, settings,
+ direction);
+ if (expressions == null) {
+ return null;
+ }
+ return new SearchQuery(adjustedNode, offset, expressions, direction);
+ }
+
+ private static DOMNode findAttrOrTextNode(DOMNode node, int offset) {
+ if (node == null || node.isText() || node.isAttribute() || node.isOwnerDocument()) {
+ return node;
+ }
+ if (node.isElement()) {
+ DOMText text = ((DOMElement) node).findTextAt(offset);
+ if (text != null) {
+ return text;
+ }
+ DOMAttr attr = node.findAttrAt(offset);
+ if (attr != null) {
+ return attr;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns XML references expressions which match the given DOM
+ * document
and null otherwise.
+ *
+ * @param document the DOM document.
+ * @param xmlReferencesSettings the XML references settings which hosts all
+ * references expressions.
+ *
+ * @return XML references expressions which match the given DOM
+ * document
and null otherwise.
+ */
+ private static List findExpressions(DOMNode node,
+ XMLReferencesSettings xmlReferencesSettings, Direction direction) {
+ List allReferences = xmlReferencesSettings != null ? xmlReferencesSettings.getReferences()
+ : null;
+ if (allReferences == null) {
+ return null;
+ }
+ DOMDocument document = node.getOwnerDocument();
+ String uri = document.getDocumentURI();
+ List matchedExpressions = null;
+ for (XMLReferences references : allReferences) {
+ // Given this XML references sample
+
+ /**
+ *
+ * "xml.references": [
+ * // references for docbook.xml files
+ * {
+ * "pattern": "*.xml",
+ * "expressions": [
+ * {
+ * "from": "xref/@linkend",
+ * "to": "@id"
+ * }
+ * ]
+ * }
+ *]
+ *
+ *
+ *
+ */
+ if (references.matches(uri)) {
+ // here uri matches the "*.xml" pattern
+ List expressions = references.getExpressions();
+ if (expressions != null) {
+ if (node.isOwnerDocument()) {
+ if (matchedExpressions == null) {
+ matchedExpressions = new ArrayList<>();
+ }
+ matchedExpressions.addAll(expressions);
+ } else {
+ for (XMLReferenceExpression expression : expressions) {
+ // Given this XML reference expression sample
+ /**
+ *
+ * "expressions": [
+ * {
+ * "from": "xref/@linkend",
+ * "to": "@id"
+ * }
+ * ]
+ *
+ *
+ *
+ */
+ boolean add = direction == Direction.FROM_2_TO ? expression.matchFrom(node)
+ : expression.matchTo(node);
+ if (add) {
+ // here the attribute matches xref/@linkend
+ if (matchedExpressions == null) {
+ matchedExpressions = new ArrayList<>();
+ }
+ matchedExpressions.add(expression);
+ }
+ }
+ }
+ }
+ }
+ }
+ return matchedExpressions;
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/settings/XMLReferenceExpression.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/settings/XMLReferenceExpression.java
index 3f5d8e9055..acd7030e18 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/settings/XMLReferenceExpression.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/settings/XMLReferenceExpression.java
@@ -41,6 +41,8 @@ public class XMLReferenceExpression {
private String to;
+ private boolean multiple;
+
public String getFrom() {
return from;
}
@@ -65,6 +67,14 @@ public void setPrefix(String prefix) {
this.prefix = prefix;
}
+ public void setMultiple(boolean multiple) {
+ this.multiple = multiple;
+ }
+
+ public boolean isMultiple() {
+ return multiple;
+ }
+
/**
* Returns true if the given DOM Node match the XPath expression of the 'from'
* XPath matcher and false otherwise.
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesSearchContext.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesSearchContext.java
deleted file mode 100644
index 6be033f84b..0000000000
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesSearchContext.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*******************************************************************************
-* Copyright (c) 2023 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.references.utils;
-
-import java.util.List;
-
-import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
-
-/**
- * The XML reference searcher.
- *
- * @author Angelo ZERR
- *
- */
-public class XMLReferencesSearchContext {
-
- private final List expressions;
-
- private final boolean searchInAttribute;
-
- private final boolean searchInText;
-
- public XMLReferencesSearchContext(List expressions, boolean searchForFromNode) {
- this.expressions = expressions;
- if (searchForFromNode) {
- searchInAttribute = expressions.stream()
- .anyMatch(expr -> expr.isFromSearchInAttribute());
- searchInText = expressions.stream()
- .anyMatch(expr -> expr.isFromSearchInText());
- } else {
- searchInAttribute = expressions.stream()
- .anyMatch(expr -> expr.isToSearchInAttribute());
- searchInText = expressions.stream()
- .anyMatch(expr -> expr.isToSearchInText());
- }
- }
-
- /**
- * Returns true if the search of nodes must be done in attribute nodes and false
- * otherwise.
- *
- * @return true if the search of nodes must be done in attribute nodes and false
- * otherwise.
- */
- public boolean isSearchInAttribute() {
- return searchInAttribute;
- }
-
- /**
- * Returns true if the search of nodes must be done in text nodes and false
- * otherwise.
- *
- * @return true if the search of nodes must be done in text nodes and false
- * otherwise.
- */
- public boolean isSearchInText() {
- return searchInText;
- }
-
- /**
- * Returns the list of reference expressions.
- *
- * @return the list of reference expressions.
- */
- public List getExpressions() {
- return expressions;
- }
-}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesUtils.java
deleted file mode 100644
index e3f9a961ff..0000000000
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/references/utils/XMLReferencesUtils.java
+++ /dev/null
@@ -1,372 +0,0 @@
-/*******************************************************************************
-* 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.references.utils;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-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.dom.DOMRange;
-import org.eclipse.lemminx.dom.DOMText;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferences;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferencesSettings;
-import org.eclipse.lemminx.uriresolver.URIResolverExtensionManager;
-import org.eclipse.lemminx.utils.DOMUtils;
-import org.eclipse.lemminx.utils.StringUtils;
-import org.eclipse.lemminx.utils.URIUtils;
-import org.w3c.dom.Element;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import com.google.common.base.Objects;
-
-/**
- * XML references utilities.
- *
- * @author Angelo ZERR
- *
- */
-public class XMLReferencesUtils {
-
- private static final String INCLUDE_TAG = "include";
-
- private static final String HREF_ATTR = "href";
-
- /**
- * Collect 'to' attributes which matches 'to' declared in
- * {@link XMLReferencesSearchContext} which contains a list of
- * {@link XMLReferenceExpression}.
- *
- * @param fromNode the from attribute, text node.
- * @param searchContext the references search context.
- * @param matchNode true if the value of the from and to nodes must
- * be checked, and false otherwise.
- * @param searchInIncludedFile true if search must be done in included XML
- * file (ex : xi:include) and false otherwise.
- * @param collector collector to collect to attribute, text nodes.
- */
- public static void searchToNodes(DOMNode fromNode, XMLReferencesSearchContext searchContext,
- boolean matchNode, boolean searchInIncludedFile, IXMLReferenceTosCollector collector) {
-
- DOMDocument document = fromNode.getOwnerDocument();
- DOMElement documentElement = document != null ? document.getDocumentElement() : null;
- if (documentElement == null) {
- return;
- }
- String fromValue = getNodeValue(fromNode);
- if (matchNode && StringUtils.isEmpty(fromValue)) {
- return;
- }
- String namespacePrefix = null;
- int index = fromValue.indexOf(':');
- if (index != -1) {
- // ex : jakartaee:applicationType
- namespacePrefix = fromValue.substring(0, index);
- }
-
- String fromName = null;
- if (matchNode) {
- fromName = getFromName(fromValue, namespacePrefix);
- }
-
- // Collect attribute, text nodes to for document element
- collectToNode(fromNode, searchContext, matchNode, collector, documentElement,
- namespacePrefix);
-
- // Collect attribute, text nodes to for children of document element
- searchToNodes(fromNode, searchContext, matchNode, collector, documentElement,
- namespacePrefix,
- fromName, new HashSet<>(), searchInIncludedFile);
- }
-
- private static void searchToNodes(DOMNode fromNode, XMLReferencesSearchContext referenceSearcher,
- boolean matchNode,
- IXMLReferenceTosCollector collector, DOMElement documentElement, String toNamespacePrefix,
- String fromName, Set visitedURIs, boolean searchInExternalDocument) {
- if (visitedURIs != null) {
- DOMDocument document = documentElement.getOwnerDocument();
- String documentURI = document.getDocumentURI();
- if (visitedURIs.contains(documentURI)) {
- return;
- }
- visitedURIs.add(documentURI);
- }
- Set externalURIS = null;
- Node parent = documentElement;
- NodeList children = parent.getChildNodes();
- for (int i = 0; i < children.getLength(); i++) {
- Node node = children.item(i);
- if (node.getNodeType() == Node.ELEMENT_NODE) {
- DOMElement toElement = (DOMElement) node;
- collectToNode(fromNode, referenceSearcher, matchNode, collector, toElement,
- toNamespacePrefix);
-
- if (isInclude(toElement)) {
- // collect xi:include
- String schemaLocation = toElement.getAttribute(HREF_ATTR);
- if (schemaLocation != null) {
- if (externalURIS == null) {
- externalURIS = new HashSet<>();
- }
- externalURIS.add(schemaLocation);
- }
- } else {
- searchToNodes(fromNode, referenceSearcher, matchNode, collector, toElement,
- toNamespacePrefix, fromName, null, false);
- }
- }
- }
- if (searchInExternalDocument && externalURIS != null) {
- // Search in include location
- DOMDocument document = documentElement.getOwnerDocument();
- String documentURI = document.getDocumentURI();
- URIResolverExtensionManager resolverExtensionManager = document.getResolverExtensionManager();
- for (String externalURI : externalURIS) {
- String resourceURI = resolverExtensionManager.resolve(documentURI, null, externalURI);
- if (URIUtils.isFileResource(resourceURI)) {
- DOMDocument externalDocument = DOMUtils.loadDocument(resourceURI,
- document.getResolverExtensionManager());
- if (externalDocument != null) {
- searchToNodes(fromNode, referenceSearcher, matchNode, collector,
- externalDocument.getDocumentElement(), toNamespacePrefix, fromName, visitedURIs,
- searchInExternalDocument);
- }
- }
- }
- }
- }
-
- private static void collectToNode(DOMNode fromNode, XMLReferencesSearchContext searchContext,
- boolean matchNode, IXMLReferenceTosCollector collector, DOMElement toElement,
- String toNamespacePrefix) {
- if (searchContext.isSearchInAttribute()) {
- // Search to reference in attribute nodes
- if (toElement.hasAttributes()) {
- NamedNodeMap toAttributes = toElement.getAttributes();
- if (toAttributes != null) {
- for (int j = 0; j < toAttributes.getLength(); j++) {
- DOMAttr toAttr = (DOMAttr) toAttributes.item(j);
- XMLReferenceExpression expression = findExpressionWhichMatchesTo(fromNode, toAttr,
- searchContext,
- matchNode);
- if (expression != null) {
- collector.collect(toNamespacePrefix, toAttr, expression);
- }
- }
- }
- }
- }
- if (searchContext.isSearchInText()) {
- // Search to reference in text node.
- DOMNode firstChild = toElement.getFirstChild();
- if (firstChild != null && firstChild.isText()) {
- XMLReferenceExpression expression = findExpressionWhichMatchesTo(fromNode, firstChild,
- searchContext,
- matchNode);
- if (expression != null) {
- collector.collect(toNamespacePrefix, firstChild, expression);
- }
- }
- }
- }
-
- /**
- * Returns the reference expression where the given attributes
- * fromNode
and
- * toNode
matches an expression from the given
- * referenceSearcher.
- *
- * @param fromNode the from attribute, text node.
- * @param toNode the to attribute, text node.
- * @param searchContext reference searcher which matches the from
- * node.
- * @param matchNode true if the test of the value of from and to
- * attribute, text must be checked and false
- * otherwise.
- * @return the reference expression where the given attributes
- * fromNode
and
- * toNode
matches an expression from the given
- * referenceSearcher.
- */
- private static XMLReferenceExpression findExpressionWhichMatchesTo(DOMNode fromNode, DOMNode toNode,
- XMLReferencesSearchContext searchContext, boolean matchNode) {
- for (XMLReferenceExpression expression : searchContext.getExpressions()) {
- if (expression.matchTo(toNode)) {
- // The current expression can be applied for the to attribute.
- if (!matchNode) {
- // No need to match the attribute value
- return expression;
- }
- // The expression is returned only if the from attribute value, text content is
- // equals to to
- // attribute value, text content.
- String fromValue = getNodeValue(fromNode);
- if (fromValue != null) {
- String prefix = expression.getPrefix();
- if (prefix != null) {
- if (!fromValue.startsWith(prefix)) {
- continue;
- }
- fromValue = fromValue.substring(prefix.length(), fromValue.length());
- }
- if (fromValue.equals(getNodeValue(toNode))) {
- return expression;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Returns the value of the given DOM node and null otherwise.
- *
- * @param node the DOM node.
- *
- * @return the value of the given DOM node and null otherwise.
- */
- public static String getNodeValue(DOMNode node) {
- if (node.isAttribute()) {
- return ((DOMAttr) node).getValue();
- }
- if (node.isText()) {
- return ((DOMText) node).getData();
- }
- return null;
- }
-
- /**
- * Returns the range of the given DOM node.
- *
- * @param node the DOM node.
- *
- * @return the range of the given DOM node.
- */
- public static DOMRange getNodeRange(DOMNode node) {
- if (node.isAttribute()) {
- return ((DOMAttr) node).getNodeAttrValue();
- }
- return node;
- }
-
- /**
- * Returns true if the given element is an include element (ex : xi:include) and
- * false otherwise.
- *
- * @param element the DOM element.
- *
- * @return true if the given element is an include element (ex : xi:include) and
- * false otherwise.
- */
- private static boolean isInclude(Element element) {
- return element != null && INCLUDE_TAG.equals(element.getLocalName());
- }
-
- private static String getFromName(String fromValue, String toNamespacePrefix) {
- int index = fromValue.indexOf(":");
- if (index != -1) {
- String prefix = fromValue.substring(0, index);
- if (!Objects.equal(prefix, toNamespacePrefix)) {
- return null;
- }
- return fromValue.substring(index + 1, fromValue.length());
- }
- return fromValue;
- }
-
- /**
- * Returns all XML reference expressions where the given DOM node
- * matches the
- * from expression and null otherwise.
- *
- * @param node the DOM attribute.
- *
- * @param xmlReferencesSettings the XML references settings.
- * @return all XML reference expressions where the given attribute matches the
- * from
- * expression and an empty list otherwise.
- */
- public static XMLReferencesSearchContext findExpressionsWhichMatchFrom(DOMNode node,
- XMLReferencesSettings xmlReferencesSettings) {
- List allReferences = xmlReferencesSettings != null ? xmlReferencesSettings.getReferences()
- : null;
- if (allReferences == null) {
- return null;
- }
- if (node == null || (!node.isAttribute() && !node.isText())) {
- return null;
- }
- String uri = node.getOwnerDocument().getDocumentURI();
- List matchedExpressions = null;
- for (XMLReferences references : allReferences) {
- // Given this XML references sample
-
- /**
- *
- * "xml.references": [
- * // references for docbook.xml files
- * {
- * "pattern": "*.xml",
- * "expressions": [
- * {
- * "from": "xref/@linkend",
- * "to": "@id"
- * }
- * ]
- * }
- *]
- *
- *
- *
- */
- if (references.matches(uri)) {
- // here uri matches the "*.xml" pattern
- List expressions = references.getExpressions();
- if (expressions != null) {
- for (XMLReferenceExpression expression : expressions) {
- // Given this XML reference expression sample
- /**
- *
- * "expressions": [
- * {
- * "from": "xref/@linkend",
- * "to": "@id"
- * }
- * ]
- *
- *
- *
- */
- if (expression.matchFrom(node)) {
- // here the attribute matches xref/@linkend
- if (matchedExpressions == null) {
- matchedExpressions = new ArrayList<>();
- }
- matchedExpressions.add(expression);
- }
- }
- }
- }
- }
- if (matchedExpressions == null) {
- return null;
- }
- return new XMLReferencesSearchContext(matchedExpressions, true);
- }
-}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGRenameParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGRenameParticipant.java
index 7c6a016dd3..f8e9cb4619 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGRenameParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/relaxng/grammar/rng/RNGRenameParticipant.java
@@ -21,13 +21,18 @@
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.extensions.relaxng.utils.RelaxNGUtils;
+import org.eclipse.lemminx.services.extensions.IPositionRequest;
+import org.eclipse.lemminx.services.extensions.IPrepareRenameRequest;
import org.eclipse.lemminx.services.extensions.IRenameParticipant;
import org.eclipse.lemminx.services.extensions.IRenameRequest;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Location;
+import org.eclipse.lsp4j.PrepareRenameResult;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
/**
* RNG rename
@@ -35,53 +40,50 @@
*/
public class RNGRenameParticipant implements IRenameParticipant {
- @Override
- public void doRename(IRenameRequest request, List locations) {
- DOMDocument xmlDocument = request.getXMLDocument();
+ // --------------- Prepare rename
- if (!DOMUtils.isRelaxNGXMLSyntax(xmlDocument)) {
- return;
+ @Override
+ public Either prepareRename(IPrepareRenameRequest request,
+ CancelChecker cancelChecker) {
+ // RNG rename can be applied for:
+ // - define/@name
+ DOMAttr attr = findAttrToRename(request);
+ if (attr != null) {
+ Range range = XMLPositionUtility.selectAttributeValue(attr, true);
+ String placeholder = attr.getValue();
+ return Either.forRight(new PrepareRenameResult(range, placeholder));
}
- locations.addAll(getRenameTextEdits(request));
-
+ return null;
}
- private List getRenameTextEdits(IRenameRequest request) {
- DOMDocument document = request.getXMLDocument();
- DOMNode node = request.getNode();
-
- if (!node.isAttribute()) {
- return Collections.emptyList();
- }
+ // --------------- Rename
- DOMAttr attr = (DOMAttr) node;
- DOMElement ownerElement = attr.getOwnerElement();
+ @Override
+ public void doRename(IRenameRequest request, List locations, CancelChecker cancelChecker) {
+ locations.addAll(getRenameTextEdits(request, cancelChecker));
+ }
- if (ownerElement == null) {
+ private List getRenameTextEdits(IRenameRequest request, CancelChecker cancelChecker) {
+ // RNG rename can be applied for:
+ // - define/@name
+ DOMAttr attr = findAttrToRename(request);
+ if (attr == null) {
return Collections.emptyList();
}
-
+ DOMElement ownerElement = attr.getOwnerElement();
+ DOMDocument document = request.getXMLDocument();
String newText = request.getNewText();
-
- if (RelaxNGUtils.isDefine(ownerElement)) {
- // locations = getReferenceLocations(ownerElement);
- return renameAttributeValueTextEdits(document, attr, newText, locations);
- }
- }
-
- return Collections.emptyList();
+ List locations = getReferenceLocations(ownerElement, cancelChecker);
+ return renameAttributeValueTextEdits(document, attr, newText, locations);
}
- private List getReferenceLocations(DOMNode node) {
+ private List getReferenceLocations(DOMNode node, CancelChecker cancelChecker) {
List locations = new ArrayList<>();
RelaxNGUtils.searchRNGOriginAttributes(node,
(origin, target) -> locations.add(XMLPositionUtility.createLocation(origin.getNodeAttrValue())),
- null);
+ cancelChecker);
return locations;
}
@@ -126,4 +128,35 @@ private void decreaseEndRange(Range range, int decrease) {
range.getEnd().setCharacter(endCharacter - decrease);
}
+ /**
+ * Returns the xsd:complexType/@name or xs:simpleType/@name to rename or null
+ * otherwise.
+ *
+ * @param request the position request.
+ *
+ * @return the xsd:complexType/@name or xs:simpleType/@name to rename or null
+ * otherwise.
+ */
+ private static DOMAttr findAttrToRename(IPositionRequest request) {
+ DOMDocument xmlDocument = request.getXMLDocument();
+ if (!DOMUtils.isRelaxNGXMLSyntax(xmlDocument)) {
+ return null;
+ }
+ DOMNode node = request.getNode();
+ if (node == null || !node.isAttribute()) {
+ return null;
+ }
+
+ DOMAttr attr = (DOMAttr) node;
+ DOMElement ownerElement = attr.getOwnerElement();
+ if (ownerElement == null) {
+ return null;
+ }
+ if (RelaxNGUtils.isDefine(ownerElement)) {
+ if (attr.getName().equals("name")) {
+ return attr;
+ }
+ }
+ return null;
+ }
}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDRenameParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDRenameParticipant.java
index c66414ce65..ccb286e567 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDRenameParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/participants/XSDRenameParticipant.java
@@ -22,14 +22,19 @@
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.DOMRange;
import org.eclipse.lemminx.extensions.xsd.utils.XSDUtils;
+import org.eclipse.lemminx.services.extensions.IPositionRequest;
+import org.eclipse.lemminx.services.extensions.IPrepareRenameRequest;
import org.eclipse.lemminx.services.extensions.IRenameParticipant;
import org.eclipse.lemminx.services.extensions.IRenameRequest;
import org.eclipse.lemminx.utils.DOMUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PrepareRenameResult;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
/**
* XSD rename
@@ -37,57 +42,58 @@
*/
public class XSDRenameParticipant implements IRenameParticipant {
- @Override
- public void doRename(IRenameRequest request, List locations) {
- DOMDocument xmlDocument = request.getXMLDocument();
+ // --------------- Prepare rename
- if (!DOMUtils.isXSD(xmlDocument)) {
- return;
+ @Override
+ public Either prepareRename(IPrepareRenameRequest request,
+ CancelChecker cancelChecker) {
+ // XSD rename can be applied for:
+ // - xsd:complexType/@name
+ // - xs:simpleType/@name
+ DOMAttr attr = findAttrToRename(request);
+ if (attr != null) {
+ Range range = XMLPositionUtility.selectAttributeValue(attr, true);
+ String placeholder = attr.getValue();
+ return Either.forRight(new PrepareRenameResult(range, placeholder));
}
- locations.addAll(getRenameTextEdits(request));
-
+ return null;
}
- private List getRenameTextEdits(IRenameRequest request) {
- DOMDocument document = request.getXMLDocument();
- DOMNode node = request.getNode();
+ // --------------- Rename
- if (!node.isAttribute()) {
- return Collections.emptyList();
- }
-
- DOMAttr attr = (DOMAttr) node;
- DOMElement ownerElement = attr.getOwnerElement();
+ @Override
+ public void doRename(IRenameRequest request, List locations, CancelChecker cancelChecker) {
+ locations.addAll(getRenameTextEdits(request, cancelChecker));
+ }
- if (ownerElement == null) {
+ private List getRenameTextEdits(IRenameRequest request, CancelChecker cancelChecker) {
+ // XSD rename can be applied for:
+ // - xsd:complexType/@name
+ // - xs:simpleType/@name
+ DOMAttr attr = findAttrToRename(request);
+ if (attr == null) {
return Collections.emptyList();
}
-
+ DOMElement ownerElement = attr.getOwnerElement();
+ DOMDocument document = request.getXMLDocument();
String newText = request.getNewText();
-
- if (XSDUtils.isXSComplexType(ownerElement) || XSDUtils.isXSSimpleType(ownerElement)) {
-
- if (attr.getName().equals("name")) {
- List locations = getReferenceLocations(ownerElement);
- return renameAttributeValueTextEdits(document, attr, newText, locations);
- }
- }
-
- return Collections.emptyList();
+ List locations = getReferenceLocations(ownerElement, cancelChecker);
+ return renameAttributeValueTextEdits(document, attr, newText, locations);
}
- private List getReferenceLocations(DOMNode node) {
+ private List getReferenceLocations(DOMNode node, CancelChecker cancelChecker) {
List locations = new ArrayList<>();
XSDUtils.searchXSOriginAttributes(node,
- (origin, target) -> locations.add(XMLPositionUtility.createLocation(origin.getNodeAttrValue())),
- null);
+ (origin, target) -> locations.add(XMLPositionUtility.createLocation(origin.getNodeAttrValue())),
+ cancelChecker);
return locations;
}
- private List renameAttributeValueTextEdits(DOMDocument document, DOMAttr attribute, String newText, List locations) {
+ private List renameAttributeValueTextEdits(DOMDocument document, DOMAttr attribute, String newText,
+ List locations) {
DOMRange attrValue = attribute.getNodeAttrValue();
List textEdits = new ArrayList<>();
@@ -100,7 +106,7 @@ private List renameAttributeValueTextEdits(DOMDocument document, DOMAt
textEdits.add(new TextEdit(range, newText));
- for (Location location: locations) {
+ for (Location location : locations) {
Range textEditRange = location.getRange();
reduceRangeFromBothEnds(textEditRange, 1);
@@ -110,9 +116,9 @@ private List renameAttributeValueTextEdits(DOMDocument document, DOMAt
} catch (BadLocationException e1) {
return Collections.emptyList();
}
-
+
int colonIndex = oldAttrValue.indexOf(":");
-
+
if (colonIndex > 0) {
increaseStartRange(textEditRange, colonIndex + 1);
}
@@ -143,5 +149,36 @@ private String getAttrTextValueFromPosition(DOMDocument document, Position posit
int offset = document.offsetAt(position);
return document.findAttrAt(offset).getValue();
}
-
-}
\ No newline at end of file
+
+ /**
+ * Returns the xsd:complexType/@name or xs:simpleType/@name to rename or null
+ * otherwise.
+ *
+ * @param request the position request.
+ *
+ * @return the xsd:complexType/@name or xs:simpleType/@name to rename or null
+ * otherwise.
+ */
+ private static DOMAttr findAttrToRename(IPositionRequest request) {
+ DOMDocument xmlDocument = request.getXMLDocument();
+ if (!DOMUtils.isXSD(xmlDocument)) {
+ return null;
+ }
+ DOMNode node = request.getNode();
+ if (node == null || !node.isAttribute()) {
+ return null;
+ }
+
+ DOMAttr attr = (DOMAttr) node;
+ DOMElement ownerElement = attr.getOwnerElement();
+ if (ownerElement == null) {
+ return null;
+ }
+ if (XSDUtils.isXSComplexType(ownerElement) || XSDUtils.isXSSimpleType(ownerElement)) {
+ if (attr.getName().equals("name")) {
+ return attr;
+ }
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/LinkedEditingRangesRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/LinkedEditingRangesRequest.java
new file mode 100644
index 0000000000..319f61430a
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/LinkedEditingRangesRequest.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.commons.BadLocationException;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.services.extensions.ILinkedEditingRangesRequest;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
+import org.eclipse.lsp4j.Position;
+
+/**
+ * Linked editing ranges request implementation.
+ *
+ */
+class LinkedEditingRangesRequest extends AbstractPositionRequest implements ILinkedEditingRangesRequest {
+
+ public LinkedEditingRangesRequest(DOMDocument xmlDocument, Position position,
+ XMLExtensionsRegistry extensionsRegistry) throws BadLocationException {
+ super(xmlDocument, position, extensionsRegistry);
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/PrepareRenameRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/PrepareRenameRequest.java
new file mode 100644
index 0000000000..1b62bdbb94
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/PrepareRenameRequest.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.commons.BadLocationException;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.services.extensions.IPrepareRenameRequest;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
+import org.eclipse.lsp4j.Position;
+
+class PrepareRenameRequest extends AbstractPositionRequest implements IPrepareRenameRequest {
+
+ public PrepareRenameRequest(DOMDocument document, Position position, XMLExtensionsRegistry extensionsRegistry) throws BadLocationException {
+ super(document, position, extensionsRegistry);
+ }
+}
\ No newline at end of file
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 93042080c8..1e62fb6391 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
@@ -52,6 +52,7 @@
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PrepareRenameResult;
import org.eclipse.lsp4j.PublishDiagnosticsParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.ReferenceContext;
@@ -60,6 +61,7 @@
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
/**
* XML Language service.
@@ -88,6 +90,7 @@ public void checkCanceled() {
private final XMLReference reference;
private final XMLCodeLens codelens;
private final XMLCodeActions codeActions;
+ private final XMLPrepareRename prepareRename;
private final XMLRename rename;
private final XMLSelectionRanges selectionRanges;
private final XMLLinkedEditing linkedEditing;
@@ -108,9 +111,10 @@ public XMLLanguageService() {
this.reference = new XMLReference(this);
this.codelens = new XMLCodeLens(this);
this.codeActions = new XMLCodeActions(this);
+ this.prepareRename = new XMLPrepareRename(this);
this.rename = new XMLRename(this);
this.selectionRanges = new XMLSelectionRanges();
- this.linkedEditing = new XMLLinkedEditing();
+ this.linkedEditing = new XMLLinkedEditing(this);
}
@Override
@@ -232,8 +236,14 @@ public List getSelectionRanges(DOMDocument xmlDocument, List prepareRename(DOMDocument xmlDocument, Position position,
+ CancelChecker cancelChecker) {
+ return prepareRename.prepareRename(xmlDocument, position, cancelChecker);
+ }
+
+ public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String newText,
+ CancelChecker cancelChecker) {
+ return rename.doRename(xmlDocument, position, newText, cancelChecker);
}
public List findDocumentLinks(DOMDocument document) {
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLinkedEditing.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLinkedEditing.java
index 32a8f05d3f..d3105bd667 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLinkedEditing.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLLinkedEditing.java
@@ -11,6 +11,7 @@
*******************************************************************************/
package org.eclipse.lemminx.services;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
@@ -20,6 +21,9 @@
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMElement;
import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.services.extensions.ILinkedEditingRangesParticipant;
+import org.eclipse.lemminx.services.extensions.ILinkedEditingRangesRequest;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.LinkedEditingRanges;
import org.eclipse.lsp4j.Position;
@@ -33,7 +37,13 @@
class XMLLinkedEditing {
private static Logger LOGGER = Logger.getLogger(XMLLinkedEditing.class.getName());
-
+
+ private final XMLExtensionsRegistry extensionsRegistry;
+
+ public XMLLinkedEditing(XMLExtensionsRegistry extensionsRegistry) {
+ this.extensionsRegistry = extensionsRegistry;
+ }
+
/**
* Returns the linked editing ranges for the given xmlDocument
at
* the given position
and null otherwise.
@@ -48,25 +58,40 @@ public LinkedEditingRanges findLinkedEditingRanges(DOMDocument document, Positio
CancelChecker cancelChecker) {
try {
cancelChecker.checkCanceled();
-
- int offset = document.offsetAt(position);
- DOMNode node = document.findNodeAt(offset);
- if (node == null || !node.isElement()) {
+
+ ILinkedEditingRangesRequest request = new LinkedEditingRangesRequest(document, position,
+ extensionsRegistry);
+ DOMNode node = request.getNode();
+ if (node == null) {
return null;
}
- DOMElement element = (DOMElement) node;
- if (element.isOrphanEndTag() || !element.hasEndTag()) {
- return null;
+
+ final List ranges = new ArrayList<>();
+ for (ILinkedEditingRangesParticipant participant : extensionsRegistry
+ .getLinkedEditingRangesParticipants()) {
+ try {
+ participant.findLinkedEditingRanges(request, ranges, cancelChecker);
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE,
+ "Error while processing linked editing ranges for the participant '"
+ + participant.getClass().getName() + "'.",
+ e);
+ }
}
- if (element.isInStartTag(offset) || element.isInEndTag(offset, true)) {
- List ranges = Arrays.asList(XMLPositionUtility.selectStartTagName(element),
- XMLPositionUtility.selectEndTagName(element));
-
- cancelChecker.checkCanceled();
-
- return new LinkedEditingRanges(ranges);
+ if (node.isElement()) {
+ DOMElement element = (DOMElement) node;
+ if (!(element.isOrphanEndTag() || !element.hasEndTag())) {
+ int offset = request.getOffset();
+ if (element.isInStartTag(offset) || element.isInEndTag(offset, true)) {
+ ranges.addAll(Arrays.asList(XMLPositionUtility.selectStartTagName(element),
+ XMLPositionUtility.selectEndTagName(element)));
+
+ }
+ }
}
+ cancelChecker.checkCanceled();
+ return !ranges.isEmpty() ? new LinkedEditingRanges(ranges) : null;
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "In XMLLinkedEditing, position error", e);
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLPrepareRename.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLPrepareRename.java
new file mode 100644
index 0000000000..ab2d05a337
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLPrepareRename.java
@@ -0,0 +1,91 @@
+/*******************************************************************************
+* Copyright (c) 2023 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 java.util.concurrent.CancellationException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.dom.DOMDocument;
+import org.eclipse.lemminx.dom.DOMElement;
+import org.eclipse.lemminx.dom.DOMNode;
+import org.eclipse.lemminx.services.extensions.IPrepareRenameRequest;
+import org.eclipse.lemminx.services.extensions.IRenameParticipant;
+import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
+import org.eclipse.lemminx.utils.XMLPositionUtility;
+import org.eclipse.lsp4j.Position;
+import org.eclipse.lsp4j.PrepareRenameResult;
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
+
+/**
+ * XML prepare rename support.
+ *
+ */
+public class XMLPrepareRename {
+
+ private static final Logger LOGGER = Logger.getLogger(XMLPrepareRename.class.getName());
+
+ private final XMLExtensionsRegistry extensionsRegistry;
+
+ public XMLPrepareRename(XMLExtensionsRegistry extensionsRegistry) {
+ this.extensionsRegistry = extensionsRegistry;
+ }
+
+ public Either prepareRename(DOMDocument xmlDocument, Position position,
+ CancelChecker cancelChecker) {
+
+ IPrepareRenameRequest request = null;
+
+ try {
+ request = new PrepareRenameRequest(xmlDocument, position, extensionsRegistry);
+ } catch (BadLocationException e) {
+ LOGGER.log(Level.SEVERE, "Failed creating prperare rename request", e);
+ return null;
+ }
+
+ for (IRenameParticipant participant : extensionsRegistry.getRenameParticipants()) {
+ try {
+ Either result = participant.prepareRename(request, cancelChecker);
+ if (result != null) {
+ return result;
+ }
+ } catch (CancellationException e) {
+ throw e;
+ } catch (Exception e) {
+ LOGGER.log(Level.SEVERE,
+ "Error while processing prepare rename for the participant '" + participant.getClass().getName()
+ + "'.",
+ e);
+ }
+ }
+
+ DOMNode node = request.getNode();
+ if (node == null) {
+ return null;
+ }
+ int offset = request.getOffset();
+ if (node.isElement()) {
+ // By default rename can be applied to rename tag name
+ DOMElement element = (DOMElement) node;
+ if (element.isInStartTag(offset)) {
+ return Either.forLeft(XMLPositionUtility.selectStartTagName(element));
+ }
+ if (element.isInEndTag(offset)) {
+ return Either.forLeft(XMLPositionUtility.selectEndTagName(element));
+ }
+ }
+ return null;
+ }
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java
index bd13a6b962..8b01877963 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLRename.java
@@ -21,6 +21,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -39,6 +40,7 @@
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.WorkspaceEdit;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
/**
* Handle all rename requests
@@ -55,8 +57,11 @@ public XMLRename(XMLExtensionsRegistry extensionsRegistry) {
this.extensionsRegistry = extensionsRegistry;
}
- public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String newText) {
+ public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String newText,
+ CancelChecker cancelChecker) {
+ cancelChecker.checkCanceled();
+
RenameRequest renameRequest = null;
try {
@@ -68,7 +73,7 @@ public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String
DOMNode node = renameRequest.getNode();
- if (node == null || (!node.isAttribute() && !node.isElement())
+ if (node == null || (!node.isAttribute() && !node.isElement() && !node.isText())
|| (node.isElement() && !((DOMElement) node).hasTagName())) {
return createWorkspaceEdit(xmlDocument.getDocumentURI(), Collections.emptyList());
@@ -78,7 +83,9 @@ public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String
for (IRenameParticipant participant : extensionsRegistry.getRenameParticipants()) {
try {
- participant.doRename(renameRequest, textEdits);
+ participant.doRename(renameRequest, textEdits, cancelChecker);
+ } catch (CancellationException e) {
+ throw e;
} catch (Exception e) {
LOGGER.log(Level.SEVERE,
"Error while processing rename for the participant '" + participant.getClass().getName() + "'.",
@@ -88,16 +95,17 @@ public WorkspaceEdit doRename(DOMDocument xmlDocument, Position position, String
textEdits.addAll(getRenameTextEdits(xmlDocument, node, position, newText));
+ cancelChecker.checkCanceled();
+
return createWorkspaceEdit(xmlDocument.getDocumentURI(), textEdits);
}
private List getRenameTextEdits(DOMDocument xmlDocument, DOMNode node, Position position,
String newText) {
-
- DOMElement element = getAssociatedElement(node);
- if (node == null) {
+ if (node == null || node.isText()) {
return Collections.emptyList();
}
+ DOMElement element = getAssociatedElement(node);
if (node.isCDATA()) {
return getCDATARenameTextEdits(xmlDocument, element, position, newText);
@@ -121,7 +129,7 @@ private List getRenameTextEdits(DOMDocument xmlDocument, DOMNode node,
* @return associated DOMElement
*/
private DOMElement getAssociatedElement(DOMNode node) {
- if (node == null || (!node.isElement() && !node.isAttribute())) {
+ if (!node.isElement() && !node.isAttribute()) {
return null;
}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesParticipant.java
new file mode 100644
index 0000000000..838a4c1525
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesParticipant.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+* Copyright (c) 2023 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;
+
+import java.util.List;
+
+import org.eclipse.lsp4j.Range;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+
+/**
+ * Linked editing ranges participant API.
+ *
+ * @author Angelo ZERR
+ *
+ */
+public interface ILinkedEditingRangesParticipant {
+
+ /**
+ * Find linked editing ranges for DOM document and position defined in the given
+ * request
.
+ *
+ * @param request the linked editing ranges request.
+ * @param ranges the ranges to update.
+ * @param cancelChecker the cancel checker.
+ */
+ void findLinkedEditingRanges(ILinkedEditingRangesRequest request, List ranges, CancelChecker cancelChecker);
+
+}
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesRequest.java
new file mode 100644
index 0000000000..49e08cfa2d
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/ILinkedEditingRangesRequest.java
@@ -0,0 +1,19 @@
+/*******************************************************************************
+* Copyright (c) 2023 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;
+
+/**
+ * Linked editing ranges request API.
+ *
+ */
+public interface ILinkedEditingRangesRequest extends IPositionRequest {
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IPrepareRenameRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IPrepareRenameRequest.java
new file mode 100644
index 0000000000..44060f3f17
--- /dev/null
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IPrepareRenameRequest.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+* Copyright (c) 2019 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;
+
+/**
+ * Prepare rename request API.
+ *
+ */
+public interface IPrepareRenameRequest extends IPositionRequest {
+
+}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameParticipant.java
index dc7a70dd0a..c213713b4b 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameParticipant.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameParticipant.java
@@ -13,7 +13,11 @@
import java.util.List;
+import org.eclipse.lsp4j.PrepareRenameResult;
+import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.jsonrpc.CancelChecker;
+import org.eclipse.lsp4j.jsonrpc.messages.Either;
/**
* Rename participant API.
@@ -21,5 +25,7 @@
*/
public interface IRenameParticipant {
- void doRename(IRenameRequest request, List locations);
+ void doRename(IRenameRequest request, List locations, CancelChecker cancelChecker);
+
+ Either prepareRename(IPrepareRenameRequest request, CancelChecker cancelChecker);
}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameRequest.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameRequest.java
index 38a1a98292..a22e1370d1 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameRequest.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/extensions/IRenameRequest.java
@@ -12,7 +12,7 @@
package org.eclipse.lemminx.services.extensions;
/**
- * Definition request API.
+ * Rename request API.
*
*/
public interface IRenameRequest extends IPositionRequest {
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 ea4c93b57f..0f74f7b056 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
@@ -60,6 +60,7 @@ public class XMLExtensionsRegistry implements IComponentProvider {
private final List codeLensParticipants;
private final List highlightingParticipants;
private final List renameParticipants;
+ private final List linkedEditingRangesParticipants;
private final List formatterParticipants;
private final List symbolsProviderParticipants;
private final List workspaceServiceParticipants;
@@ -94,6 +95,7 @@ public XMLExtensionsRegistry() {
codeLensParticipants = new ArrayList<>();
highlightingParticipants = new ArrayList<>();
renameParticipants = new ArrayList<>();
+ linkedEditingRangesParticipants = new ArrayList<>();
formatterParticipants = new ArrayList<>();
symbolsProviderParticipants = new ArrayList<>();
workspaceServiceParticipants = new ArrayList<>();
@@ -205,6 +207,11 @@ public Collection getRenameParticipants() {
return renameParticipants;
}
+ public List getLinkedEditingRangesParticipants() {
+ initializeIfNeeded();
+ return linkedEditingRangesParticipants;
+ }
+
public Collection getFormatterParticipants() {
initializeIfNeeded();
return formatterParticipants;
@@ -394,6 +401,16 @@ public void unregisterRenameParticipant(IRenameParticipant renameParticipant) {
renameParticipants.remove(renameParticipant);
}
+ public void registerLinkedEditingRangesParticipants(
+ ILinkedEditingRangesParticipant linkedEditingRangesParticipant) {
+ linkedEditingRangesParticipants.add(linkedEditingRangesParticipant);
+ }
+
+ public void unregisterLinkedEditingRangesParticipants(
+ ILinkedEditingRangesParticipant linkedEditingRangesParticipant) {
+ linkedEditingRangesParticipants.remove(linkedEditingRangesParticipant);
+ }
+
public void registerFormatterParticipant(IFormatterParticipant formatterParticipant) {
formatterParticipants.add(formatterParticipant);
}
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 332720ab2c..d80d8bccd1 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
@@ -20,6 +20,7 @@
import org.eclipse.lsp4j.ColorProviderOptions;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.DocumentLinkOptions;
+import org.eclipse.lsp4j.RenameOptions;
import org.eclipse.lsp4j.TextDocumentSyncKind;
/**
@@ -82,9 +83,10 @@ private ServerCapabilitiesConstants() {
public static final String LINKED_EDITING_RANGE_ID = UUID.randomUUID().toString();
public static final CompletionOptions DEFAULT_COMPLETION_OPTIONS = new CompletionOptions(true,
- Arrays.asList(".", ":", "<", "\"", "=", "/", "\\", "?", "\'", "&"));
+ Arrays.asList(".", ":", "<", "\"", "=", "/", "\\", "?", "\'", "&", "#"));
public static final TextDocumentSyncKind DEFAULT_SYNC_OPTION = TextDocumentSyncKind.Full;
public static final DocumentLinkOptions DEFAULT_LINK_OPTIONS = new DocumentLinkOptions(true);
+ public static final RenameOptions DEFAULT_RENAME_OPTIONS = new RenameOptions(true);
public static final ColorProviderOptions DEFAULT_COLOR_OPTIONS = new ColorProviderOptions();
public static final CodeLensOptions DEFAULT_CODELENS_OPTIONS = new CodeLensOptions();
public static final CodeActionOptions DEFAULT_CODEACTION_OPTIONS = createDefaultCodeActionOptions();
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java
index 99acc25da6..3a37648eed 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/settings/capabilities/XMLCapabilityManager.java
@@ -20,6 +20,7 @@
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_COLOR_OPTIONS;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_COMPLETION_OPTIONS;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_LINK_OPTIONS;
+import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFAULT_RENAME_OPTIONS;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DEFINITION_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DOCUMENT_HIGHLIGHT_ID;
import static org.eclipse.lemminx.settings.capabilities.ServerCapabilitiesConstants.DOCUMENT_SYMBOL_ID;
@@ -170,7 +171,7 @@ public void initializeCapabilities() {
registerCapability(COLOR_ID, TEXT_DOCUMENT_COLOR, DEFAULT_COLOR_OPTIONS);
}
if (this.getClientCapabilities().isRenameDynamicRegistrationSupported()) {
- registerCapability(RENAME_ID, TEXT_DOCUMENT_RENAME);
+ registerCapability(RENAME_ID, TEXT_DOCUMENT_RENAME, DEFAULT_RENAME_OPTIONS);
}
if (this.getClientCapabilities().isDefinitionDynamicRegistered()) {
registerCapability(DEFINITION_ID, TEXT_DOCUMENT_DEFINITION);
diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java
index e97745b1bc..d4c174548c 100644
--- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java
+++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java
@@ -70,11 +70,11 @@ public static boolean isWhitespace(String value) {
*
* @param value The string to check
* @return true
if any of the below hold, and false otherwise:
- *
- * - The String is
null
- * - The String is of length 0
- * - The String contains only whitespace characters
- *
+ *
+ * - The String is
null
+ * - The String is of length 0
+ * - The String contains only whitespace characters
+ *
*/
public static boolean isBlank(String value) {
return isEmpty(value) || isWhitespace(value);
@@ -422,15 +422,19 @@ public static String getString(Object obj) {
* and -1 if no word.
*/
public static int findStartWord(String text, int offset, Predicate isValidChar) {
+ return findStartWord(text, offset, 0, isValidChar);
+ }
+
+ public static int findStartWord(String text, int offset, int min, Predicate isValidChar) {
if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) {
return -1;
}
- for (int i = offset - 1; i >= 0; i--) {
+ for (int i = offset - 1; i >= min; i--) {
if (!isValidChar.test(text.charAt(i))) {
return i + 1;
}
}
- return 0;
+ return min;
}
/**
@@ -444,16 +448,21 @@ public static int findStartWord(String text, int offset, Predicate is
* @return the start word offset from the right of the given offset
* and -1 if no word.
*/
+
public static int findEndWord(String text, int offset, Predicate isValidChar) {
+ return findEndWord(text, offset, text.length(), isValidChar);
+ }
+
+ public static int findEndWord(String text, int offset, int max, Predicate isValidChar) {
if (offset < 0 || offset >= text.length() || !isValidChar.test(text.charAt(offset))) {
return -1;
}
- for (int i = offset + 1; i < text.length(); i++) {
+ for (int i = offset + 1; i < max; i++) {
if (!isValidChar.test(text.charAt(i))) {
return i;
}
}
- return -1;
+ return max;
}
/**
@@ -498,7 +507,7 @@ public static boolean isQuoted(String value) {
*
* @param reference the string being compared to
* @param current the string compared
- * @return true if the two strings are similar, false otherwise
+ * @return true if the two strings are similar, false otherwise
*/
public static boolean isSimilar(String reference, String current) {
int threshold = Math.round(MAX_DISTANCE_DIFF_RATIO * reference.length());
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 37de6e37df..e512257732 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
@@ -745,6 +745,10 @@ public static Diagnostic d(int startLine, int startCharacter, int endLine, int e
code != null ? code.getCode() : null);
}
+ public static Range r(int line, int startCharacter, int endCharacter) {
+ return r(line, startCharacter, line, endCharacter);
+ }
+
public static Range r(int startLine, int startCharacter, int endLine, int endCharacter) {
return new Range(new Position(startLine, startCharacter), new Position(endLine, endCharacter));
}
@@ -1443,9 +1447,9 @@ public static void testReferencesFor(XMLLanguageService xmlLanguageService, Stri
xmlLanguageService = new XMLLanguageService();
}
- ContentModelSettings settings = new ContentModelSettings();
- settings.setUseCache(false);
- xmlLanguageService.doSave(new SettingsSaveContext(settings));
+// ContentModelSettings settings = new ContentModelSettings();
+// settings.setUseCache(false);
+// xmlLanguageService.doSave(new SettingsSaveContext(settings));
DOMDocument xmlDocument = DOMParser.getInstance().parse(document,
xmlLanguageService.getResolverExtensionManager());
@@ -1691,18 +1695,26 @@ public static void assertRename(String value, String newText, List exp
public static void assertRename(XMLLanguageService languageService, String value, String newText,
List expectedEdits) throws BadLocationException {
+ assertRename(languageService, value, null, newText, expectedEdits);
+ }
+
+ public static void assertRename(XMLLanguageService languageService, String value, String fileURI, String newText,
+ List expectedEdits) throws BadLocationException {
int offset = value.indexOf("|");
value = value.substring(0, offset) + value.substring(offset + 1);
- DOMDocument document = DOMParser.getInstance().parse(value, "test://test/test.html", null);
+ fileURI = fileURI != null ? fileURI : "test://test/test.html";
+ DOMDocument document = DOMParser.getInstance().parse(value, fileURI,
+ null);
Position position = document.positionAt(offset);
if (languageService == null) {
languageService = new XMLLanguageService();
}
- WorkspaceEdit workspaceEdit = languageService.doRename(document, position, newText);
- List actualEdits = workspaceEdit.getChanges().get("test://test/test.html");
+ WorkspaceEdit workspaceEdit = languageService.doRename(document, position, newText, () -> {
+ });
+ List actualEdits = workspaceEdit.getChanges().get(fileURI);
assertArrayEquals(expectedEdits.toArray(), actualEdits.toArray());
}
@@ -1714,18 +1726,18 @@ public static void testLinkedEditingFor(String xml, LinkedEditingRanges expected
public static void testLinkedEditingFor(String value, String fileURI, LinkedEditingRanges expected)
throws BadLocationException {
+ testLinkedEditingFor(new XMLLanguageService(), value, fileURI, expected);
+ }
+
+ public static void testLinkedEditingFor(XMLLanguageService xmlLanguageService, String value, String fileURI,
+ LinkedEditingRanges expected)
+ 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);
- XMLLanguageService xmlLanguageService = new XMLLanguageService();
-
- ContentModelSettings settings = new ContentModelSettings();
- settings.setUseCache(false);
- xmlLanguageService.doSave(new SettingsSaveContext(settings));
-
DOMDocument xmlDocument = DOMParser.getInstance().parse(document,
xmlLanguageService.getResolverExtensionManager());
xmlLanguageService.setDocumentProvider((uri) -> xmlDocument);
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCodeLensExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCodeLensExtensionsTest.java
new file mode 100644
index 0000000000..afe5eaf5cd
--- /dev/null
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCodeLensExtensionsTest.java
@@ -0,0 +1,125 @@
+/*******************************************************************************
+* Copyright (c) 2023 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.references;
+
+import static org.eclipse.lemminx.XMLAssert.cl;
+import static org.eclipse.lemminx.XMLAssert.r;
+import static org.eclipse.lemminx.client.ClientCommands.SHOW_REFERENCES;
+
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+import org.eclipse.lemminx.AbstractCacheBasedTest;
+import org.eclipse.lemminx.XMLAssert;
+import org.eclipse.lemminx.XMLAssert.SettingsSaveContext;
+import org.eclipse.lemminx.client.CodeLensKind;
+import org.eclipse.lemminx.commons.BadLocationException;
+import org.eclipse.lemminx.services.XMLLanguageService;
+import org.eclipse.lsp4j.CodeLens;
+import org.junit.jupiter.api.Test;
+
+/**
+ * XML references codelens tests
+ *
+ */
+public class XMLReferencesCodeLensExtensionsTest extends AbstractCacheBasedTest {
+
+ @Test
+ public void tei() throws BadLocationException {
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Title\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Publication information
\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Information about the source
\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " Some text here.
\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCodeLensFor(xml, "file:///test/tei.xml", //
+ cl(r(16, 10, 16, 26), "1 reference", SHOW_REFERENCES));
+ }
+
+ @Test
+ public void teiTargetMulti() throws BadLocationException {
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ // [2 references]
+ + " \r\n"
+ // [1 reference]
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCodeLensFor(xml, "file:///test/tei.xml", //
+ cl(r(5, 6, 5, 16), "1 reference", SHOW_REFERENCES), //
+ cl(r(6, 6, 6, 16), "2 references", SHOW_REFERENCES));
+ }
+
+ @Test
+ public void web() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ + " \r\n"
+ + " comingsoon\r\n"
+ + " mysite.server.ComingSoonServlet\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " comingsoon\r\n"
+ + " /*\r\n"
+ + " \r\n"
+ + "\r\n"
+ + "";
+ testCodeLensFor(xml, "file:///test/web.xml", //
+ cl(r(4, 18, 4, 28), "1 reference", SHOW_REFERENCES));
+ }
+
+ private static void testCodeLensFor(String value, String fileURI, CodeLens... expected) {
+ XMLLanguageService xmlLanguageService = new XMLLanguageService();
+ xmlLanguageService.getExtensions();
+ Consumer customConfiguration = ls -> {
+ ls.doSave(new SettingsSaveContext(XMLReferencesSettingsForTest.createXMLReferencesSettings()));
+ };
+ XMLAssert.testCodeLensFor(value, fileURI, xmlLanguageService, Arrays.asList(CodeLensKind.References),
+ customConfiguration, expected);
+ }
+
+}
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCompletionExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCompletionExtensionsTest.java
index 161e494ddf..ec14aba69d 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCompletionExtensionsTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesCompletionExtensionsTest.java
@@ -14,17 +14,10 @@
import static org.eclipse.lemminx.XMLAssert.c;
import static org.eclipse.lemminx.XMLAssert.te;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.XMLAssert.SettingsSaveContext;
import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.extensions.contentmodel.BaseFileTempTest;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferences;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferencesSettings;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lsp4j.CompletionItem;
import org.junit.jupiter.api.Test;
@@ -36,7 +29,7 @@
public class XMLReferencesCompletionExtensionsTest extends BaseFileTempTest {
@Test
- public void tei() throws BadLocationException {
+ public void teiCorresp() throws BadLocationException {
// completion on <|
String xml = "\r\n"
// + "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 16, 8, 16, "#A"), "#A"), //
+ c("#B", te(8, 16, 8, 16, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti1() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 16, 8, 18, "#A"), "#A"), //
+ c("#B", te(8, 16, 8, 18, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti2() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 16, 8, 18, "#A"), "#A"), //
+ c("#B", te(8, 16, 8, 18, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti3() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 16, 8, 18, "#A"), "#A"), //
+ c("#B", te(8, 16, 8, 18, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti4() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 16, 8, 19, "#A"), "#A"), //
+ c("#B", te(8, 16, 8, 19, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti5() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 16, 8, 18, "#A"), "#A"), //
+ c("#B", te(8, 16, 8, 18, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti6() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 19, 8, 21, "#A"), "#A"), //
+ c("#B", te(8, 19, 8, 21, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti7() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 19, 8, 21, "#A"), "#A"), //
+ c("#B", te(8, 19, 8, 21, "#B"), "#B"));
+ }
+
+ @Test
+ public void teiTargetMulti8() throws BadLocationException {
+ // completion on <|
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testCompletionFor(xml, "file:///test/tei.xml", //
+ 2, //
+ c("#A", te(8, 19, 8, 19, "#A"), "#A"), //
+ c("#B", te(8, 19, 8, 19, "#B"), "#B"));
+ }
+
@Test
public void docbook() throws BadLocationException {
// completion on <|
@@ -98,7 +307,8 @@ public void web() throws BadLocationException {
// completion on <|
String xml = "\r\n"
+ " \r\n"
+ " comingsoon\r\n"
@@ -120,7 +330,8 @@ public void webInEmptyText() throws BadLocationException {
// completion on <|
String xml = "\r\n"
+ " \r\n"
+ " comingsoon\r\n"
@@ -136,64 +347,14 @@ public void webInEmptyText() throws BadLocationException {
1 + XMLAssert.CDATA_SNIPPETS + XMLAssert.COMMENT_SNIPPETS, //
c("comingsoon", te(8, 18, 8, 18, "comingsoon"), "comingsoon"));
}
+
private static void testCompletionFor(String value, String fileURI, Integer expectedCount,
CompletionItem... expectedItems)
throws BadLocationException {
XMLAssert.testCompletionFor(new XMLLanguageService(), value, null, ls -> {
-
- XMLReferencesSettings referencesSettings = new XMLReferencesSettings();
- referencesSettings.setReferences(createReferences());
- ls.doSave(new SettingsSaveContext(referencesSettings));
+ ls.doSave(new SettingsSaveContext(XMLReferencesSettingsForTest.createXMLReferencesSettings()));
}, fileURI,
expectedCount, true, expectedItems);
}
-
- private static List createReferences() {
- List references = new ArrayList<>();
- /*
- * {
- * "prefix": "#",
- * "from": "@corresp",
- * "to": "@xml:id"
- * }
- */
- XMLReferences tei = new XMLReferences();
- tei.setPattern("**/*tei.xml");
- XMLReferenceExpression corresp = new XMLReferenceExpression();
- corresp.setPrefix("#");
- corresp.setFrom("@corresp");
- corresp.setTo("@xml:id");
- tei.setExpressions(Arrays.asList(corresp));
- references.add(tei);
- /*
- * {
- * "from": "xref/@linkend",
- * "to": "@id"
- * }
- */
- XMLReferences docbook = new XMLReferences();
- docbook.setPattern("**/*docbook.xml");
- XMLReferenceExpression linkend = new XMLReferenceExpression();
- linkend.setFrom("xref/@linkend");
- linkend.setTo("@id");
- docbook.setExpressions(Arrays.asList(linkend));
- references.add(docbook);
-
- /*
- * {
- * "from": "servlet-mapping/servlet-name/text()",
- * "to": "servlet/servlet-name/text()"
- * }
- */
- XMLReferences web = new XMLReferences();
- web.setPattern("**/web.xml");
- XMLReferenceExpression servletName = new XMLReferenceExpression();
- servletName.setFrom("servlet-mapping/servlet-name/text()");
- servletName.setTo("servlet/servlet-name/text()");
- web.setExpressions(Arrays.asList(servletName));
- references.add(web);
-
- return references;
- }
}
\ No newline at end of file
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesDefinitionExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesDefinitionExtensionsTest.java
index 43fd3bb1df..6317e32b2b 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesDefinitionExtensionsTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesDefinitionExtensionsTest.java
@@ -14,17 +14,10 @@
import static org.eclipse.lemminx.XMLAssert.ll;
import static org.eclipse.lemminx.XMLAssert.r;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
import org.eclipse.lemminx.AbstractCacheBasedTest;
import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.XMLAssert.SettingsSaveContext;
import org.eclipse.lemminx.commons.BadLocationException;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferences;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferencesSettings;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lsp4j.LocationLink;
import org.junit.jupiter.api.Test;
@@ -65,9 +58,51 @@ public void tei() throws BadLocationException {
+ " \r\n"
+ "";
testDefinitionFor(xml, "file:///test/tei.xml",
- ll("file:///test/tei.xml", r(18, 22, 18, 32), r(16, 17, 16, 26)));
+ ll("file:///test/tei.xml", r(18, 23, 18, 31), r(16, 18, 16, 25)));
+ }
+
+ @Test
+ public void teiTargetMulti1() throws BadLocationException {
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testDefinitionFor(xml, "file:///test/tei.xml",
+ ll("file:///test/tei.xml", r(8, 16, 8, 18), r(5, 14, 5, 15)));
}
+ @Test
+ public void teiTargetMulti2() throws BadLocationException {
+ String xml = "\r\n"
+ // + "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ testDefinitionFor(xml, "file:///test/tei.xml",
+ ll("file:///test/tei.xml", r(8, 19, 8, 21), r(6, 14, 6, 15)));
+ }
+
@Test
public void docbook() throws BadLocationException {
String xml = "\r\n"
@@ -85,7 +120,7 @@ public void docbook() throws BadLocationException {
+ " \r\n"
+ "";
testDefinitionFor(xml, "file:///test/docbook.xml",
- ll("file:///test/docbook.xml", r(4, 22, 4, 33), r(2, 16, 2, 27)));
+ ll("file:///test/docbook.xml", r(4, 23, 4, 32), r(2, 17, 2, 26)));
}
@Test
@@ -175,60 +210,11 @@ public void noDefinitionWithTEIAndNotHash() throws BadLocationException {
private static void testDefinitionFor(String xml, String fileURI, LocationLink... expectedItems)
throws BadLocationException {
- XMLReferencesSettings referencesSettings = new XMLReferencesSettings();
- referencesSettings.setReferences(createReferences());
XMLLanguageService xmlLanguageService = new XMLLanguageService();
xmlLanguageService.getExtensions();
- xmlLanguageService.doSave(new SettingsSaveContext(referencesSettings));
+ xmlLanguageService.doSave(new SettingsSaveContext(XMLReferencesSettingsForTest.createXMLReferencesSettings()));
XMLAssert.testDefinitionFor(xmlLanguageService, xml, fileURI, expectedItems);
}
- private static List createReferences() {
- List references = new ArrayList<>();
- /*
- * {
- * "prefix": "#",
- * "from": "@corresp",
- * "to": "@xml:id"
- * }
- */
- XMLReferences tei = new XMLReferences();
- tei.setPattern("**/*tei.xml");
- XMLReferenceExpression corresp = new XMLReferenceExpression();
- corresp.setPrefix("#");
- corresp.setFrom("@corresp");
- corresp.setTo("@xml:id");
- tei.setExpressions(Arrays.asList(corresp));
- references.add(tei);
- /*
- * {
- * "from": "xref/@linkend",
- * "to": "@id"
- * }
- */
- XMLReferences docbook = new XMLReferences();
- docbook.setPattern("**/*docbook.xml");
- XMLReferenceExpression linkend = new XMLReferenceExpression();
- linkend.setFrom("xref/@linkend");
- linkend.setTo("@id");
- docbook.setExpressions(Arrays.asList(linkend));
- references.add(docbook);
-
- /*
- * {
- * "from": "servlet-mapping/servlet-name/text()",
- * "to": "servlet/servlet-name/text()"
- * }
- */
- XMLReferences web = new XMLReferences();
- web.setPattern("**/web.xml");
- XMLReferenceExpression servletName = new XMLReferenceExpression();
- servletName.setFrom("servlet-mapping/servlet-name/text()");
- servletName.setTo("servlet/servlet-name/text()");
- web.setExpressions(Arrays.asList(servletName));
- references.add(web);
-
- return references;
- }
}
diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesHighlightingExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesHighlightingExtensionsTest.java
index f63b361584..4121ad6cf3 100644
--- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesHighlightingExtensionsTest.java
+++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/references/XMLReferencesHighlightingExtensionsTest.java
@@ -16,17 +16,10 @@
import static org.eclipse.lsp4j.DocumentHighlightKind.Read;
import static org.eclipse.lsp4j.DocumentHighlightKind.Write;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
import org.eclipse.lemminx.AbstractCacheBasedTest;
import org.eclipse.lemminx.XMLAssert;
import org.eclipse.lemminx.XMLAssert.SettingsSaveContext;
import org.eclipse.lemminx.commons.BadLocationException;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferenceExpression;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferences;
-import org.eclipse.lemminx.extensions.references.settings.XMLReferencesSettings;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.eclipse.lsp4j.DocumentHighlight;
import org.junit.jupiter.api.Test;
@@ -39,7 +32,7 @@
public class XMLReferencesHighlightingExtensionsTest extends AbstractCacheBasedTest {
@Test
- public void tei() throws BadLocationException {
+ public void teiOnCorresp() throws BadLocationException {
String xml = "\r\n"
// + "Some text here.
\r\n"
+ " \r\n" // <-- // highlighting here should highlight
// body/@xml-id="body-id"
+ + " \r\n"
+ "