Skip to content

Commit

Permalink
Completion rework WiP
Browse files Browse the repository at this point in the history
  • Loading branch information
m0rkeulv committed May 12, 2024
1 parent 41dc3a9 commit 68d53c0
Show file tree
Hide file tree
Showing 43 changed files with 1,732 additions and 317 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
# Changelog
## 1.5.4
* Bugfix: import would be displayed as unused if last reference in a file was a fully qualified reference.
* Improvement: major rework of completion suggestions
- Added completion for constructors
- Added public static members to completion suggestions
- Added auto insertion of import statement if missing
- Fixed documentation lookup for indexed items
- Fixed issue for classes with identical names
- Ignoring files in platform specific implementations of standard library (_std)
- Changed the rendering of the completion elements to look more like the intellij default.
- initial attempt at prioritizing lookup elements by relevance.

## 1.5.3
* Fixed: Problem displaying import suggestions in Intellij 2024.1
* Improvement: Syntax highlighting for metadata arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Icon getIcon() {
}
@Override
public Icon getCompletionIcon() {
return AllIcons.Nodes.Class;
return HaxeIcons.Class;
}
}, ENUM(1) {
@Override
Expand All @@ -47,7 +47,7 @@ public Icon getIcon() {
}
@Override
public Icon getCompletionIcon() {
return AllIcons.Nodes.Enum;
return HaxeIcons.Enum;
}
}, INTERFACE(2) {
@Override
Expand All @@ -56,7 +56,7 @@ public Icon getIcon() {
}
@Override
public Icon getCompletionIcon() {
return AllIcons.Nodes.Interface;
return HaxeIcons.Interface;
}
}, FUNCTION(3) {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void invoke(@NotNull final Project project, final Editor editor, PsiFile
.showInBestPositionFor(editor);
});
}
else {
else if (!candidates.isEmpty()) {
doImport(candidates.iterator().next());
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpressi
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
} else {
if (parameter.isOptional()) {
argumentCounter--; //retry argument with next parameter
Expand All @@ -242,6 +243,7 @@ public static CallExpressionValidation checkMethodCall(@NotNull HaxeCallExpressi
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
} else {
if (parameter.isOptional()) {
argumentCounter--; //retry argument with next parameter
Expand Down Expand Up @@ -415,6 +417,7 @@ public static CallExpressionValidation checkFunctionCall(HaxeCallExpression call
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
}else {
if (parameter.isOptional()) {
argumentCounter--; //retry argument with next parameter
Expand Down Expand Up @@ -578,6 +581,7 @@ public static CallExpressionValidation checkConstructor(HaxeNewExpression newExp
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
} else {
if (parameter.isOptional()) {
argumentCounter--; //retry argument with next parameter
Expand All @@ -596,6 +600,7 @@ public static CallExpressionValidation checkConstructor(HaxeNewExpression newExp
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
}
else {
if (parameter.isOptional()) {
Expand Down Expand Up @@ -749,6 +754,7 @@ public static CallExpressionValidation checkEnumConstructor(HaxeCallExpression e
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
} else {
if (parameter.isOptional()) {
argumentCounter--; //retry argument with next parameter
Expand All @@ -767,6 +773,7 @@ public static CallExpressionValidation checkEnumConstructor(HaxeCallExpression e
addToIndexMap(validation, argumentCounter, parameterCounter);
addArgumentTypeToIndex(validation, argumentCounter, argumentType);
addParameterTypeToIndex(validation, parameterCounter, parameterType);
validation.ParameterNames.add(parameter.getName());
}
else {
if (parameter.isOptional()) {
Expand Down Expand Up @@ -1066,6 +1073,7 @@ public static class CallExpressionValidation {
Map<Integer, Integer> argumentToParameterIndex = new HashMap<>();
Map<Integer, ResultHolder> argumentIndexToType = new HashMap<>();
Map<Integer, ResultHolder> ParameterIndexToType = new HashMap<>();
List<String> ParameterNames = new ArrayList<>();
ResultHolder returnType;

List<ErrorRecord> errors = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,12 @@
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.plugins.haxe.ide.index.HaxeClassInfo;
import com.intellij.plugins.haxe.ide.index.HaxeComponentIndex;
import com.intellij.plugins.haxe.ide.lookup.HaxeIndexedClassElement;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.model.*;
import com.intellij.plugins.haxe.util.HaxeAddImportHelper;
Expand Down Expand Up @@ -119,7 +119,7 @@ private static void addVariantsFromIndex(final CompletionResultSet resultSet,
@Nullable final InsertHandler<LookupElement> insertHandler) {
final Project project = targetFile.getProject();
final GlobalSearchScope scope = HaxeResolveUtil.getScopeForElement(targetFile);
final MyProcessor processor = new MyProcessor(resultSet, prefixPackage, insertHandler);
final MyProcessor processor = new MyProcessor(resultSet, prefixPackage, insertHandler, targetFile);
HaxeComponentIndex.processAll(project, processor, scope);
}

Expand Down Expand Up @@ -180,25 +180,25 @@ private static class MyProcessor implements Processor<Pair<String, HaxeClassInfo
private final CompletionResultSet myResultSet;
@Nullable private final InsertHandler<LookupElement> myInsertHandler;
@Nullable private final String myPrefixPackage;
@Nullable private final PsiElement element;

private MyProcessor(CompletionResultSet resultSet,
@Nullable String prefixPackage,
@Nullable InsertHandler<LookupElement> insertHandler) {
@Nullable InsertHandler<LookupElement> insertHandler,
@Nullable PsiElement element
) {
this.element = element;
myResultSet = resultSet;
myPrefixPackage = prefixPackage;
myInsertHandler = insertHandler;

}

@Override
public boolean process(Pair<String, HaxeClassInfo> pair) {
HaxeClassInfo info = pair.getSecond();
if (myPrefixPackage == null || myPrefixPackage.equalsIgnoreCase(info.getValue())) {
String name = pair.getFirst();
final String qName = HaxeResolveUtil.joinQName(info.getValue(), name);
myResultSet.addElement(LookupElementBuilder.create(qName, name)
.withIcon(info.getCompletionIcon())
.withTailText(" " + info.getValue(), true)
.withInsertHandler(myInsertHandler));
if (myPrefixPackage == null || myPrefixPackage.equalsIgnoreCase(info.getPath())) {
myResultSet.addElement(new HaxeIndexedClassElement(info.getName(), info.getPath(), info.getType(), element));
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
*/
package com.intellij.plugins.haxe.ide.completion;

import com.intellij.openapi.diagnostic.LogLevel;
import com.intellij.patterns.*;
import com.intellij.plugins.haxe.lang.psi.*;

Expand Down Expand Up @@ -53,6 +52,11 @@ public class HaxeCommonCompletionPattern {
elementPattern("inImportOrUsing")
.withSuperParent(3, matchUsingAndImport);

public static final PsiElementPattern.Capture<PsiElement> identifierInNewExpression =
elementPattern("nextToNewKeyword")
.inside(HaxeIdentifier.class)
.withSuperParent(4, psiElement(HaxeNewExpression.class));

public static final PsiElementPattern.Capture<PsiElement> skippableWhitespace =
elementPattern("skippableWhitespace")
.andOr(psiElement().withText(" "),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package com.intellij.plugins.haxe.ide.completion;

import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionResult;
import com.intellij.codeInsight.completion.PrioritizedLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.plugins.haxe.HaxeComponentType;
import com.intellij.plugins.haxe.ide.annotator.semantics.HaxeCallExpressionUtil;
import com.intellij.plugins.haxe.ide.lookup.*;
import com.intellij.plugins.haxe.lang.psi.*;
import com.intellij.plugins.haxe.model.*;
import com.intellij.plugins.haxe.model.type.ResultHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;

import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.intellij.plugins.haxe.ide.completion.HaxeCommonCompletionPattern.identifierInNewExpression;
import static com.intellij.plugins.haxe.ide.lookup.HaxeCompletionPriorityData.*;

public class HaxeCompletionPriorityUtil {

public static Set<CompletionResult> calculatePriority(Set<CompletionResult> completions, CompletionParameters parameters) {
// ignore any completion that does not implement boost
List<HaxeLookupElement> lookupList =
completions.stream().filter(result -> result.getLookupElement() instanceof HaxeLookupElement)
.map(result -> (HaxeLookupElement)result.getLookupElement())
.toList();

PsiElement position = parameters.getPosition();
PsiElement parent = position.getParent();
if (identifierInNewExpression.accepts(position)) {
return prioritizeConstructorsRemoveOtherMembers(completions);
}
// is argument (get parameter type)
boolean sorted = false;
if (!sorted)sorted = trySortForExtends(position, lookupList);
if (!sorted) sorted = trySortForArgument(position, lookupList); // NOTE TO SELF: function keyword if parameter is function typ
//TODO WiP
// is for-loop (prioritize itrables)
// is extends (if class, then class, if interface then interfaces)
// is implements (interfaces)
// is IF (prioritize bool)
// is switch block (find expression type, prioritize type)
// - if enumvalue compare declaring class with type

// local variables first (3), then fields(2) then methods (1)

//DEBUG STUFF:
List<HaxeLookupElement> debugSortedList = lookupList.stream().sorted((o1, o2) -> {
double calculate1 = o1.getPriority().calculate();
double calculate2 = o2.getPriority().calculate();
if (calculate1 > calculate2) return -1;
if (calculate1 < calculate2) return 1;
return 0;

}).toList();

return completions;
}

/*
* If part of an inherit list, prioritize classes and interfaces
* - if part of an interface, prioritize other interfaces to extend
* - if part of class, prioritize classes if in extends, and interfaces in implements lists
*/
private static boolean trySortForExtends(PsiElement position, List<HaxeLookupElement> list) {
HaxeInheritList inheritList = PsiTreeUtil.getParentOfType(position, HaxeInheritList.class);
if (inheritList != null) {
HaxeClassDeclaration classDeclaration = PsiTreeUtil.getParentOfType(inheritList, HaxeClassDeclaration.class);
if (classDeclaration != null) {
HaxeExtendsDeclaration extendsDeclaration = PsiTreeUtil.getParentOfType(position, HaxeExtendsDeclaration.class);
if (extendsDeclaration != null) {
list.stream()
.filter(element -> element instanceof HaxePsiLookupElement lookupElement && lookupElement.getType() == HaxeComponentType.CLASS)
.forEach(element -> element.getPriority().type += 1);
return true;
}
HaxeImplementsDeclaration implementsDeclaration = PsiTreeUtil.getParentOfType(position, HaxeImplementsDeclaration.class);
if (implementsDeclaration != null) {
list.stream()
.filter(element -> element instanceof HaxePsiLookupElement lookupElement && lookupElement.getType() == HaxeComponentType.INTERFACE)
.forEach(element -> element.getPriority().type += 1);
return true;
}
}else {
list.stream()
.filter(element -> element instanceof HaxePsiLookupElement lookupElement && lookupElement.getType() == HaxeComponentType.INTERFACE)
.forEach(element -> element.getPriority().type += 1);
return true;
}
}
return false;
}

private static Set<CompletionResult> prioritizeConstructorsRemoveOtherMembers(Set<CompletionResult> completions) {
Stream<CompletionResult> constructors = completions.stream()
.filter(result -> result.getLookupElement() instanceof HaxeConstructorLookupElement)
.peek(result -> ((HaxeConstructorLookupElement)result.getLookupElement()).getPriority().type += 1);
Stream<CompletionResult> others = completions.stream().filter(result -> !(result.getLookupElement() instanceof HaxeLookupElement));
return Stream.concat(constructors, others).collect(Collectors.toSet());
}

private static boolean trySortForArgument(PsiElement position, List<HaxeLookupElement> lookupElements) {
HaxeCallExpression callExpression = PsiTreeUtil.getParentOfType(position, HaxeCallExpression.class, true, HaxeNewExpression.class);
HaxeNewExpression newExpression = PsiTreeUtil.getParentOfType(position, HaxeNewExpression.class, true, HaxeCallExpression.class);
if (newExpression == null && callExpression == null) return false;

HaxeCallExpressionUtil.CallExpressionValidation validation = null;
if (callExpression != null) {
validation = getValidationForMethod(callExpression);
}
if (newExpression != null && newExpression.getType() != null) {
boolean completeType = newExpression.getType().textMatches(position);
// if completing type name
if (completeType) {
lookupElements.stream()
.filter(element -> element instanceof HaxeClassLookupElement)
.forEach(e -> e.getPriority().type += 1);
return true;
}else {
// else if completing parameters
validation = HaxeCallExpressionUtil.checkConstructor(newExpression);
}
}

if (validation!= null) {
Collection<ResultHolder> parameterTypes = validation.getParameterIndexToType().values();
List<String> names = validation.getParameterNames();
lookupElements.stream()
.peek( element -> {if(element instanceof HaxePackageLookupElement lookupElement) lookupElement.getPriority().type -=0.1;})
.filter(lookupElement -> lookupElement instanceof HaxeMemberLookupElement)
.peek(element -> element.getPriority().type +=1)
.map(lookupElement -> (HaxeMemberLookupElement)lookupElement)
.forEach( r-> memberAssignCalculation(r, parameterTypes));
return true;
}
return false;
}

private static HaxeCallExpressionUtil.CallExpressionValidation getValidationForMethod(HaxeCallExpression callExpression) {
if (callExpression.getExpression() instanceof HaxeReferenceExpression referenceExpression) {
PsiElement resolve = referenceExpression.resolve();
if (resolve instanceof HaxeMethod method) {
return HaxeCallExpressionUtil.checkMethodCall(callExpression, method);
}
}
return null;
}


private static void memberAssignCalculation(HaxeMemberLookupElement element, Collection<ResultHolder> parameterTypes) {
HaxeBaseMemberModel model = element.getModel();
if (model == null) return;

if(model instanceof HaxeLocalVarModel) {
element.getPriority().type += LOCAL_VAR;
}

if(model instanceof HaxeFieldModel) {
element.getPriority().type += FIELD;
}

if (model instanceof HaxeMethodModel) {
element.getPriority().type += METHOD;
}

ResultHolder type = model.getResultType(null);
if (type != null && !type.isVoid()) {
if (parameterTypes.stream().anyMatch(type::canAssign)) {
element.getPriority().assignable += 1;
}
}
}


private static @NotNull CompletionResult boost(CompletionResult result, double finalBoost) {
LookupElement element = result.getLookupElement();
if (element instanceof HaxeMemberLookupElement lookupElement) {
if (lookupElement.getModel() instanceof HaxeFieldModel) {
return result.withLookupElement(PrioritizedLookupElement.withPriority(element, finalBoost));
}
}
return result;
}

public static CompletionResult convertToPrioritized(CompletionResult result) {
if (result.getLookupElement() instanceof HaxeLookupElement element) {
return CompletionResult.wrap(element.toPrioritized(), result.getPrefixMatcher(), result.getSorter());
}
return result;
}

}
Loading

0 comments on commit 68d53c0

Please sign in to comment.