Skip to content

Commit

Permalink
Added Qute CodeAction(s) for similar text suggestions
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Chen <alchen@redhat.com>
  • Loading branch information
Alexander Chen committed Jul 27, 2022
1 parent e12966c commit 58df6a2
Show file tree
Hide file tree
Showing 4 changed files with 605 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,40 +16,54 @@
import static com.redhat.qute.services.diagnostics.QuteDiagnosticContants.DIAGNOSTIC_DATA_ITERABLE;
import static com.redhat.qute.services.diagnostics.QuteDiagnosticContants.DIAGNOSTIC_DATA_NAME;
import static com.redhat.qute.services.diagnostics.QuteDiagnosticContants.DIAGNOSTIC_DATA_TAG;
import static com.redhat.qute.utils.StringUtils.isSimilar;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.Diagnostic;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.jsonrpc.CancelChecker;

import com.google.gson.JsonObject;
import com.redhat.qute.commons.JavaFieldInfo;
import com.redhat.qute.commons.JavaMethodInfo;
import com.redhat.qute.commons.ResolvedJavaTypeInfo;
import com.redhat.qute.ls.commons.BadLocationException;
import com.redhat.qute.ls.commons.CodeActionFactory;
import com.redhat.qute.ls.commons.TextDocument;
import com.redhat.qute.ls.commons.client.ConfigurationItemEdit;
import com.redhat.qute.ls.commons.client.ConfigurationItemEditType;
import com.redhat.qute.parser.expression.ObjectPart;
import com.redhat.qute.parser.expression.Part;
import com.redhat.qute.parser.template.Expression;
import com.redhat.qute.parser.template.Node;
import com.redhat.qute.parser.template.NodeKind;
import com.redhat.qute.parser.template.ParameterDeclaration;
import com.redhat.qute.parser.template.Section;
import com.redhat.qute.parser.template.SectionMetadata;
import com.redhat.qute.parser.template.Template;
import com.redhat.qute.project.QuteProject;
import com.redhat.qute.project.datamodel.ExtendedDataModelParameter;
import com.redhat.qute.project.datamodel.ExtendedDataModelTemplate;
import com.redhat.qute.project.datamodel.JavaDataModelCache;
import com.redhat.qute.project.datamodel.resolvers.ValueResolver;
import com.redhat.qute.services.commands.QuteClientCommandConstants;
import com.redhat.qute.services.diagnostics.QuteErrorCode;
import com.redhat.qute.settings.QuteValidationSettings.Severity;
import com.redhat.qute.settings.SharedSettings;
import com.redhat.qute.utils.QutePositionUtility;
import com.redhat.qute.utils.UserTagUtils;

