Skip to content

Commit

Permalink
Fuzzy Element name codeaction
Browse files Browse the repository at this point in the history
Handles regular element names and ones with prefixes

Fixes #589

Refer to #589 for code to test

Signed-off-by: Nikolas Komonen <[email protected]>
  • Loading branch information
NikolasKomonen authored and angelozerr committed Nov 22, 2019
1 parent bc9f68b commit 7e49c1c
Show file tree
Hide file tree
Showing 15 changed files with 775 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
*/
package org.eclipse.lsp4xml.commons;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
Expand Down Expand Up @@ -85,4 +88,24 @@ public static CodeAction replace(String title, Range range, String replaceText,
insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}

public static CodeAction replaceAt(String title, String replaceText, TextDocumentItem document,
Diagnostic diagnostic, Collection<Range> ranges) {
CodeAction insertContentAction = new CodeAction(title);
insertContentAction.setKind(CodeActionKind.QuickFix);
insertContentAction.setDiagnostics(Arrays.asList(diagnostic));

VersionedTextDocumentIdentifier versionedTextDocumentIdentifier = new VersionedTextDocumentIdentifier(
document.getUri(), document.getVersion());
ArrayList<TextEdit> edits = new ArrayList<TextEdit>();
for (Range range : ranges) {
TextEdit edit = new TextEdit(range, replaceText);
edits.add(edit);
}
TextDocumentEdit textDocumentEdit = new TextDocumentEdit(versionedTextDocumentIdentifier, edits);
WorkspaceEdit workspaceEdit = new WorkspaceEdit(Collections.singletonList(Either.forLeft(textDocumentEdit)));

insertContentAction.setEdit(workspaceEdit);
return insertContentAction;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
case MSG_REQUIRED_ATTRIBUTE_NOT_SPECIFIED:
case MSG_ELEMENT_NOT_DECLARED:
case MSG_CONTENT_INVALID: {
return XMLPositionUtility.selectStartTag(offset, document);
return XMLPositionUtility.selectStartTagName(offset, document);
}
case MSG_ATTRIBUTE_NOT_DECLARED: {
return XMLPositionUtility.selectAttributeValueAt(getString(arguments[1]), offset, document);
Expand All @@ -134,7 +134,7 @@ public static Range toLSPRange(XMLLocator location, DTDErrorCode code, Object[]
case MSG_ELEMENT_WITH_ID_REQUIRED: {
DOMElement element = document.getDocumentElement();
if (element != null) {
return XMLPositionUtility.selectStartTag(element);
return XMLPositionUtility.selectStartTagName(element);
}
}
case IDREFSInvalid:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_attribute_3CodeAction;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_complex_type_2_1CodeAction;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_complex_type_2_3CodeAction;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_complex_type_2_4_aCodeAction;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_complex_type_3_2_2CodeAction;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_complex_type_4CodeAction;
import org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions.cvc_enumeration_validCodeAction;
Expand Down Expand Up @@ -134,7 +135,7 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
case cvc_complex_type_4:
case src_element_3:
case TargetNamespace_2:
return XMLPositionUtility.selectStartTag(offset, document);
return XMLPositionUtility.selectStartTagName(offset, document);
case cvc_complex_type_3_2_2: {
String attrName = getString(arguments[1]);
return XMLPositionUtility.selectAttributeNameFromGivenNameAt(attrName, offset, document);
Expand Down Expand Up @@ -237,13 +238,15 @@ public static Range toLSPRange(XMLLocator location, XMLSchemaErrorCode code, Obj
}
}
case cvc_type_3_1_2:
return XMLPositionUtility.selectStartTag(offset, document);
return XMLPositionUtility.selectStartTagName(offset, document);
default:
}
return null;
}

public static void registerCodeActionParticipants(Map<String, ICodeActionParticipant> codeActions) {
codeActions.put(cvc_complex_type_2_4_a.getCode(), new cvc_complex_type_2_4_aCodeAction());
codeActions.put(cvc_complex_type_2_4_c.getCode(), new cvc_complex_type_2_4_aCodeAction());
codeActions.put(cvc_complex_type_2_3.getCode(), new cvc_complex_type_2_3CodeAction());
codeActions.put(cvc_complex_type_4.getCode(), new cvc_complex_type_4CodeAction());
codeActions.put(cvc_type_3_1_1.getCode(), new cvc_type_3_1_1CodeAction());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
case ElementPrefixUnbound:
case ElementUnterminated:
case RootElementTypeMustMatchDoctypedecl:
return XMLPositionUtility.selectStartTag(offset, document);
return XMLPositionUtility.selectStartTagName(offset, document);
case EqRequiredInAttribute: {
String attrName = getString(arguments[1]);
return XMLPositionUtility.selectAttributeNameFromGivenNameAt(attrName, offset, document);
Expand Down Expand Up @@ -157,7 +157,7 @@ public static Range toLSPRange(XMLLocator location, XMLSyntaxErrorCode code, Obj
*/
return XMLPositionUtility.selectPreviousNodesEndTag(offset, document);
case CustomETag:
return XMLPositionUtility.selectEndTag(offset, document);
return XMLPositionUtility.selectEndTagName(offset, document);
case ETagRequired: {
String tag = getString(arguments[0]);
return XMLPositionUtility.selectChildEndTag(tag, offset, document);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*******************************************************************************
* 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/l-v20.html
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lsp4xml.extensions.contentmodel.participants.codeactions;

import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;

import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4xml.commons.CodeActionFactory;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lsp4xml.services.extensions.ICodeActionParticipant;
import org.eclipse.lsp4xml.services.extensions.IComponentProvider;
import org.eclipse.lsp4xml.settings.XMLFormattingOptions;
import org.eclipse.lsp4xml.utils.LevenshteinDistance;
import org.eclipse.lsp4xml.utils.XMLPositionUtility;

/**
* cvc_complex_type_2_4_a
*/
public class cvc_complex_type_2_4_aCodeAction implements ICodeActionParticipant {

private static final float MAX_DISTANCE_DIFF_RATIO = 0.4f;

@Override
public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument document, List<CodeAction> codeActions,
XMLFormattingOptions formattingSettings, IComponentProvider componentProvider) {
try {
int offset = document.offsetAt(diagnostic.getRange().getStart());
DOMNode node = document.findNodeAt(offset);
if (node != null && node.isElement()) {
// Get element from the diagnostic
DOMElement element = (DOMElement) node;
String localName = element.getLocalName();

Collection<CMElementDeclaration> possibleElements = getPossibleElements(element, componentProvider);
if (possibleElements != null) {

// When added to these collections, the names will be ordered alphabetically
Collection<String> otherElementNames = new TreeSet<String>(Collator.getInstance());
Collection<String> similarElementNames = new TreeSet<String>(Collator.getInstance());

// Try to collect similar names coming from tag name
for (CMElementDeclaration possibleElement : possibleElements) {
String possibleElementName = possibleElement.getName();
if (isSimilar(possibleElementName, localName)) {
similarElementNames.add(possibleElementName);
} else {
otherElementNames.add(possibleElementName);
}
}

// Create ranges for the replace.
boolean selectLocalNameOnly = element.getPrefix() != null;
List<Range> ranges = new ArrayList<>();
Range startRange, endRange;
if(selectLocalNameOnly) {
startRange = XMLPositionUtility.selectStartTagLocalName(element);
endRange = XMLPositionUtility.selectEndTagLocalName(element);
}
else {
startRange = XMLPositionUtility.selectStartTagName(element);
endRange = XMLPositionUtility.selectEndTagName(element);
}
ranges.add(startRange);

if (endRange != null) {
ranges.add(endRange);
}

if (!similarElementNames.isEmpty()) {
// // Add code actions for each similar elements
for (String elementName : similarElementNames) {
CodeAction similarCodeAction = CodeActionFactory.replaceAt(
"Did you mean '" + elementName + "'?", elementName, document.getTextDocument(),
diagnostic, ranges);
codeActions.add(similarCodeAction);
}
} else {
// Add code actions for each possible elements
for (String elementName : otherElementNames) {
CodeAction otherCodeAction = CodeActionFactory.replaceAt(
"Replace with '" + elementName + "'", elementName, document.getTextDocument(),
diagnostic, ranges);
codeActions.add(otherCodeAction);
}
}
}
}

} catch (Exception e) {
// Do nothing
}
}

/**
* Returns the possible elements for the given DOM element.
*
* @param element the DOM element
* @param componentProvider the component provider
* @return the possible elements for the given DOM element.
* @throws Exception
*/
private static Collection<CMElementDeclaration> getPossibleElements(DOMElement element,
IComponentProvider componentProvider) throws Exception {
ContentModelManager contentModelManager = componentProvider.getComponent(ContentModelManager.class);

String prefix = element.getPrefix();
DOMElement parentElement = element.getParentElement();
String parentPrefix = parentElement.getPrefix();
// check if prefix is the same than the parent profix
if (prefix != null && !prefix.equals(parentPrefix)) {
// We are in the case
// <b:bean><camel:beani

// returns the all element for the camel XML Schema.
String namespaceURI = element.getNamespaceURI();
CMDocument cmDocument = contentModelManager.findCMDocument(parentElement, namespaceURI);
return cmDocument != null ? cmDocument.getElements() : null;
}

CMElementDeclaration cmElement = contentModelManager.findCMElement(parentElement);
if (cmElement != null) {
// Collect all possible elements from the parent element upon the offset start
// of the element
return cmElement.getPossibleElements(parentElement, element.getStart());
}
return null;
}

private static boolean isSimilar(String reference, String current) {
int threshold = Math.round(MAX_DISTANCE_DIFF_RATIO * reference.length());
LevenshteinDistance levenshteinDistance = new LevenshteinDistance(threshold);
return levenshteinDistance.apply(reference, current) != -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ private static void warnNoGrammar(DOMDocument document, List<Diagnostic> diagnos
Range range = null;
DOMElement documentElement = document.getDocumentElement();
if (documentElement != null) {
range = XMLPositionUtility.selectStartTag(documentElement);
range = XMLPositionUtility.selectStartTagName(documentElement);
}
if (range == null) {
range = new Range(new Position(0, 0), new Position(0, 0));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public static Range toLSPRange(XMLLocator location, XSDErrorCode code, Object[]
List<DOMNode> children = parent.getChildrenWithAttributeValue("name", nameValue);

if (children.isEmpty()) {
return XMLPositionUtility.selectStartTag(offset, document);
return XMLPositionUtility.selectStartTagName(offset, document);
}

offset = children.get(0).getStart() + 1;
Expand All @@ -128,7 +128,7 @@ public static Range toLSPRange(XMLLocator location, XSDErrorCode code, Object[]
case src_element_2_1:
case src_element_3:
case src_import_1_2:
return XMLPositionUtility.selectStartTag(offset, document);
return XMLPositionUtility.selectStartTagName(offset, document);
case s4s_att_not_allowed: {
String attrName = getString(arguments[1]);
return XMLPositionUtility.selectAttributeNameFromGivenNameAt(attrName, offset, document);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ private static void findStartEndTagDefinition(IDefinitionRequest request, List<L
if (element.hasStartTag() && element.hasEndTag()) {
// The DOM element has end and start tag
DOMDocument document = element.getOwnerDocument();
Range startRange = XMLPositionUtility.selectStartTag(element);
Range endRange = XMLPositionUtility.selectEndTag(element);
Range startRange = XMLPositionUtility.selectStartTagName(element);
Range endRange = XMLPositionUtility.selectEndTagName(element);
int offset = request.getOffset();
if (element.isInStartTag(offset)) {
// Start tag was clicked, jump to the end tag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ private static void publishOneDiagnosticInRoot(DOMDocument document, String mess
Consumer<PublishDiagnosticsParams> publishDiagnostics) {
String uri = document.getDocumentURI();
DOMElement documentElement = document.getDocumentElement();
Range range = XMLPositionUtility.selectStartTag(documentElement);
Range range = XMLPositionUtility.selectStartTagName(documentElement);
List<Diagnostic> diagnostics = new ArrayList<>();
diagnostics.add(new Diagnostic(range, message, severity, "XML"));
publishDiagnostics.accept(new PublishDiagnosticsParams(uri, diagnostics));
Expand Down
Loading

0 comments on commit 7e49c1c

Please sign in to comment.