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

Implement itemDefaults for completionList #1567

Merged
merged 1 commit into from
Aug 2, 2023
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 @@ -23,7 +23,7 @@
* Completion response implementation.
*
*/
class CompletionResponse extends CompletionList implements ICompletionResponse {
public class CompletionResponse extends CompletionList implements ICompletionResponse {

private transient List<String> seenAttributes;
private transient boolean hasSomeItemFromGrammar;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.eclipse.lemminx.services.snippets.IXMLSnippetContext;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lemminx.settings.XMLCompletionSettings;
import org.eclipse.lemminx.utils.CompletionItemDefaultsUtils;
import org.eclipse.lemminx.utils.StringUtils;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.CompletionItem;
Expand Down Expand Up @@ -288,6 +289,8 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, Sha
return completionResponse;
} finally {
collectSnippetSuggestions(completionRequest, completionResponse);
// Manage itemDefaults
CompletionItemDefaultsUtils.process(completionResponse, settings);
}
}

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
@@ -0,0 +1,116 @@
/*******************************************************************************
* Copyright (c) 2023 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
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.lemminx.utils;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.eclipse.lemminx.services.CompletionResponse;
import org.eclipse.lemminx.settings.SharedSettings;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemDefaults;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.messages.Either;

/**
* CompletionItemDefaultsUtils for implementing itemDefaults in completion list
*/
public class CompletionItemDefaultsUtils {

private static final String ITEM_DEFAULTS_EDIT_RANGE = "editRange";

private static final String ITEM_DEFAULTS_INSERT_TEXT_FORMAT = "insertTextFormat";

/**
* Processes the completion list and manages item defaults.
*
* @param completionResponse the completion response
* @param sharedSettings the shared settings
*/
public static void process(CompletionResponse completionResponse, SharedSettings sharedSettings) {
CompletionItemDefaults itemDefaults = new CompletionItemDefaults();
List<CompletionItem> completionList = completionResponse.getItems();
if (sharedSettings.getCompletionSettings().isCompletionListItemDefaultsSupport(ITEM_DEFAULTS_EDIT_RANGE)) {
setToMostCommonEditRange(completionList, itemDefaults);
completionResponse.setItemDefaults(itemDefaults);
}
if (sharedSettings.getCompletionSettings()
.isCompletionListItemDefaultsSupport(ITEM_DEFAULTS_INSERT_TEXT_FORMAT)) {
setToMostCommonInsertTextFormat(completionList, itemDefaults);
completionResponse.setItemDefaults(itemDefaults);
}
}

/**
* Sets the most common editRange in the completion list in item
* defaults and processes the completion list.
*
* @param completionList the completion list
* @param itemDefaults the item defaults
*/
private static void setToMostCommonEditRange(List<CompletionItem> completionList,
CompletionItemDefaults itemDefaults) {
Map<Range, List<CompletionItem>> itemsByRange = completionList.stream()
.collect(Collectors.groupingBy(item -> item.getTextEdit().getLeft().getRange()));
int maxCount = 0;
Range mostCommonRange = null;
for (Map.Entry<Range, List<CompletionItem>> entry : itemsByRange.entrySet()) {
int currentSize = entry.getValue().size();
if (currentSize > maxCount) {
maxCount = currentSize;
mostCommonRange = entry.getKey();
}
}
itemsByRange.get(mostCommonRange).forEach(item -> {
item.setTextEditText(item.getTextEdit().getLeft().getNewText());
item.setTextEdit(null);
});
itemDefaults.setEditRange(Either.forLeft(mostCommonRange));
completionList = itemsByRange.values().stream().flatMap(Collection::stream)
.collect(Collectors.toList());
}

/**
* Sets the most common InsertTextFormat in the completion list in item
* defaults and processes the completion list.
*
* @param completionList the completion list
* @param itemDefaults the item defaults
*/
private static void setToMostCommonInsertTextFormat(List<CompletionItem> completionList,
CompletionItemDefaults itemDefaults) {
Map<InsertTextFormat, List<CompletionItem>> itemsByInsertTextFormat = completionList.stream()
.filter(item -> item.getInsertTextFormat() != null)
.collect(Collectors.groupingBy(item -> item.getInsertTextFormat()));
int maxCount = 0;
InsertTextFormat mostCommonInsertTextFormat = null;
for (Map.Entry<InsertTextFormat, List<CompletionItem>> entry : itemsByInsertTextFormat.entrySet()) {
int currentSize = entry.getValue().size();
if (currentSize > maxCount) {
maxCount = currentSize;
mostCommonInsertTextFormat = entry.getKey();
}
}
if (mostCommonInsertTextFormat == null) {
return;
}
itemsByInsertTextFormat.get(mostCommonInsertTextFormat).forEach(item -> {
item.setInsertTextFormat(null);
});
itemDefaults.setInsertTextFormat(mostCommonInsertTextFormat);
completionList = itemsByInsertTextFormat.values().stream().flatMap(Collection::stream)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,14 @@
"parameterTypes": []
}]
},
{
"name": "org.eclipse.lsp4j.CompletionItemDefaults",
"allDeclaredFields": true,
"methods": [{
"name": "<init>",
"parameterTypes": []
}]
},
{
"name": "org.eclipse.lsp4j.CompletionItemKind",
"fields": [{
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
Loading