/**
* Qute code actions support.
Expand Down Expand Up @@ -81,6 +95,12 @@ class QuteCodeActions {

private static final String UNDEFINED_NAMESPACE_SEVERITY_SETTING = "qute.validation.undefinedNamespace.severity";

private final JavaDataModelCache javaCache;

public QuteCodeActions(JavaDataModelCache javaCache) {
this.javaCache = javaCache;
}

public CompletableFuture<List<CodeAction>> doCodeActions(Template template, CodeActionContext context, Range range,
SharedSettings sharedSettings) {
List<CodeAction> codeActions = new ArrayList<>();
Expand All @@ -100,6 +120,18 @@ public CompletableFuture<List<CodeAction>> doCodeActions(Template template, Code
QuteErrorCode errorCode = QuteErrorCode.getErrorCode(diagnostic.getCode());
if (errorCode != null) {
switch (errorCode) {
case UnknownProperty:
case UnknownMethod:
// CodeAction(s) to replace text with similar suggestions
try {
String varName = QutePositionUtility
.findBestNode(template.offsetAt(diagnostic.getRange().getStart()), template
.findNodeBefore(template.offsetAt(diagnostic.getRange().getStart())))
.getNodeName();
doCodeActionsForSimilarValues(template, diagnostic, codeActions, varName);
} catch (BadLocationException e) {
}
break;
case UndefinedObject:
// The following Qute template:
// {undefinedObject}
Expand All @@ -123,6 +155,15 @@ public CompletableFuture<List<CodeAction>> doCodeActions(Template template, Code
// {undefinedNamespace:xyz}
doCodeActionToSetIgnoreSeverity(template, Collections.singletonList(diagnostic), errorCode,
codeActions, UNDEFINED_NAMESPACE_SEVERITY_SETTING);
// CodeAction(s) to replace text with similar suggestions
try {
String varName = QutePositionUtility
.findBestNode(template.offsetAt(diagnostic.getRange().getStart()), template
.findNodeBefore(template.offsetAt(diagnostic.getRange().getStart())))
.getNodeName();
doCodeActionsForSimilarValues(template, diagnostic, codeActions, varName);
} catch (BadLocationException e) {
}
break;
default:
break;
Expand All @@ -133,8 +174,8 @@ public CompletableFuture<List<CodeAction>> doCodeActions(Template template, Code
return CompletableFuture.completedFuture(codeActions);
}

private static void doCodeActionsForUndefinedObject(Template template, Diagnostic diagnostic,
QuteErrorCode errorCode, List<CodeAction> codeActions) {
private void doCodeActionsForUndefinedObject(Template template, Diagnostic diagnostic, QuteErrorCode errorCode,
List<CodeAction> codeActions) {
try {
String varName = null;
boolean isIterable = false;
Expand Down Expand Up @@ -184,6 +225,9 @@ private static void doCodeActionsForUndefinedObject(Template template, Diagnosti

// CodeAction to append ?? to object to make it optional
doCodeActionToAddOptionalSuffix(template, diagnostic, codeActions);

// CodeAction(s) to replace text with similar suggestions
doCodeActionsForSimilarValues(template, diagnostic, codeActions, varName);
}

} catch (BadLocationException e) {
Expand Down Expand Up @@ -298,4 +342,128 @@ private static void doCodeActionsForUndefinedSectionTag(Template template, Diagn
LOGGER.log(Level.SEVERE, "Creation of undefined user tag code action failed", e);
}
}

private void doCodeActionsForSimilarValues(Template template, Diagnostic diagnostic, List<CodeAction> codeActions,
String varName) throws BadLocationException {
Range diagnosticRange = diagnostic.getRange();
int offset = template.offsetAt(diagnosticRange.getEnd());

Node nodeBefore = template.findNodeBefore(offset);
if (nodeBefore == null) {
return;
}

Node node = QutePositionUtility.findBestNode(offset, nodeBefore);

if (node.getKind() == NodeKind.ExpressionPart) {
Range rangeValue = new Range(
new Position(diagnosticRange.getStart().getLine(), diagnosticRange.getStart().getCharacter()),
new Position(diagnosticRange.getEnd().getLine(), diagnosticRange.getEnd().getCharacter()));

Collection<String> availableValues = new TreeSet<>();

switch (((Part) node).getPartKind()) {
case Object:
collectAvailableValuesForObjectPart(node, template, availableValues);
case Namespace:
collectAvailableValuesForNamespacePart(node, template, availableValues);
break;
case Method:
case Property:
String signature = null;
JsonObject data = (JsonObject) diagnostic.getData();
if (data != null) {
signature = data.get(DIAGNOSTIC_DATA_TAG).getAsString();
collectAvailableValuesForJavaTypePart(node, template, signature, availableValues);
break;
}
}

for (String value : availableValues) {
if (isSimilar(value, varName)) {
CodeAction similarCodeAction = CodeActionFactory.replace("Did you mean '" + value + "'?",
rangeValue, value, template.getTextDocument(), diagnostic);
codeActions.add(similarCodeAction);
}
}
}
}

private void collectAvailableValuesForObjectPart(Node node, Template template, Collection<String> availableValues) {
String projectUri = template.getProjectUri();

List<ValueResolver> globalResolvers = javaCache.getGlobalVariables(projectUri);
for (ValueResolver resolver : globalResolvers) {
availableValues.add(resolver.getName());
}

List<String> aliases = template.getChildren().stream() //
.filter(n -> n.getKind() == NodeKind.ParameterDeclaration) //
.map(n -> ((ParameterDeclaration) n).getAlias()) //
.filter(alias -> alias != null) //
.collect(Collectors.toList());
for (String alias : aliases) {
availableValues.add(alias);
}

ExtendedDataModelTemplate dataModel = javaCache.getDataModelTemplate(template).getNow(null);
if (dataModel != null) {
for (ExtendedDataModelParameter parameter : dataModel.getParameters()) {
availableValues.add(parameter.getKey());
}
}

Section section = node != null ? node.getParentSection() : null;
if (section != null) {
List<SectionMetadata> metadatas = section.getMetadata();
for (SectionMetadata metadata : metadatas) {
availableValues.add(metadata.getName());
}
}

Collection<SectionMetadata> specialKeysMetadatas = UserTagUtils.getSpecialKeys();
for (SectionMetadata metadata : specialKeysMetadatas) {
String name = metadata.getName();
if (!availableValues.contains(name)) {
availableValues.add(name);
}
}
}

private void collectAvailableValuesForNamespacePart(Node node, Template template,
Collection<String> availableValues) {
String projectUri = template.getProjectUri();

String namespace = ((Part) node).getNamespace();
if (namespace != null) {
List<ValueResolver> namespaceResolvers = javaCache.getNamespaceResolvers(namespace, projectUri);
for (ValueResolver resolver : namespaceResolvers) {
boolean useNamespaceInTextEdit = namespace == null;
String named = resolver.getNamed();
if (named != null) {
// @Named("user")
// User getUser();
String label = useNamespaceInTextEdit ? resolver.getNamespace() + ':' + named : named;
if (!availableValues.contains(label)) {
availableValues.add(label);
}
}
}
}
}

private void collectAvailableValuesForJavaTypePart(Node node, Template template, String signature,
Collection<String> availableValues) {
String projectUri = template.getProjectUri();

ResolvedJavaTypeInfo resolvedJavaType = javaCache.resolveJavaType(signature, projectUri).getNow(null);

for (JavaFieldInfo field : resolvedJavaType.getFields()) {
availableValues.add(field.getName());
}

for (JavaMethodInfo method : resolvedJavaType.getMethods()) {
availableValues.add(method.getName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class QuteLanguageService implements SnippetRegistryProvider<Snippet> {
private SnippetRegistry<Snippet> coreTagSnippetRegistry;

public QuteLanguageService(JavaDataModelCache javaCache) {
this.codeActions = new QuteCodeActions();
this.codeActions = new QuteCodeActions(javaCache);
this.codeLens = new QuteCodeLens(javaCache);
this.completions = new QuteCompletions(javaCache, this);
this.definition = new QuteDefinition(javaCache);
Expand Down
Loading

0 comments on commit 58df6a2

Please sign in to comment.