Skip to content

Commit

Permalink
Find definition for locally declared entity
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#625

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed May 15, 2020
1 parent c4804f0 commit 41806c8
Show file tree
Hide file tree
Showing 7 changed files with 272 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
*/
package org.eclipse.lemminx.dom;

import static org.eclipse.lemminx.utils.StringUtils.findEndWord;
import static org.eclipse.lemminx.utils.StringUtils.findStartWord;

import java.util.function.BiConsumer;
import java.util.function.Predicate;

/**
* DTD Element Declaration <!ELEMENT
Expand All @@ -22,6 +26,8 @@
*/
public class DTDElementDecl extends DTDDeclNode {

private static final Predicate<Character> isValidChar = (c) -> Character.isJavaIdentifierPart(c) || c == '-';

/**
* Formats:
*
Expand Down Expand Up @@ -95,15 +101,13 @@ public DTDDeclParameter getParameterAt(int offset) {
return null;
}
// We are after the <!ELEMENT name, search the parameter
int start = getNameParameter().getEnd();
int end = getEnd();
String text = getOwnerDocument().getText();
// Find the start word offset from the left of the offset (ex : (head|ing) will
// return offset of 'h'
int paramStart = findStartWord(text, start, offset);
int paramStart = findStartWord(text, offset, isValidChar);
// Find the end word to the right of the offset (ex : (head|ing) will return
// offset of 'g'
int paramEnd = findEndWord(text, offset, end);
int paramEnd = findEndWord(text, offset, isValidChar);
if (paramStart == -1 || paramEnd == -1) {
// no word
return null;
Expand Down Expand Up @@ -131,7 +135,6 @@ public void collectParameters(DTDDeclParameter target, BiConsumer<DTDDeclParamet
int end = getEnd();

String text = getOwnerDocument().getText();
text.length();
int wordStart = -1;
int wordEnd = -1;
// Loop for content after <!ELEMENT element-name (
Expand All @@ -143,7 +146,7 @@ public void collectParameters(DTDDeclParameter target, BiConsumer<DTDDeclParamet
for (int i = start; i < end; i++) {
char c = text.charAt(i);
// check if current character is valid for a word.
if (isValidChar(c)) {
if (isValidChar.test(c)) {
if (wordStart == -1) {
// start of the word
wordStart = i;
Expand All @@ -164,63 +167,6 @@ public void collectParameters(DTDDeclParameter target, BiConsumer<DTDDeclParamet
}
}

/**
* Returns the start word offset from the <code>from</code> offset to the
* <code>to</code> offse and -1 if no word.
*
* @param text the text
* @param from the from offset
* @param to the to offset
* @return the start word offset from the <code>from</code> offset to the
* <code>to</code> offse and -1 if no word.
*/
private static int findStartWord(String text, int from, int to) {
int wordStart = -1;
int length = to - from;
for (int i = 0; i < length; i++) {
if (isValidChar(text.charAt(to - i))) {
wordStart = to - i;
} else {
return wordStart;
}
}
return wordStart;
}

/**
* Returns the end word offset from the <code>from</code> offset to the
* <code>to</code> offse and -1 if no word.
*
* @param text the text
* @param from the from offset
* @param to the to offset
* @return the end word offset from the <code>from</code> offset to the
* <code>to</code> offse and -1 if no word.
*/
private static int findEndWord(String text, int from, int to) {
int wordEnd = -1;
int length = to - from;
for (int i = 0; i < length; i++) {
if (isValidChar(text.charAt(from + i))) {
wordEnd = from + i + 1;
} else {
return wordEnd;
}
}
return wordEnd;
}

/**
* Return true if the given character belong to the element name and false
* otherwise.
*
* @param c the character
* @return true if the given character belong to the element name and false
*/
private static boolean isValidChar(char c) {
return Character.isJavaIdentifierPart(c) || c == '-';
}

/**
* Returns true if the word in the given <code>text</code> which starts at
* <code>wordStart</code> offset and ends at <code>wordEnd</code> matches the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
*/
package org.eclipse.lemminx.extensions.entities;

import org.eclipse.lemminx.extensions.entities.participants.EntitiesCompletionParticipant;
import org.eclipse.lemminx.extensions.entities.participants.EntitiesDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.ICompletionParticipant;
import org.eclipse.lemminx.services.extensions.IDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.IXMLExtension;
import org.eclipse.lemminx.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lemminx.services.extensions.save.ISaveContext;
Expand All @@ -25,15 +28,20 @@ public class EntitiesPlugin implements IXMLExtension {

private ICompletionParticipant completionParticipant;

private IDefinitionParticipant definitionParticipant;

@Override
public void start(InitializeParams params, XMLExtensionsRegistry registry) {
completionParticipant = new EntitiesCompletionParticipant();
registry.registerCompletionParticipant(completionParticipant);
definitionParticipant = new EntitiesDefinitionParticipant();
registry.registerDefinitionParticipant(definitionParticipant);
}

@Override
public void stop(XMLExtensionsRegistry registry) {
registry.unregisterCompletionParticipant(completionParticipant);
registry.unregisterDefinitionParticipant(definitionParticipant);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@
* Contributors:
* Red Hat Inc. - initial API and implementation
*/
package org.eclipse.lemminx.extensions.entities;
package org.eclipse.lemminx.extensions.entities.participants;

import java.util.List;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.extensions.entities.EntitiesDocumentationUtils;
import org.eclipse.lemminx.extensions.entities.EntitiesDocumentationUtils.PredefinedEntity;
import org.eclipse.lemminx.services.extensions.CompletionParticipantAdapter;
import org.eclipse.lemminx.services.extensions.ICompletionRequest;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright (c) 2020 Red Hat, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* 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.entities.participants;

import static org.eclipse.lemminx.utils.StringUtils.findEndWord;
import static org.eclipse.lemminx.utils.StringUtils.findStartWord;

import java.util.List;
import java.util.function.Predicate;

import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.dom.DOMDocumentType;
import org.eclipse.lemminx.dom.DOMNode;
import org.eclipse.lemminx.dom.DTDDeclParameter;
import org.eclipse.lemminx.dom.DTDEntityDecl;
import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument;
import org.eclipse.lemminx.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lemminx.services.extensions.AbstractDefinitionParticipant;
import org.eclipse.lemminx.services.extensions.IDefinitionRequest;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.LocationLink;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.w3c.dom.Entity;
import org.w3c.dom.NamedNodeMap;

/**
* Entities definition used in a text node (ex : &amp;).
*
*/
public class EntitiesDefinitionParticipant extends AbstractDefinitionParticipant {

private static final Predicate<Character> isValidChar = (c) -> Character.isJavaIdentifierPart(c) || c == '-';

@Override
protected boolean match(DOMDocument document) {
return true;
}

@Override
protected void doFindDefinition(IDefinitionRequest request, List<LocationLink> locations,
CancelChecker cancelChecker) {
DOMNode node = request.getNode();
if (!node.isText()) {
return;
}
// definition is done in a text node, check if it's a referenced entity
DOMDocument document = request.getXMLDocument();
int offset = request.getOffset();
String text = document.getText();

int paramStart = findStartWord(text, offset, isValidChar);
if (paramStart > 0 && text.charAt(paramStart - 1) == '&') {
int paramEnd = findEndWord(text, offset, isValidChar);
// Find definition of the entity
// abcd &loc|al; --> in this case search local entity
String entityName = text.substring(paramStart, paramEnd);
Range entityRange = XMLPositionUtility.createRange(paramStart, paramEnd, document);
searchInInternalEntities(entityName, entityRange, document, locations, cancelChecker);
searchInExternalEntities(entityName, entityRange, document, locations, request, cancelChecker);
}
}

/**
* Search the given entity name in the internal entities.
*
* @param document the DOM document.
* @param entityName the entity name.
* @param entityRange the entity range.
* @param locations the location links
* @param cancelChecker the cancel checker.
*/
private static void searchInInternalEntities(String entityName, Range entityRange, DOMDocument document,
List<LocationLink> locations, CancelChecker cancelChecker) {
DOMDocumentType docType = document.getDoctype();
if (docType == null) {
return;
}
cancelChecker.checkCanceled();
// Loop for entities declared in the DOCTYPE of the document
NamedNodeMap entities = docType.getEntities();
for (int i = 0; i < entities.getLength(); i++) {
cancelChecker.checkCanceled();
DTDEntityDecl entity = (DTDEntityDecl) entities.item(i);
if (entityName.equals(entity.getName())) {
DTDDeclParameter name = entity.getNameParameter();
locations.add(XMLPositionUtility.createLocationLink(entityRange, name));
}
}
}

/**
* Search the given entity name in the external entities.
*
* @param document the DOM document.
* @param entityName the entity name.
* @param entityRange the entity range.
* @param locations the location links
* @param request the definition request.
* @param cancelChecker the cancel checker.
*/
private static void searchInExternalEntities(String entityName, Range entityRange, DOMDocument document,
List<LocationLink> locations, IDefinitionRequest request, CancelChecker cancelChecker) {
ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class);
CMDocument cmDocument = contentModelManager.findCMDocument(document, null);
if (cmDocument != null) {
List<Entity> entities = cmDocument.getEntities();
for (Entity entity : entities) {
if (entityName.equals(entity.getNodeName())) {
// TODO : retrieve location from the external entity?
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.Arrays;
import java.util.Collection;
import java.util.function.Predicate;

/**
* String utilities.
Expand Down Expand Up @@ -42,6 +43,7 @@ public static boolean isQuote(char c) {
public static boolean isWhitespace(String value, int index) {
return isWhitespace(value, index, value.length());
}

public static boolean isWhitespace(String value, int index, int end) {
if (value == null) {
return false;
Expand Down Expand Up @@ -391,4 +393,50 @@ public static String getString(Object obj) {
return null;
}

/**
* Returns the start word offset from the left of the given <code>offset</code>
* and -1 if no word.
*
* @param text the text
* @param offset the offset
* @param isValidChar predicate to check if current character belong to the
* word.
* @return the start word offset from the left of the given <code>offset</code>
* and -1 if no word.
*/
public static int findStartWord(String text, int offset, Predicate<Character> isValidChar) {
if (!isValidChar.test(text.charAt(offset))) {
return -1;
}
for (int i = offset - 1; i >= 0; i--) {
if (!isValidChar.test(text.charAt(i))) {
return i + 1;
}
}
return -1;
}

/**
* Returns the end word offset from the right of the given <code>offset</code>
* and -1 if no word.
*
* @param text the text
* @param offset the offset
* @param isValidChar predicate to check if current character belong to the
* word.
* @return the start word offset from the right of the given <code>offset</code>
* and -1 if no word.
*/
public static int findEndWord(String text, int offset, Predicate<Character> isValidChar) {
if (!isValidChar.test(text.charAt(offset))) {
return -1;
}
for (int i = offset + 1; i < text.length(); i++) {
if (!isValidChar.test(text.charAt(i))) {
return i;
}
}
return -1;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,10 @@ public static LocationLink createLocationLink(DOMRange origin, DOMRange target)
} else {
originSelectionRange = XMLPositionUtility.createRange(origin);
}
return createLocationLink(originSelectionRange, target);
}

public static LocationLink createLocationLink(Range originSelectionRange, DOMRange target) {
Range targetRange = XMLPositionUtility.createRange(target);
Range targetSelectionRange = targetRange;
DOMDocument targetDocument = target.getOwnerDocument();
Expand Down
Loading

0 comments on commit 41806c8

Please sign in to comment.