Skip to content

Commit

Permalink
Implement itemDefaults for completionList
Browse files Browse the repository at this point in the history
Signed-off-by: Jessica He <jhe@redhat.com>
  • Loading branch information
JessicaJHee committed Jul 31, 2023
1 parent 2b0fe29 commit 5f03635
Show file tree
Hide file tree
Showing 13 changed files with 548 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@

import static java.lang.Character.isWhitespace;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
Expand Down Expand Up @@ -51,8 +54,11 @@
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemDefaults;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.InsertReplaceEdit;
import org.eclipse.lsp4j.InsertReplaceRange;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.MarkupKind;
import org.eclipse.lsp4j.Position;
Expand Down Expand Up @@ -288,7 +294,83 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha
return completionResponse;
} finally {
collectSnippetSuggestions(completionRequest, completionResponse);
// Manage itemDefaults
CompletionItemDefaults itemDefaults = new CompletionItemDefaults();
if (settings.getCompletionSettings().isCompletionListItemDefaultsSupport("editRange")) {
Range editRange = findMostCommonEditRange(completionResponse);
itemDefaults.setEditRange(Either.forLeft(editRange));
for (CompletionItem item : completionResponse.getItems()) {
if (item.getTextEdit().isLeft() && item.getTextEdit().getLeft().getRange().equals(editRange)) {
item.setTextEditText(item.getTextEdit().getLeft().getNewText());
item.setTextEdit(null);
}
}
completionResponse.setItemDefaults(itemDefaults);
}
if (settings.getCompletionSettings().isCompletionListItemDefaultsSupport("insertTextFormat")) {
itemDefaults.setInsertTextFormat(findMostCommonInsertTextFormat(completionResponse));
for (CompletionItem item : completionResponse.getItems()) {
if (item.getInsertTextFormat() == itemDefaults.getInsertTextFormat()) {
item.setInsertTextFormat(null);
}
}
completionResponse.setItemDefaults(itemDefaults);
}
}
}

/**
* Returns the most common editRange in the completion list.
*
* @param completionResponse the completion response
* @return the most common editRange in the completion list
*/
private Range findMostCommonEditRange(CompletionResponse completionResponse) {
Map<Range, Integer> countMapEditRange = new HashMap<>();

for (CompletionItem item : completionResponse.getItems()) {
if (item.getTextEdit().getLeft() != null) {
countMapEditRange.put(item.getTextEdit().getLeft().getRange(),
countMapEditRange.getOrDefault(item, 0) + 1);
}
}

Range mostCommonRange = null;
int maxCount = 0;
for (Map.Entry<Range, Integer> entry : countMapEditRange.entrySet()) {
if (entry.getValue() > maxCount) {
mostCommonRange = entry.getKey();
maxCount = entry.getValue();
}
}
return mostCommonRange;
}

