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

Multi value property fix #343

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*******************************************************************************/
package io.openliberty.tools.langserver.codeactions;

import com.google.gson.JsonPrimitive;
import io.openliberty.tools.langserver.LibertyLanguageServer;
import io.openliberty.tools.langserver.LibertyTextDocumentService;
import io.openliberty.tools.langserver.ls.LibertyTextDocument;
Expand All @@ -32,6 +33,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

public abstract class CodeActionQuickfixFactory {

Expand All @@ -46,6 +48,22 @@ protected CodeActionQuickfixFactory(LibertyTextDocumentService libertyTextDocume
/**
* returns list of code actions or commands.
* called from CodeActionParticipant
* 1. In case of quick fix for single value property
* a. show all allowed values in code action
* 2. Multi value property,
* a. If field has multiple values specified
* b. If any of the value is valid, show code action
* "Replace with {validValues}"
* "Replace with {validValues},${nextAllowedValue1}"
* ...
* "Replace with {validValues},${nextAllowedValueN}"
* example, user entered,WLP_LOGGING_CONSOLE_SOURCE=abc,audit,message,kyc
* quickfix should contain something like
* Replace with "audit,message
* Replace with "audit,message,trace"
* Replace with "audit,message,ffdc"
* Replace with "audit,message,auditLog"
*
* @param params code action params
* @return codeaction
*/
Expand All @@ -57,9 +75,19 @@ public List<Either<Command, CodeAction>> apply(CodeActionParams params) {
if (diagnostic.getCode() != null && getErrorCode().equals(diagnostic.getCode().getLeft())) {
String line = new ParserFileHelperUtil().getLine(new LibertyTextDocument(openedDocument), diagnostic.getRange().getStart().getLine());
if (line != null) {
String prefix = getUserEnteredValidValues(diagnostic);
if (!Objects.equals(prefix, "")) {
// add a code action to just replace with valid values
// present in current string
res.add(Either.forRight(createCodeAction(params, diagnostic, prefix)));
// append a comma so that completion will show all values
// for multi value
prefix += ",";
}
// fetch all completion values and shows them as quick fix
// prefix will contain all valid values in current entered string, or else ""
// completion returns empty list if no completion item is present
List<String> possibleProperties = retrieveCompletionValues(openedDocument, diagnostic.getRange().getStart());
List<String> possibleProperties = retrieveCompletionValues(openedDocument, diagnostic.getRange().getStart(), prefix);
for (String mostProbableProperty : possibleProperties) {
// expected format for a code action is <Command,CodeAction>
res.add(Either.forRight(createCodeAction(params, diagnostic, mostProbableProperty)));
Expand All @@ -70,6 +98,26 @@ public List<Either<Command, CodeAction>> apply(CodeActionParams params) {
return res;
}

/**
* get valid values entered by user in the current line
* in case of multi value property, user may have entered some valid and some invalid values
* @param diagnostic
* @return
*/
private static String getUserEnteredValidValues(Diagnostic diagnostic) {
String prefix = "";
// user entered valid values are passed in diagnostic.setData()
if (diagnostic.getData() != null) {
if (diagnostic.getData() instanceof JsonPrimitive) {
prefix = ((JsonPrimitive) diagnostic.getData()).getAsString();
}
if (diagnostic.getData() instanceof String) {
prefix = (String) diagnostic.getData();
}
}
return prefix;
}

/**
* used to create code action object for quickfix
* @param params codeaction params
Expand All @@ -88,7 +136,7 @@ protected CodeAction createCodeAction(CodeActionParams params, Diagnostic diagno
return codeAction;
}

protected abstract List<String> retrieveCompletionValues(TextDocumentItem textDocumentItem, Position position);
protected abstract List<String> retrieveCompletionValues(TextDocumentItem textDocumentItem, Position position, String prefix);

protected abstract String getErrorCode();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,23 @@ protected String getErrorCode() {

/**
* retrieve list of completion items for a property key
*
* @param textDocumentItem text document
* @param position current position, used to compute key
* @param position current position, used to compute key
* @param prefix prefix value to trigger completion with
* @return list of string of completion item names
*/
@Override
protected List<String> retrieveCompletionValues(TextDocumentItem textDocumentItem,
Position position) {
Position position, String prefix) {
List<String> completionValues = new ArrayList<>();
try {
LibertyTextDocument openedDocument = libertyLanguageServer.getTextDocumentService().getOpenedDocument(textDocumentItem.getUri());
String line = new ParserFileHelperUtil().getLine(openedDocument, position);
PropertiesEntryInstance propertiesEntryInstance = new PropertiesEntryInstance(line, openedDocument);
CompletableFuture<List<CompletionItem>> completions;
// get all completions for current property key
CompletableFuture<List<CompletionItem>> completions = propertiesEntryInstance.getPropertyValueInstance().getCompletions("", position);
completions = propertiesEntryInstance.getPropertyValueInstance().getCompletions(prefix, position);
// map text values from completion items
completionValues = completions.thenApply(completionItems -> completionItems.stream()
.map(it -> it.getTextEdit().getLeft().getNewText())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ private List<Diagnostic> computeInvalidValuesDiagnostic(PropertiesValidationResu
String messageTemplate = DiagnosticMessages.getString(validationResult.getDiagnosticType());

// Currently the last arg (getIntegerRange) is only used for the Integer messages which use {2}. Otherwise null is passed and is ignored by the other messages.
String message = MessageFormat.format(messageTemplate, validationResult.getValue(), property, ServerPropertyValues.getIntegerRange(property));
String message = MessageFormat.format(messageTemplate, validationResult.getCustomValue() != null ? validationResult.getCustomValue() : validationResult.getValue(), property, ServerPropertyValues.getIntegerRange(property));
Diagnostic diagnostic = new Diagnostic(computeRange(validationResult, lineContentInError), message, DiagnosticSeverity.Error, "Liberty Config Language Server");
diagnostic.setCode(ERROR_CODE_INVALID_PROPERTY_VALUE);
diagnostic.setData(validationResult.getMultiValuePrefix());
lspDiagnostics.add(diagnostic);
}
return lspDiagnostics;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022, 2024 IBM Corporation and others.
* Copyright (c) 2022, 2025 IBM Corporation and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand All @@ -9,9 +9,13 @@
*******************************************************************************/
package io.openliberty.tools.langserver.model.propertiesfile;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
Expand Down Expand Up @@ -108,28 +112,104 @@ protected void setDetailsOnCompletionItems(List<CompletionItem> items, String ke
}
}

/**
* this method will return valid properties for a specified key
* 1. In case of multi value property and user has entered multiple properties
* a. split input to prefix and filtervalue
* b. if all values in prefix are valid, use filtervalue and show completion
* of all allowed properties exlcuding already added valid properties
* c. if any of the values in entered text is invalid, do not show completion
* 2. in case of single value property or no comma is specified in multi value
* a. consider the entered value as filter and filter through all valid values
* to show completion
* @param enteredValue entered value
* @param position current position in document
* @return completion Items
*/
public List<CompletionItem> getValidValues(String enteredValue, Position position) {
List<String> values = ServerPropertyValues.getValidValues(textDocumentItem, propertyKey);
List<CompletionItem> results = values.stream()
.filter(s -> s.toLowerCase().contains(enteredValue.trim().toLowerCase()))
.map(s -> {
int line = position.getLine();
Position rangeStart = new Position(line, getEndPosition() + 2);
Position rangeEnd = new Position(line, getEndPosition() + 2 + s.length());
Range range = new Range(rangeStart, rangeEnd);
Either<TextEdit, InsertReplaceEdit> edit = Either.forLeft(new TextEdit(range, s));
CompletionItem completionItem = new CompletionItem();
completionItem.setTextEdit(edit);
completionItem.setLabel(s);
return completionItem;
}).toList();
List<String> validValues = ServerPropertyValues.getValidValues(textDocumentItem, propertyKey);
List<String> enteredValuesLowerCase = new ArrayList<>();
String filterValue;
String prefix;
int endPosition = getEndPosition() + 2;
if (ServerPropertyValues.multipleCommaSeparatedValuesAllowed(propertyKey)) {
if (enteredValue != null && enteredValue.contains(",")) {
// has comma separated values
prefix = enteredValue.substring(0, enteredValue.lastIndexOf(",") + 1);
filterValue = enteredValue.substring(enteredValue.lastIndexOf(",") + 1);
enteredValuesLowerCase = ServerPropertyValues.getCommaSeparatedValues(prefix).stream()
.map(String::toLowerCase).toList();
// in case any of the entered value is invalid, do not show completion
Set<String> invalidValuesEntered = new HashSet<>(enteredValuesLowerCase);
validValues.forEach(v -> invalidValuesEntered.remove(v.toLowerCase()));
if (!invalidValuesEntered.isEmpty()) {
return Collections.emptyList();
}
} else {
// in case of no comma, set prefix as empty and use entered value to filter for property
filterValue = enteredValue;
prefix = "";
}
} else {
// in case single value allowed, set prefix as empty and use entered value to filter for property
// this else is required to make variable effectively final
filterValue = enteredValue;
prefix = "";
}
List<CompletionItem> results = generateCompletionItemsList(position, validValues, filterValue, enteredValuesLowerCase, prefix, endPosition);
// Preselect the default.
// This uses the first item in the List as default.
// This uses the first item in the List as default.
// (Check ServerPropertyValues.java) Currently sorted to have confirmed/sensible values as default.
setDetailsOnCompletionItems(results, propertyKey, true);
return results;
}

/**
* 1. generate list of completion items based on filtering using filter value,
* and excluding all existing values
* 2. append prefix into completion string in case of multi value properties
* if user has user entered,WLP_LOGGING_CONSOLE_SOURCE=abc,audit,message,|
* completion should contain something like
* "audit,message,trace"
* "audit,message,ffdc"
* "audit,message,auditLog"
* @param position current position
* @param validValues allowed values list
* @param filterValue filter through all valid allowed values
* @param enteredValuesLowerCase existing values in property, empty incase of single value
* @param prefix prefix in case of multi value
* @param endPosition last position
* @return completion items
*/
private List<CompletionItem> generateCompletionItemsList(Position position, List<String> validValues, String filterValue, List<String> enteredValuesLowerCase, String prefix, int endPosition) {
Stream<String> filteredCompletion = validValues.stream()
.filter(s -> s.toLowerCase().contains(filterValue.trim().toLowerCase()));
if (!enteredValuesLowerCase.isEmpty()) {
// if there is already a value specified in case of comma separated field, filter out existing
filteredCompletion = filteredCompletion.filter(c -> !enteredValuesLowerCase.contains(c.toLowerCase()));
}
return filteredCompletion.map(s -> getCompletionItem(position, prefix + s, endPosition)).toList();
}

/**
* populate completion item object
* @param position current line position
* @param labelString string
* @param endPosition endposition
* @return
*/
private CompletionItem getCompletionItem(Position position, String labelString, int endPosition) {
int line = position.getLine();
Position rangeStart = new Position(line, endPosition);
Position rangeEnd = new Position(line, endPosition + labelString.length());
Range range = new Range(rangeStart, rangeEnd);
Either<TextEdit, InsertReplaceEdit> edit = Either.forLeft(new TextEdit(range, labelString));
CompletionItem completionItem = new CompletionItem();
completionItem.setTextEdit(edit);
completionItem.setLabel(labelString);
return completionItem;
}

@Override
public String toString() {
return this.propertyKey;
Expand Down
Loading