Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for textDocument/documentHighlight for DTD #552

Merged
merged 1 commit into from
Aug 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/
package org.eclipse.lsp4xml.dom;

import java.util.function.BiConsumer;

/**
* DTD Element Declaration <!ELEMENT
*
Expand Down Expand Up @@ -107,6 +109,59 @@ public DTDDeclParameter getParameterAt(int offset) {
return new DTDDeclParameter(this, paramStart, paramEnd);
}

@Override
public DTDDeclParameter getReferencedElementNameAt(int offset) {
return getParameterAt(offset);
}

/**
* Collect parameters which matches the given target.
*
* @param target the target
* @param collector the collector to collect parameters.
*/
public void collectParameters(DTDDeclParameter target, BiConsumer<DTDDeclParameter, DTDDeclParameter> collector) {
DTDDeclParameter name = getNameParameter();
if (name == null) {
return;
}
int start = name.getEnd();
int end = getEnd();

String text = getOwnerDocument().getText();
text.length();
int wordStart = -1;
int wordEnd = -1;
// Loop for content after <!ELEMENT element-name (
// to check if target checks words

// Ex : if 'svg' is the word to find (the target node)
// and we have <!ELEMENT element-name (svg, title, font-face)
// we must collect svg as child
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 (wordStart == -1) {
// start of the word
wordStart = i;
}
} else if (wordStart != -1) {
// current character is not valid, it's the end of the word.
wordEnd = i;
}
if (wordStart != -1 && wordEnd != -1) {
// a word was found
boolean check = isMatchName(target.getParameter(), text, wordStart, wordEnd);
if (check) {
collector.accept(new DTDDeclParameter(this, wordStart, wordEnd), target);
}
wordStart = -1;
wordEnd = -1;
}
}
}