/**
* Returns the most common insertTextFormat in the completion list.
*
* @param completionResponse the completion response
* @return the most common insertTextFormat in the completion list
*/
private InsertTextFormat findMostCommonInsertTextFormat(CompletionResponse completionResponse) {
Map<InsertTextFormat, Integer> countMapInsertTextFormat = new HashMap<>();

for (CompletionItem item : completionResponse.getItems()) {
if (item.getInsertTextFormat() != null) {
countMapInsertTextFormat.put(item.getInsertTextFormat(),
countMapInsertTextFormat.getOrDefault(item, 0) + 1);
}
}

InsertTextFormat mostCommonInsertTextFormat = null;
int maxCount = 0;
for (Map.Entry<InsertTextFormat, Integer> entry : countMapInsertTextFormat.entrySet()) {
if (entry.getValue() > maxCount) {
mostCommonInsertTextFormat = entry.getKey();
maxCount = entry.getValue();
}
}
return mostCommonInsertTextFormat;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ public boolean isCompletionResolveSupported(CompletionResolveSupportProperty pro
.contains(property.name());
}

/**
* Returns <code>true</code> if the client support completion list itemDefaults given the field and
* <code>false</code> otherwise.
*
* @param field the completionList itemDefaults field
*
* @return <code>true</code> if the client support completion list itemDefaults given the field and
* <code>false</code> otherwise.
*/
public boolean isCompletionListItemDefaultsSupport(String field) {
return completionCapabilities != null && completionCapabilities.getCompletionList() != null
&& completionCapabilities.getCompletionList().getItemDefaults() != null
&& completionCapabilities.getCompletionList().getItemDefaults().contains(field);
}

/**
* Merge only the given completion settings (and not the capability) in the
* settings.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
import org.eclipse.lsp4j.CompletionItemCapabilities;
import org.eclipse.lsp4j.CompletionItemResolveSupportCapabilities;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.CompletionListCapabilities;
import org.eclipse.lsp4j.CreateFile;
import org.eclipse.lsp4j.CreateFileOptions;
import org.eclipse.lsp4j.Diagnostic;
Expand Down Expand Up @@ -180,6 +181,14 @@ public static CompletionList testCompletionFor(String value, int expectedCount,
return testCompletionFor(value, null, null, expectedCount, expectedItems);
}

public static CompletionList testCompletionFor(String value, Integer expectedCount, boolean enableItemDefaults,
CompletionItem... expectedItems)
throws BadLocationException {
SharedSettings settings = new SharedSettings();
return testCompletionFor(new XMLLanguageService(), value, null, null, null, expectedCount, settings,
enableItemDefaults, expectedItems);
}

public static CompletionList testCompletionFor(String value, String catalogPath, String fileURI,
Integer expectedCount,
CompletionItem... expectedItems) throws BadLocationException {
Expand All @@ -193,21 +202,49 @@ public static CompletionList testCompletionFor(String value, boolean autoCloseTa
}

public static CompletionList testCompletionFor(XMLLanguageService xmlLanguageService, String value,
String catalogPath,
Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
String catalogPath, Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
boolean autoCloseTags, CompletionItem... expectedItems) throws BadLocationException {
return testCompletionFor(xmlLanguageService, value,
catalogPath, customConfiguration, fileURI, expectedCount,
autoCloseTags, false, expectedItems);
}

public static CompletionList testCompletionFor(XMLLanguageService xmlLanguageService, String value,
String catalogPath, Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
boolean autoCloseTags, boolean enableItemDefaults, CompletionItem... expectedItems)
throws BadLocationException {

SharedSettings settings = new SharedSettings();
settings.getCompletionSettings().setAutoCloseTags(autoCloseTags);
return testCompletionFor(xmlLanguageService, value, catalogPath, customConfiguration, fileURI, expectedCount,
settings,
expectedItems);
settings, enableItemDefaults, expectedItems);
}

public static CompletionList testCompletionFor(XMLLanguageService xmlLanguageService, String value,
String catalogPath, Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
SharedSettings sharedSettings, CompletionItem... expectedItems) throws BadLocationException {
return testCompletionFor(xmlLanguageService, value, catalogPath, customConfiguration, fileURI, expectedCount,
sharedSettings, false, expectedItems);
}

public static CompletionList testCompletionFor(XMLLanguageService xmlLanguageService, String value,
String catalogPath,
Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
SharedSettings sharedSettings, CompletionItem... expectedItems) throws BadLocationException {
SharedSettings sharedSettings, boolean enableItemDefaults, CompletionItem... expectedItems)
throws BadLocationException {
if (enableItemDefaults) {
if (sharedSettings.getCompletionSettings().getCompletionCapabilities() == null) {
CompletionCapabilities completionCapabilities = new CompletionCapabilities();
sharedSettings.getCompletionSettings().setCapabilities(completionCapabilities);
}
if (sharedSettings.getCompletionSettings().getCompletionCapabilities().getCompletionList() == null) {
CompletionListCapabilities completionListCapabilities = new CompletionListCapabilities();
sharedSettings.getCompletionSettings().getCompletionCapabilities()
.setCompletionList(completionListCapabilities);
}
sharedSettings.getCompletionSettings().getCompletionCapabilities().getCompletionList()
.setItemDefaults(Arrays.asList("insertTextFormat", "editRange"));
}
int offset = value.indexOf('|');
value = value.substring(0, offset) + value.substring(offset + 1);

Expand Down Expand Up @@ -248,13 +285,18 @@ public static CompletionList testCompletionFor(XMLLanguageService xmlLanguageSer
}
if (expectedItems != null) {
for (CompletionItem item : expectedItems) {
assertCompletion(list, item, expectedCount);
assertCompletion(list, item, enableItemDefaults, expectedCount);
}
}
return list;
}

public static void assertCompletion(CompletionList completions, CompletionItem expected, Integer expectedCount) {
assertCompletion(completions, expected, false, expectedCount);
}

public static void assertCompletion(CompletionList completions, CompletionItem expected, boolean enableItemDefaults,
Integer expectedCount) {
List<CompletionItem> matches = completions.getItems().stream().filter(completion -> {
return expected.getLabel().equals(completion.getLabel());
}).collect(Collectors.toList());
Expand All @@ -271,8 +313,8 @@ public static void assertCompletion(CompletionList completions, CompletionItem e
});
}

CompletionItem match = getCompletionMatch(matches, expected);
if (expected.getTextEdit() != null && match.getTextEdit() != null) {
CompletionItem match = getCompletionMatch(matches, enableItemDefaults, expected);
if (!enableItemDefaults && expected.getTextEdit() != null && match.getTextEdit() != null) {
if (expected.getTextEdit().getLeft().getNewText() != null) {
assertEquals(expected.getTextEdit().getLeft().getNewText(), match.getTextEdit().getLeft().getNewText());
}
Expand All @@ -290,6 +332,17 @@ public static void assertCompletion(CompletionList completions, CompletionItem e
assertArrayEquals(expected.getAdditionalTextEdits().toArray(),
matchedAdditionalTextEdits.toArray());
}
} else {
assertNull(match.getTextEdit());
assertNull(match.getInsertTextFormat());
if (match.getTextEditText() != null) {
assertEquals(expected.getTextEdit().getLeft().getNewText(), match.getTextEditText());
}
Range r = expected.getTextEdit().getLeft().getRange();
if (r != null && r.getStart() != null && r.getEnd() != null) {
assertEquals(expected.getTextEdit().getLeft().getRange(),
completions.getItemDefaults().getEditRange().getLeft());
}
}
if (expected.getFilterText() != null && match.getFilterText() != null) {
assertEquals(expected.getFilterText(), match.getFilterText());
Expand Down Expand Up @@ -330,8 +383,16 @@ public static void assertAdditionalTextEdit(List<TextEdit> matches, TextEdit exp
}

private static CompletionItem getCompletionMatch(List<CompletionItem> matches, CompletionItem expected) {
return getCompletionMatch(matches, false, expected);
}

private static CompletionItem getCompletionMatch(List<CompletionItem> matches, boolean enableItemDefaults,
CompletionItem expected) {
for (CompletionItem item : matches) {
if (expected.getTextEdit().getLeft().getNewText().equals(item.getTextEdit().getLeft().getNewText())) {
if (!enableItemDefaults && expected.getTextEdit().getLeft().getNewText()
.equals(item.getTextEdit().getLeft().getNewText())) {
return item;
} else if (expected.getTextEdit().getLeft().getNewText().equals(item.getTextEditText())) {
return item;
}
}
Expand Down Expand Up @@ -912,15 +973,19 @@ public static List<CodeAction> testCodeActionsFor(String xml, Diagnostic diagnos
public static List<CodeAction> testCodeActionsFor(String xml, Diagnostic diagnostic, String catalogPath,
SharedSettings sharedSettings, XMLLanguageService xmlLanguageService, CodeAction... expected)
throws BadLocationException {
return testCodeActionsFor(xml, diagnostic, null, catalogPath, null, sharedSettings, xmlLanguageService, -1, expected);
return testCodeActionsFor(xml, diagnostic, null, catalogPath, null, sharedSettings, xmlLanguageService, -1,
expected);
}

public static List<CodeAction> testCodeActionsFor(String xml, Range range, String catalogPath,
SharedSettings sharedSettings, XMLLanguageService xmlLanguageService, CodeAction... expected)
throws BadLocationException {
return testCodeActionsFor(xml, null, range, catalogPath, null, sharedSettings, xmlLanguageService, -1, expected);
return testCodeActionsFor(xml, null, range, catalogPath, null, sharedSettings, xmlLanguageService, -1,
expected);
}
public static List<CodeAction> testCodeActionsFor(String xml, Diagnostic diagnostic, Range range, String catalogPath,

public static List<CodeAction> testCodeActionsFor(String xml, Diagnostic diagnostic, Range range,
String catalogPath,
String fileURI, SharedSettings sharedSettings, XMLLanguageService xmlLanguageService, int index,
CodeAction... expected) throws BadLocationException {
int offset = xml.indexOf('|');
Expand All @@ -936,7 +1001,7 @@ public static List<CodeAction> testCodeActionsFor(String xml, Diagnostic diagnos
} else if (range == null && diagnostic != null) {
range = diagnostic.getRange();
}

// Otherwise, range is to be specified in parameters
assertNotNull(range, "Range cannot be null");

Expand Down Expand Up @@ -1124,7 +1189,7 @@ public static Either<TextDocumentEdit, ResourceOperation> teOp(String uri, int s
}

public static Either<TextDocumentEdit, ResourceOperation> teOp(String uri, TextEdit... te) {
return Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(uri, 0), Arrays.asList(te)));
return Either.forLeft(new TextDocumentEdit(new VersionedTextDocumentIdentifier(uri, 0), Arrays.asList(te)));
}

// ------------------- Hover assert
Expand Down Expand Up @@ -1791,8 +1856,8 @@ public static void assertRename(XMLLanguageService languageService, String value
.stream().filter(Either::isLeft)
.filter(e -> uri.equals(e.getLeft().getTextDocument().getUri()))
.map(Either::getLeft).findFirst();
List<TextEdit> actualEdits = documentChange.isPresent() ?
documentChange.get().getEdits() : Collections.emptyList();
List<TextEdit> actualEdits = documentChange.isPresent() ? documentChange.get().getEdits()
: Collections.emptyList();
assertArrayEquals(expectedEdits.toArray(), actualEdits.toArray());
}

Expand Down Expand Up @@ -1836,7 +1901,7 @@ public static void assertLinkedEditing(LinkedEditingRanges actual, LinkedEditing
}

public static LinkedEditingRanges le(Range... ranges) {
return new LinkedEditingRanges(Arrays.asList(ranges), "[^\\s>]+");
return new LinkedEditingRanges(Arrays.asList(ranges), "[^\\s>]+");
}

public static LinkedEditingRanges le(String wordPattern, Range... ranges) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,31 @@ public void externalDTDCompletionAllDecls() throws BadLocationException {
te(3, 1, 3, 1, "<!ENTITY ${1:entity-name} SYSTEM \"${2:entity-value}\">"), "<!ENTITY"));
}

@Test
public void externalDTDCompletionAllDeclsItemDefaults() throws BadLocationException {
// completion on <|
String xml = "<?xml version = \"1.0\"?>\r\n" + //
"<!DOCTYPE Folks [\r\n" + //
" <!ELEMENT Folks (Person*)>\r\n" + //
" |\r\n" + //
"]>\r\n" + //
"<Folks>\r\n" + //
" " + //
"</Folks>";
testCompletionFor(xml, true, true, //
DTDNODE_SNIPPETS /* DTD node snippets */ + //
COMMENT_SNIPPETS /* Comment snippets */ , "catalog.xml", //
c("Insert DTD Element Declaration", te(3, 1, 3, 1, "<!ELEMENT ${1:element-name} (${2:#PCDATA})>"),
"<!ELEMENT"),
c("Insert Internal DTD Entity Declaration",
te(3, 1, 3, 1, "<!ENTITY ${1:entity-name} \"${2:entity-value}\">"), "<!ENTITY"),
c("Insert DTD Attributes List Declaration",
te(3, 1, 3, 1, "<!ATTLIST ${1:element-name} ${2:attribute-name} ${3:ID} ${4:#REQUIRED}>"),
"<!ATTLIST"),
c("Insert External DTD Entity Declaration",
te(3, 1, 3, 1, "<!ENTITY ${1:entity-name} SYSTEM \"${2:entity-value}\">"), "<!ENTITY"));
}

