diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_attribute_3CodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_attribute_3CodeAction.java index 009e69205..c32e895e0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_attribute_3CodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_attribute_3CodeAction.java @@ -14,6 +14,8 @@ import java.util.Collection; import java.util.List; +import java.text.Collator; +import java.util.TreeSet; import org.eclipse.lemminx.commons.CodeActionFactory; import org.eclipse.lemminx.dom.DOMAttr; @@ -30,6 +32,8 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; +import static org.eclipse.lemminx.utils.StringUtils.isSimilar; + /** * Code action to fix cvc-attribute-3 error. * @@ -48,6 +52,7 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen String attributeName = attr.getName(); ContentModelManager contentModelManager = componentProvider.getComponent(ContentModelManager.class); Collection cmDocuments = contentModelManager.findCMDocument(element); + String attributeValue = attr.getValue(); for (CMDocument cmDocument : cmDocuments) { CMAttributeDeclaration cmAttribute = cmDocument.findCMAttribute(element, attributeName); if (cmAttribute != null) { @@ -56,14 +61,33 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen diagnosticRange.getStart().getCharacter() + 1), new Position(diagnosticRange.getEnd().getLine(), diagnosticRange.getEnd().getCharacter() - 1)); - cmAttribute.getEnumerationValues().forEach(value -> { - // Replace attribute value - // value = "${1:" + value + "}"; - CodeAction replaceAttrValueAction = CodeActionFactory.replace( - "Replace with '" + value + "'", rangeValue, value, document.getTextDocument(), - diagnostic); - codeActions.add(replaceAttrValueAction); - }); + Collection similarValues = new TreeSet(Collator.getInstance()); + Collection otherValues = new TreeSet(Collator.getInstance()); + + for (String enumValue : cmAttribute.getEnumerationValues()) { + if (isSimilar(enumValue, attributeValue)) { + similarValues.add(enumValue); + } else { + otherValues.add(enumValue); + } + } + if (!similarValues.isEmpty()) { + // Add code actions for each similar value + for (String similarValue : similarValues) { + CodeAction similarCodeAction = CodeActionFactory.replace( + "Did you mean '" + similarValue + "'?", rangeValue, similarValue, document.getTextDocument(), + diagnostic); + codeActions.add(similarCodeAction); + } + } else { + // Add code actions for each possible elements + for (String otherValue : otherValues) { + CodeAction otherCodeAction = CodeActionFactory.replace( + "Replace with '" + otherValue + "'", rangeValue, otherValue, document.getTextDocument(), + diagnostic); + codeActions.add(otherCodeAction); + } + } } } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_complex_type_2_4_aCodeAction.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_complex_type_2_4_aCodeAction.java index 93470d7f0..3ac40abe4 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_complex_type_2_4_aCodeAction.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/codeactions/cvc_complex_type_2_4_aCodeAction.java @@ -27,19 +27,18 @@ import org.eclipse.lemminx.services.extensions.ICodeActionParticipant; import org.eclipse.lemminx.services.extensions.IComponentProvider; import org.eclipse.lemminx.settings.SharedSettings; -import org.eclipse.lemminx.utils.LevenshteinDistance; import org.eclipse.lemminx.utils.XMLPositionUtility; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Range; +import static org.eclipse.lemminx.utils.StringUtils.isSimilar; + /** * 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 codeActions, SharedSettings sharedSettings, IComponentProvider componentProvider) { @@ -86,7 +85,7 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen } if (!similarElementNames.isEmpty()) { - // // Add code actions for each similar elements + // Add code actions for each similar elements for (String elementName : similarElementNames) { CodeAction similarCodeAction = CodeActionFactory.replaceAt( "Did you mean '" + elementName + "'?", elementName, document.getTextDocument(), @@ -112,7 +111,7 @@ public void doCodeAction(Diagnostic diagnostic, Range range, DOMDocument documen /** * 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. @@ -149,9 +148,4 @@ private static Collection getPossibleElements(DOMElement e return possibleElements; } - 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; - } } \ No newline at end of file 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 5cea3620f..e97745b1b 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 @@ -28,6 +28,8 @@ public class StringUtils { public static final String FALSE = "false"; public static final Collection TRUE_FALSE_ARRAY = Arrays.asList(TRUE, FALSE); + private static final float MAX_DISTANCE_DIFF_RATIO = 0.4f; + private StringUtils() { } @@ -65,7 +67,7 @@ public static boolean isWhitespace(String value) { /** * Checks if a string is null or consists of only whitespace characters. - * + * * @param value The string to check * @return true if any of the below hold, and false otherwise: *
    @@ -81,7 +83,7 @@ public static boolean isBlank(String value) { /** * Normalizes the whitespace characters of a given string and applies it to the * given string builder. - * + * * @param str * @return the result of normalize space of the given string. */ @@ -104,7 +106,7 @@ public static void normalizeSpace(String str, StringBuilder b) { /** * Returns the result of normalize space of the given string. - * + * * @param str * @return the result of normalize space of the given string. */ @@ -116,7 +118,7 @@ public static String normalizeSpace(String str) { /** * Returns the start whitespaces of the given line text. - * + * * @param lineText * @return the start whitespaces of the given line text. */ @@ -126,7 +128,7 @@ public static String getStartWhitespaces(String lineText) { /** * Returns the whitespaces from the given range start/end of the given text. - * + * * @param start the range start * @param end the range end * @param text the text @@ -214,10 +216,10 @@ public static String lTrim(String value) { /** * Given a string that is only whitespace, this will return the amount of * newline characters. - * + * * If the newLineCounter becomes > newLineLimit, then the value of newLineLimit * is always returned. - * + * * @param text * @param isWhitespace * @param delimiter @@ -253,7 +255,7 @@ public static int getNumberOfNewLines(String text, boolean isWhitespace, String /** * Given a string will give back a non null string that is either the given * string, or an empty string. - * + * * @param text * @return */ @@ -266,12 +268,12 @@ public static String getDefaultString(String text) { /** * Traverses backwards from the endOffset until it finds a whitespace character. - * + * * The offset of the character after the whitespace is returned. - * + * * (text = "abcd efg|h", endOffset = 8) -> 5 - * - * + * + * * @param text * @param endOffset non-inclusive * @return Start offset directly after the first whitespace. @@ -300,7 +302,7 @@ public static int getOffsetAfterWhitespace(String text, int endOffset) { /** * Returns the number of consecutive whitespace characters in front of text - * + * * @param text String of interest * @return the number of consecutive whitespace characters in front of text */ @@ -320,7 +322,7 @@ public static int getFrontWhitespaceLength(String text) { /** * Returns the number of consecutive whitespace characters from the end of text - * + * * @param text String of interest * @return the number of consecutive whitespace characters from the end of text */ @@ -411,7 +413,7 @@ public static String getString(Object obj) { /** * Returns the start word offset from the left of the given offset * and -1 if no word. - * + * * @param text the text * @param offset the offset * @param isValidChar predicate to check if current character belong to the @@ -434,7 +436,7 @@ public static int findStartWord(String text, int offset, Predicate is /** * Returns the end word offset from the right of the given offset * and -1 if no word. - * + * * @param text the text * @param offset the offset * @param isValidChar predicate to check if current character belong to the @@ -456,10 +458,10 @@ public static int findEndWord(String text, int offset, Predicate isVa /** * Returns value without surrounding quotes. - * + * * If value does not have matching surrounding quotes, * returns value. - * + * * @param value * @return value without surrounding quotes. */ @@ -474,7 +476,7 @@ public static String convertToQuotelessValue(String value) { /** * Returns true if value has matching surrounding quotes * and false otherwise. - * + * * @param value * @return true if value has matching surrounding quotes. */ @@ -490,4 +492,17 @@ public static boolean isQuoted(String value) { char quoteValueEnd = value.charAt(value.length() - 1); return quoteValueEnd == quoteValueStart; } + + /** + * Uses Levenshtein distance to determine similarity between strings + * + * @param reference the string being compared to + * @param current the string compared + * @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()); + LevenshteinDistance levenshteinDistance = new LevenshteinDistance(threshold); + return levenshteinDistance.apply(reference, current) != -1; + } } diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDiagnosticsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDiagnosticsTest.java index fa0344fca..7869fd58d 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDiagnosticsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaDiagnosticsTest.java @@ -638,7 +638,6 @@ public void fuzzyElementNamesWithPrefix() throws Exception { Diagnostic diagnostic = d(6, 5, 6, 16, XMLSchemaErrorCode.cvc_complex_type_2_4_c, "cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'camel:beani'."); testDiagnosticsWithCatalogFor(xml, diagnostic); - testCodeActionsWithCatalogFor(xml, diagnostic, // ca(diagnostic, te(6, 11, 6, 16, "bean"), te(6, 25, 6, 30, "bean")), // ca(diagnostic, te(6, 11, 6, 16, "beanio"), te(6, 25, 6, 30, "beanio"))); @@ -658,6 +657,20 @@ public void fuzzyElementNamesWithPrefixAndNoMatch() throws Exception { ca(diagnostic, te(5, 12, 5, 17, "AElement2"), te(5, 28, 5, 33, "AElement2"))); } + @Test + public void fuzzyElementMemberValueCodeActionTest() throws Exception { + String xml = "\r\n" + // + ""; + Diagnostic diagnostic1 = d(4, 5, 4, 11, XMLSchemaErrorCode.cvc_attribute_3, + "cvc-attribute.3: The value 'larg' of attribute 'size' on element 'dress' is not valid with respect to its type, 'SizeType'."); + testCodeActionsFor(xml, diagnostic1, + ca(diagnostic1, te(4, 6, 4, 10, "large")), ca(diagnostic1, te(4, 6, 4, 10, "x-large"))); + } + + @Test public void cvc_complex_type_2_2_withElement() throws Exception { String xml = "\n" + //