/**
* Returns the start word offset from the <code>from</code> offset to the
* <code>to</code> offse and -1 if no word.
Expand Down Expand Up @@ -164,9 +219,30 @@ private static boolean isValidChar(char c) {
return Character.isJavaIdentifierPart(c) || c == '-';
}

@Override
public DTDDeclParameter getReferencedElementNameAt(int offset) {
return getParameterAt(offset);
/**
* 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
* given <code>searchWord</code>
*
* @param searchWord the word to search
* @param text the text
* @param wordStart the word start offset
* @param wordEnd the word end offset
* @return 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 given <code>searchName</code>
*/
private static boolean isMatchName(String searchWord, String text, int wordStart, int wordEnd) {
int length = wordEnd - wordStart;
if (searchWord.length() != length) {
return false;
}
for (int j = 0; j < length; j++) {
if ((searchWord.charAt(j) != text.charAt(wordStart + j))) {
return false;
}
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelProvider;
import org.eclipse.lsp4xml.extensions.dtd.contentmodel.CMDTDContentModelProvider;
import org.eclipse.lsp4xml.extensions.dtd.participants.DTDDefinitionParticipant;
import org.eclipse.lsp4xml.extensions.dtd.participants.DTDHighlightingParticipant;
import org.eclipse.lsp4xml.extensions.dtd.participants.diagnostics.DTDDiagnosticsParticipant;
import org.eclipse.lsp4xml.services.extensions.IDefinitionParticipant;
import org.eclipse.lsp4xml.services.extensions.IHighlightingParticipant;
import org.eclipse.lsp4xml.services.extensions.IXMLExtension;
import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry;
import org.eclipse.lsp4xml.services.extensions.diagnostics.IDiagnosticsParticipant;
Expand All @@ -29,10 +31,12 @@ public class DTDPlugin implements IXMLExtension {

private final IDiagnosticsParticipant diagnosticsParticipant;
private final IDefinitionParticipant definitionParticipant;
private IHighlightingParticipant highlightingParticipant;

public DTDPlugin() {
diagnosticsParticipant = new DTDDiagnosticsParticipant();
definitionParticipant = new DTDDefinitionParticipant();
highlightingParticipant = new DTDHighlightingParticipant();
}

@Override
Expand All @@ -50,6 +54,8 @@ public void start(InitializeParams params, XMLExtensionsRegistry registry) {
registry.registerDiagnosticsParticipant(diagnosticsParticipant);
// register definition participant
registry.registerDefinitionParticipant(definitionParticipant);
// register highlighting participant
registry.registerHighlightingParticipant(highlightingParticipant);
}

@Override
Expand All @@ -58,5 +64,7 @@ public void stop(XMLExtensionsRegistry registry) {
registry.unregisterDiagnosticsParticipant(diagnosticsParticipant);
// unregister definition participant
registry.unregisterDefinitionParticipant(definitionParticipant);
// unregister highlighting participant
registry.unregisterHighlightingParticipant(highlightingParticipant);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*******************************************************************************
* 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
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lsp4xml.extensions.dtd.participants;

import java.util.List;

import org.eclipse.lsp4j.DocumentHighlight;
import org.eclipse.lsp4j.DocumentHighlightKind;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.DTDAttlistDecl;
import org.eclipse.lsp4xml.dom.DTDDeclParameter;
import org.eclipse.lsp4xml.dom.DTDElementDecl;
import org.eclipse.lsp4xml.extensions.dtd.utils.DTDUtils;
import org.eclipse.lsp4xml.services.extensions.IHighlightingParticipant;
import org.eclipse.lsp4xml.utils.XMLPositionUtility;

/**
* DTD highlight participant
*
* @author Angelo ZERR
*
*/

public class DTDHighlightingParticipant implements IHighlightingParticipant {

@Override
public void findDocumentHighlights(DOMNode node, Position position, int offset, List<DocumentHighlight> highlights,
CancelChecker cancelChecker) {
boolean findReferences = false;
DTDDeclParameter parameter = null;
DTDElementDecl elementDecl = null;

if (node.isDTDElementDecl()) {
elementDecl = (DTDElementDecl) node;
if (elementDecl.isInNameParameter(offset)) {
// <!ELEMENT na|me --> here cursor is in the name of <!ELEMENT
// we must find all references from the <!ELEMENT which defines the name
findReferences = true;
parameter = elementDecl.getNameParameter();
} else {
// <!ELEMENT name (chi|ld --> here cursor is in the child element
// we must find only the <!ELEMENT child
parameter = elementDecl.getParameterAt(offset);
}
} else if (node.isDTDAttListDecl()) {
DTDAttlistDecl attlistDecl = (DTDAttlistDecl) node;
if (attlistDecl.isInNameParameter(offset)) {
// <!ATTLIST na|me --> here cusror is in the name of <!ATTLIST
// we must find only the <!ELEMENT name
parameter = attlistDecl.getNameParameter();
}
}

if (parameter == null) {
return;
}

if (findReferences) {
// case with <!ELEMENT na|me

// highlight <!ELEMENT na|me
DTDDeclParameter originNode = parameter;
highlights.add(
new DocumentHighlight(XMLPositionUtility.createRange(originNode), DocumentHighlightKind.Write));

// highlight all references of na|me in ATTLIST and child of <!ELEMENT
DTDUtils.searchDTDOriginElementDecls(elementDecl, (origin, target) -> {
highlights
.add(new DocumentHighlight(XMLPositionUtility.createRange(origin), DocumentHighlightKind.Read));
}, cancelChecker);
} else {
// case with
// - <!ELEMENT name (chi|ld
// - <!ATTLIST na|me

// highlight <!ELEMENT name (chi|ld or <!ATTLIST na|me
DTDDeclParameter targetNode = parameter;
highlights
.add(new DocumentHighlight(XMLPositionUtility.createRange(targetNode), DocumentHighlightKind.Read));

// highlight the target <!ELEMENT nam|e
DTDUtils.searchDTDTargetElementDecl(parameter, true, targetName -> {
highlights.add(
new DocumentHighlight(XMLPositionUtility.createRange(targetName), DocumentHighlightKind.Write));
});
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@
*******************************************************************************/
package org.eclipse.lsp4xml.extensions.dtd.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

import org.eclipse.lsp4j.jsonrpc.CancelChecker;
import org.eclipse.lsp4xml.dom.DOMDocumentType;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.DTDAttlistDecl;
import org.eclipse.lsp4xml.dom.DTDDeclNode;
import org.eclipse.lsp4xml.dom.DTDDeclParameter;
import org.eclipse.lsp4xml.dom.DTDElementDecl;
import org.w3c.dom.Node;
Expand All @@ -31,8 +37,8 @@ public class DTDUtils {
* node.
*
* @param originNameNode the origin name node (<ex :<!ATTLIST name).
* @param matchName true if the attribute value must match the value of
* target attribute value and false otherwise.
* @param matchName true if the origin name must match the value of target
* name and false otherwise.
* @param collector collector to collect DTD <!ELEMENT target name.
*/
public static void searchDTDTargetElementDecl(DTDDeclParameter originNameNode, boolean matchName,
Expand All @@ -59,6 +65,108 @@ public static void searchDTDTargetElementDecl(DTDDeclParameter originNameNode, b
}
}

/**
* Search origin DTD node (<!ATTRLIST element-name) or (child <!ELEMENT
* element-name (child)) from the given target DTD node (<!ELEMENT element-name.
*
* @param targetNode the referenced node
* @param collector the collector to collect reference between an origin and
* target attribute.
*/
public static void searchDTDOriginElementDecls(DTDDeclNode targetNode,
BiConsumer<DTDDeclParameter, DTDDeclParameter> collector, CancelChecker cancelChecker) {
// Collect all potential target DTD nodes (all <!ELEMENT)
List<DTDDeclNode> targetNodes = getTargetNodes(targetNode);
if (targetNodes.isEmpty()) {
// None referenced nodes, stop the search of references
return;
}

// Loop for each <!ELEMENT and check for each target DTD nodes if it reference
// it.
DOMDocumentType docType = targetNode.getOwnerDocType();
if (docType.hasChildNodes()) {
// Loop for <!ELEMENT.
NodeList children = docType.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
if (cancelChecker != null) {
cancelChecker.checkCanceled();
}
Node origin = children.item(i);
for (DTDDeclNode target : targetNodes) {
if (target.isDTDElementDecl()) {
// target node is <!ELEMENT, check if it is references by the current origin
// node
DTDElementDecl targetElement = (DTDElementDecl) target;
switch (origin.getNodeType()) {
case DOMNode.DTD_ELEMENT_DECL_NODE:
// check if the current <!ELEMENT origin defines a child which references the
// target node <!ELEMENT
// <!ELEMENT from > --> here target node is 'from'
// <!ELEMENT note(from)> --> here origin node is 'note'
// --> here 'note' has a 'from' as child and it should be collected
DTDElementDecl originElement = (DTDElementDecl) origin;
originElement.collectParameters(targetElement.getNameParameter(), collector);
break;
case DOMNode.DTD_ATT_LIST_NODE:
String name = targetElement.getName();
// check if the current <!ATTLIST element-name reference the current <!ELEMENT
// element-name
// <!ELEMENT note --> here target node is 'note'
// <!ATTLIST note ... -> here origin node is 'note'
// --> here <!ATTLIST defines 'note' as element name, and it should be collected
DTDAttlistDecl originAttribute = (DTDAttlistDecl) origin;
if (name.equals(originAttribute.getElementName())) {
// <!ATTLIST origin has the same name than <!ELEMENT target
collector.accept(originAttribute.getNameParameter(), targetElement.getNameParameter());
}
break;
}
}
}
}
}
}

/**
* Returns the referenced attributes list from the given referenced node.
*
* @param referencedNode the referenced node.
* @return the referenced attributes list from the given referenced node.
*/
private static List<DTDDeclNode> getTargetNodes(DTDDeclNode referencedNode) {
List<DTDDeclNode> referencedNodes = new ArrayList<>();
switch (referencedNode.getNodeType()) {
case DOMNode.DTD_ELEMENT_DECL_NODE:
addTargetNode(referencedNode, referencedNodes);
break;
case Node.DOCUMENT_TYPE_NODE:
DOMDocumentType docType = (DOMDocumentType) referencedNode;
if (docType.hasChildNodes()) {
// Loop for element <!ELEMENT.
NodeList children = docType.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node node = children.item(i);
if (node.getNodeType() == DOMNode.DTD_ELEMENT_DECL_NODE) {
addTargetNode((DTDElementDecl) node, referencedNodes);
}
}
}
break;
}
return referencedNodes;
}

private static void addTargetNode(DTDDeclNode referencedNode, List<DTDDeclNode> referencedNodes) {
if (referencedNode.isDTDElementDecl()) {
// Add only <!ELEMENT which defines a name.
DTDElementDecl elementDecl = (DTDElementDecl) referencedNode;
if (isValid(elementDecl)) {
referencedNodes.add(elementDecl);
}
}
}

/**
* Returns true if the given <!ELEMENT defines a name and false otherwise.
*
Expand Down
Loading