@Test
public void externalDTDCompletionAllDeclsSnippetsNotSupported() throws BadLocationException {
// completion on <|
Expand Down Expand Up @@ -270,6 +295,12 @@ private void testCompletionFor(String xml, boolean isSnippetsSupported, Integer

private void testCompletionFor(String xml, boolean isSnippetsSupported, Integer expectedCount, String catalog,
CompletionItem... expectedItems) throws BadLocationException {
testCompletionFor(xml, isSnippetsSupported, false, expectedCount, catalog, expectedItems);
}

private void testCompletionFor(String xml, boolean isSnippetsSupported, boolean enableItemDefaults,
Integer expectedCount, String catalog,
CompletionItem... expectedItems) throws BadLocationException {
CompletionCapabilities completionCapabilities = new CompletionCapabilities();
CompletionItemCapabilities completionItem = new CompletionItemCapabilities(isSnippetsSupported); // activate
// snippets
Expand All @@ -278,6 +309,6 @@ private void testCompletionFor(String xml, boolean isSnippetsSupported, Integer
SharedSettings sharedSettings = new SharedSettings();
sharedSettings.getCompletionSettings().setCapabilities(completionCapabilities);
XMLAssert.testCompletionFor(new XMLLanguageService(), xml, "src/test/resources/catalogs/" + catalog, null, null,
expectedCount, sharedSettings, expectedItems);
expectedCount, sharedSettings, enableItemDefaults, expectedItems);
}
}
Loading

0 comments on commit 5f03635

Please sign in to comment.