mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
MLP-6 Similarity Groups in Find Usages initial support
Added support for java and kt usages KTIJ-21819 IDEA-294547 GitOrigin-RevId: 5bf947ecd97ba1ec106adbb9469307a51cc9508c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4a8945601e
commit
fcfc76cd46
@@ -2274,6 +2274,8 @@
|
||||
|
||||
<dependencyCollector kind="java" implementation="com.intellij.ide.JavaDependencyCollector"/>
|
||||
|
||||
<usageFeaturesProvider implementation="com.intellij.find.findUsages.similarity.JavaUsageSimilarityFeaturesProvider"/>
|
||||
|
||||
<registryKey key="java.formatter.chained.calls.pre212.compatibility"
|
||||
defaultValue="false"
|
||||
description="Format chained calls as in versions prior to 2021.2"/>
|
||||
|
||||
+237
@@ -0,0 +1,237 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesRecorder;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.intellij.psi.JavaTokenType.FINAL_KEYWORD;
|
||||
|
||||
public class JavaSimilarityPropertiesExtractor extends JavaRecursiveElementVisitor {
|
||||
private final @NotNull UsageSimilarityFeaturesRecorder myUsageSimilarityFeaturesRecorder;
|
||||
private final @NotNull PsiElement myContext;
|
||||
|
||||
public JavaSimilarityPropertiesExtractor(@NotNull PsiElement context) {
|
||||
myUsageSimilarityFeaturesRecorder = new UsageSimilarityFeaturesRecorder(context);
|
||||
myContext = context;
|
||||
}
|
||||
|
||||
public @NotNull Bag getFeatures() {
|
||||
myContext.accept(this);
|
||||
return myUsageSimilarityFeaturesRecorder.getFeatures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitKeyword(PsiKeyword keyword) {
|
||||
final IElementType type = keyword.getTokenType();
|
||||
if (!type.equals(FINAL_KEYWORD)) {
|
||||
myUsageSimilarityFeaturesRecorder.addFeature(type.toString());
|
||||
}
|
||||
super.visitKeyword(keyword);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitVariable(PsiVariable variable) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(variable, "VAR: " + getTypeRepresentation(variable.getType()));
|
||||
super.visitVariable(variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNewExpression(PsiNewExpression expression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "NEW: " + getTypeRepresentation(expression));
|
||||
super.visitNewExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForStatement(PsiForStatement statement) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "FOR");
|
||||
super.visitForStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForeachStatement(PsiForeachStatement statement) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "FOREACH");
|
||||
super.visitForeachStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitDoWhileStatement(PsiDoWhileStatement statement) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "DO");
|
||||
super.visitDoWhileStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIfStatement(PsiIfStatement statement) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "IF");
|
||||
super.visitIfStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.getOperationSign().getText());
|
||||
super.visitAssignmentExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "instanceof");
|
||||
super.visitInstanceOfExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.getOperationTokenType().toString());
|
||||
super.visitPolyadicExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSwitchStatement(PsiSwitchStatement statement) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "SWITCH");
|
||||
super.visitSwitchStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitWhileStatement(PsiWhileStatement statement) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(statement, "WHILE");
|
||||
super.visitWhileStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(PsiReferenceExpression expression) {
|
||||
String tokenFeature = null;
|
||||
if (!(expression instanceof PsiMethodReferenceExpression)) {
|
||||
tokenFeature = "VAR: ";
|
||||
if (!Registry.is("similarity.find.usages.fast.clustering")) {
|
||||
tokenFeature += getTypeRepresentation(expression);
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenFeature != null) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
|
||||
}
|
||||
super.visitReferenceExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) {
|
||||
String tokenFeature;
|
||||
if (Registry.is("similarity.find.usages.fast.clustering")) {
|
||||
final PsiElement referenceNameElement = expression.getReferenceNameElement();
|
||||
tokenFeature = "{CALL: " + (referenceNameElement != null ? referenceNameElement.getText() : null) + "}";
|
||||
}
|
||||
else {
|
||||
tokenFeature = viaResolve(expression);
|
||||
}
|
||||
if (tokenFeature != null) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
|
||||
}
|
||||
super.visitMethodReferenceExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
|
||||
PsiExpression arrayIndex = expression.getIndexExpression();
|
||||
String tokenFeature = "{ARRAY: data: " + expression.getType() + " index:" + getTypeRepresentation(arrayIndex) + "}";
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
|
||||
super.visitArrayAccessExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
|
||||
String tokenFeature;
|
||||
if (Registry.is("similarity.find.usages.fast.clustering")) {
|
||||
tokenFeature = "{CALL: " + expression.getMethodExpression().getReferenceName() + "}";
|
||||
}
|
||||
else {
|
||||
tokenFeature = getTokenFeatureViaResolve(expression);
|
||||
}
|
||||
if (tokenFeature != null) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, tokenFeature);
|
||||
}
|
||||
super.visitMethodCallExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLiteralExpression(PsiLiteralExpression expression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "literal:" + getTypeRepresentation(expression));
|
||||
super.visitLiteralExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLambdaExpression(PsiLambdaExpression expression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "lambda");
|
||||
super.visitLambdaExpression(expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnonymousClass(PsiAnonymousClass clazz) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(clazz, "anonymousClazz");
|
||||
super.visitAnonymousClass(clazz);
|
||||
}
|
||||
|
||||
private static @Nullable String viaResolve(@NotNull PsiMethodReferenceExpression expression) {
|
||||
final PsiElement resolve = expression.resolve();
|
||||
final PsiMethod method = ObjectUtils.tryCast(resolve, PsiMethod.class);
|
||||
if (method != null) {
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass != null) {
|
||||
return "{CALL: " +
|
||||
containingClass.getQualifiedName() +
|
||||
"." +
|
||||
method.getName() + "()" +
|
||||
" ret:" +
|
||||
getTypeRepresentation(expression) + "}";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NotNull String getTypeRepresentation(@Nullable PsiExpression expression) {
|
||||
if (expression == null) return "null";
|
||||
return getTypeRepresentation(expression.getType());
|
||||
}
|
||||
|
||||
private static @NotNull String getTypeRepresentation(@Nullable PsiType type) {
|
||||
if (type != null) {
|
||||
return type.getCanonicalText();
|
||||
}
|
||||
else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getTokenFeatureViaResolve(@NotNull PsiMethodCallExpression expression) {
|
||||
String tokenFeature = null;
|
||||
final PsiReferenceExpression referenceExpression = expression.getMethodExpression();
|
||||
final PsiElement resolve = referenceExpression.resolve();
|
||||
final PsiMethod method = ObjectUtils.tryCast(resolve, PsiMethod.class);
|
||||
if (method != null) {
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass != null) {
|
||||
tokenFeature = "{CALL: " +
|
||||
containingClass.getQualifiedName() +
|
||||
"." +
|
||||
method.getName() + "()" +
|
||||
" ret:" +
|
||||
getTypeRepresentation(referenceExpression) + " " +
|
||||
getArgumentData(expression) + "}";
|
||||
}
|
||||
}
|
||||
return tokenFeature;
|
||||
}
|
||||
|
||||
public static @NotNull String getArgumentData(@NotNull PsiMethodCallExpression methodCallExpression) {
|
||||
PsiExpression[] expressions = methodCallExpression.getArgumentList().getExpressions();
|
||||
StringBuilder arguments = new StringBuilder();
|
||||
for (int i = 0; i < expressions.length; i++) {
|
||||
arguments.append("arg").append(i).append(" type: ").append(getTypeRepresentation(expressions[i]));
|
||||
}
|
||||
return arguments.toString();
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.source.PsiMethodImpl;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.usages.PsiElementUsageTarget;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JavaUsageSimilarityFeaturesProvider implements UsageSimilarityFeaturesProvider {
|
||||
|
||||
@Override
|
||||
@RequiresReadLock
|
||||
@RequiresBackgroundThread
|
||||
public @NotNull Bag getFeatures(@NotNull PsiElement usage) {
|
||||
PsiElement statement = getContainingStatement(usage);
|
||||
if (statement != null) {
|
||||
final Bag usageProperties = new JavaSimilarityPropertiesExtractor(statement).getFeatures();
|
||||
if (Registry.is("similarity.find.usages.parent.statement.condition.feature")) {
|
||||
usageProperties.addAll(getParentStatementFeatures(statement));
|
||||
}
|
||||
return usageProperties;
|
||||
}
|
||||
return Bag.EMPTY_BAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresReadLock
|
||||
public boolean isAvailable(@NotNull PsiElementUsageTarget usageTarget) {
|
||||
return usageTarget.getElement() instanceof PsiMethodImpl;
|
||||
}
|
||||
|
||||
public @Nullable PsiElement getContainingStatement(@NotNull PsiElement element) {
|
||||
return PsiTreeUtil.getParentOfType(element, PsiDeclarationStatement.class,
|
||||
PsiExpressionStatement.class,
|
||||
PsiIfStatement.class,
|
||||
PsiWhileStatement.class,
|
||||
PsiTryStatement.class,
|
||||
PsiThrowStatement.class,
|
||||
PsiSwitchStatement.class,
|
||||
PsiReturnStatement.class,
|
||||
PsiLoopStatement.class,
|
||||
PsiImportStatement.class,
|
||||
PsiForStatement.class,
|
||||
PsiForeachStatement.class,
|
||||
PsiConditionalLoopStatement.class,
|
||||
PsiBlockStatement.class);
|
||||
}
|
||||
|
||||
private static @NotNull Bag getParentStatementFeatures(@NotNull PsiElement context) {
|
||||
final PsiElement parentControlStatement =
|
||||
PsiTreeUtil.findFirstParent(context, true,
|
||||
element -> element instanceof PsiConditionalLoopStatement || element instanceof PsiIfStatement);
|
||||
if (parentControlStatement instanceof PsiConditionalLoopStatement) {
|
||||
final PsiExpression conditionExpression = ((PsiConditionalLoopStatement)parentControlStatement).getCondition();
|
||||
if (conditionExpression != null) {
|
||||
return new JavaSimilarityPropertiesExtractor(conditionExpression).getFeatures();
|
||||
}
|
||||
}
|
||||
if (parentControlStatement instanceof PsiIfStatement) {
|
||||
final PsiExpression conditionExpression = ((PsiIfStatement)parentControlStatement).getCondition();
|
||||
if (conditionExpression != null) {
|
||||
return new JavaSimilarityPropertiesExtractor(conditionExpression).getFeatures();
|
||||
}
|
||||
}
|
||||
return Bag.EMPTY_BAG;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
class A {
|
||||
public int foo(A a) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
int a = a1.f<caret>oo(new A() {
|
||||
public int foo(A a) { return 1; }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
class A {
|
||||
public int foo(A a){
|
||||
return 0;
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A(); int a = a1.f<caret>oo(new A());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
class A {
|
||||
public int foo(A a){
|
||||
return 0;
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
for (int a=0; i<f<caret>oo(this); i++) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
class A {
|
||||
public int af() { return 1; }
|
||||
public static void main(String[] args) {
|
||||
int a = 0;
|
||||
a += <caret>af();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package aPackage;
|
||||
|
||||
class A {
|
||||
public int foo(Runnable run) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
int a = a1.fo<caret>o(()->{});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package aPackage;
|
||||
|
||||
class A {
|
||||
public int foo(Runnable run) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
A a1 = new A();
|
||||
int a = a1.fo<caret>o(A::run);
|
||||
}
|
||||
|
||||
private static void run() { }
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class Market {
|
||||
|
||||
public void a() {
|
||||
System.out.println(getGoods()[0]);
|
||||
System.out.println(getGoods()[1]);
|
||||
System.out.println(getGoods()[1].charAt(0));
|
||||
System.out.println(getGoods()[0].charAt(0));
|
||||
System.out.println(getGoods()[0]+ getGoods()[1]);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String[] getGoods() {
|
||||
return new String[]{"orange", "apple", "cucumber", "tomato"};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[{id=0
|
||||
, myUsages=6|System.out.println(|getGoods|()[|0|]);,
|
||||
7|System.out.println(|getGoods|()[|1|]);}
|
||||
, {id=1
|
||||
, myUsages=8|System.out.println(|getGoods|()[|1|].charAt(|0|));,
|
||||
9|System.out.println(|getGoods|()[|0|].charAt(|0|));}
|
||||
, {id=2
|
||||
, myUsages=10|System.out.println(|getGoods|()[|0|]+ getGoods()[|1|]);,
|
||||
10|System.out.println(getGoods()[|0|]+ |getGoods|()[|1|]);}
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
class Foo {
|
||||
public int f() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
class D {
|
||||
public void run() {
|
||||
Foo foo = new Foo()
|
||||
foo.f() + foo.f();
|
||||
foo.f()+foo.f();
|
||||
int ff1 = foo.f() +1;
|
||||
int ff2 = foo.f() +2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[{id=0
|
||||
, myUsages=9|foo.|f|() + foo.f();,
|
||||
9|foo.f() + foo.|f|();,
|
||||
10|foo.|f|()+foo.f();,
|
||||
10|foo.f()+foo.|f|();}
|
||||
, {id=1
|
||||
, myUsages=11|int| ff1 = foo.|f|() +|1|;,
|
||||
12|int| ff2 = foo.|f|() +|2|;}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class MapUsages {
|
||||
|
||||
public void a() {
|
||||
Map<String, String> map =new HashMap<>();
|
||||
//init map
|
||||
map.put("1", "first");
|
||||
map.put("2", "second");
|
||||
//verify map
|
||||
if (!map.containsKey("1")) {
|
||||
map.put("1", "first");
|
||||
}
|
||||
if (!map.containsKey("2")) {
|
||||
map.put("2", "second");
|
||||
}
|
||||
//clear data
|
||||
map.put("1", null);
|
||||
map.put("2", null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[{id=0
|
||||
, myUsages=9|map.|put|(|"1"|, |"first"|);,
|
||||
10|map.|put|(|"2"|, |"second"|);}
|
||||
, {id=1
|
||||
, myUsages=13|map.|put|(|"1"|, |"first"|);,
|
||||
16|map.|put|(|"2"|, |"second"|);}
|
||||
, {id=2
|
||||
, myUsages=19|map.|put|(|"1"|, |null|);,
|
||||
20|map.|put|(|"2"|, |null|);}
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CollectionsPlayGround {
|
||||
|
||||
public static void main(String[] args) {
|
||||
List<String> list = new ArrayList<>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
list.add("entry:" + i);
|
||||
}
|
||||
|
||||
final List<Integer> unmodifiableHashcodes = list.stream().map(String::hashCode).collect(Collectors.toUnmodifiableList());
|
||||
final List<Integer> modifiableHashcodes = list.stream().map(String::hashCode).collect(Collectors.toList());
|
||||
|
||||
List<String> nonEmptyEntries = list.stream().filter(e -> e.length() > 0).collect(Collectors.toList());
|
||||
List<String> moreThanOneCharEntry = list.stream().filter(e -> e.length() > 1).collect(Collectors.toList());
|
||||
List<String> nonEmptyEntries2 = list.stream().filter(e -> e.isEmpty() ).collect(Collectors.toList());
|
||||
|
||||
Set<String> nonEmptySet = list.stream().filter(e -> e.length() > 0).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[{id=0
|
||||
, myUsages=14|final| List<Integer> unmodifiableHashcodes = list.|stream|().map(String::hashCode).collect(Collectors.toUnmodifiableList());}
|
||||
, {id=1
|
||||
, myUsages=15|final| List<Integer> modifiableHashcodes = list.|stream|().map(String::hashCode).collect(Collectors.toList());}
|
||||
, {id=2
|
||||
, myUsages=17|List<String> nonEmptyEntries = list.|stream|().filter(e -> e.length() > |0|).collect(Collectors.toList());,
|
||||
18|List<String> moreThanOneCharEntry = list.|stream|().filter(e -> e.length() > |1|).collect(Collectors.toList());}
|
||||
, {id=3
|
||||
, myUsages=19|List<String> nonEmptyEntries2 = list.|stream|().filter(e -> e.isEmpty() ).collect(Collectors.toList());}
|
||||
, {id=4
|
||||
, myUsages=21|Set<String> nonEmptySet = list.|stream|().filter(e -> e.length() > |0|).collect(Collectors.toSet());}
|
||||
]
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.groupSimilar;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.find.findUsages.JavaFindUsagesHandler;
|
||||
import com.intellij.find.findUsages.JavaFindUsagesHandlerFactory;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.testFramework.IdeaTestUtil;
|
||||
import com.intellij.testFramework.JavaPsiTestCase;
|
||||
import com.intellij.testFramework.PsiTestUtil;
|
||||
import com.intellij.usages.UsageInfoToUsageConverter;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import static com.intellij.testFramework.EqualsToFile.assertEqualsToFile;
|
||||
|
||||
public class JavaFindUsagesGroupsTest extends JavaPsiTestCase {
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
PsiTestUtil.removeAllRoots(myModule, IdeaTestUtil.getMockJdk18());
|
||||
createTestProjectStructure(getTestRoot());
|
||||
}
|
||||
|
||||
private @NotNull String getTestRoot() {
|
||||
return JavaTestUtil.getJavaTestDataPath() + "/findSimilar/usageGroups/" + getTestName(true);
|
||||
}
|
||||
|
||||
private void doTest(@NotNull PsiMethod streamMethod) {
|
||||
final JavaFindUsagesHandlerFactory factory = JavaFindUsagesHandlerFactory.getInstance(getProject());
|
||||
JavaFindUsagesHandler handler = new JavaFindUsagesHandler(streamMethod, factory);
|
||||
ClusteringSearchSession session = new ClusteringSearchSession();
|
||||
handler.processElementUsages(streamMethod, info -> {
|
||||
UsageInfoToUsageConverter.convertToSimilarUsage(new PsiMethod[]{streamMethod}, info, session);
|
||||
return true;
|
||||
}, factory.getFindMethodOptions());
|
||||
File file = new File(getTestRoot() + "/results.txt");
|
||||
assertEqualsToFile("", file, session.getClusters().toString());
|
||||
}
|
||||
|
||||
private @NotNull PsiMethod getMethod(String className, String methodName) {
|
||||
PsiClass anInterface = myJavaFacade.findClass(className, GlobalSearchScope.allScope(myProject));
|
||||
return anInterface.findMethodsByName(methodName, false)[0];
|
||||
}
|
||||
|
||||
public void testStreamUsages() {
|
||||
doTest(getMethod("java.util.Collection", "stream"));
|
||||
}
|
||||
|
||||
public void testBinaryExpressions() {
|
||||
doTest(getMethod("Foo", "f"));
|
||||
}
|
||||
|
||||
public void testMapUsages() {
|
||||
try {
|
||||
Registry.get("similarity.find.usages.parent.statement.condition.feature").setValue(true);
|
||||
doTest(getMethod("java.util.Map", "put"));
|
||||
} finally {
|
||||
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public void testArrayAccess() { doTest(getMethod("Market", "getGoods")); }
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.groupSimilar;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.find.findUsages.similarity.JavaUsageSimilarityFeaturesProvider;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.bag.BagsDistanceCalculator;
|
||||
|
||||
public class JavaUsagesBySimilarityTest extends JavaCodeInsightFixtureTestCase {
|
||||
@Override
|
||||
protected String getTestDataPath() {
|
||||
return JavaTestUtil.getJavaTestDataPath() + "/findSimilar/";
|
||||
}
|
||||
|
||||
public void testFeaturesProvider() {
|
||||
myFixture.configureByFile("FeaturesProvider.java");
|
||||
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
|
||||
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
|
||||
assertEquals(1, features.get("{CALL: foo}"));
|
||||
assertEquals(1, features.get("NEW: A"));
|
||||
assertEquals(1, features.get("NEW_KEYWORD"));
|
||||
}
|
||||
|
||||
public void testForFeatures() {
|
||||
myFixture.configureByFile("ForFeatures.java");
|
||||
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
|
||||
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
|
||||
assertEquals(1, features.get("FOR"));
|
||||
assertEquals(1, features.get("FOR_KEYWORD"));
|
||||
assertEquals(1, features.get("GP:VAR: FOR_STATEMENT -1"));
|
||||
}
|
||||
|
||||
public void testAnonymous() {
|
||||
try {
|
||||
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
|
||||
myFixture.configureByFile("Anonymous.java");
|
||||
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
|
||||
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
|
||||
assertEquals(1, features.get("P:anonymousClazz NEW_EXPRESSION -1"));
|
||||
assertEquals(1, features.get("NEXT: {CALL: A.foo() ret:int arg0 type: A} PsiJavaToken:SEMICOLON"));
|
||||
}
|
||||
finally {
|
||||
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public void testIncrement() {
|
||||
try {
|
||||
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
|
||||
myFixture.configureByFile("Increment.java");
|
||||
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
|
||||
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
|
||||
assertEquals(1, features.get("+="));
|
||||
assertEquals(1, features.get("{CALL: A.af() ret:int }"));
|
||||
}
|
||||
finally {
|
||||
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public void testLambda() {
|
||||
try {
|
||||
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
|
||||
myFixture.configureByFile("Lambda.java");
|
||||
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
|
||||
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
|
||||
assertEquals(1, features.get("{CALL: aPackage.A.foo() ret:int arg0 type: <lambda expression>}"));
|
||||
assertEquals(1, features.get("GP:lambda METHOD_CALL_EXPRESSION -1"));
|
||||
}
|
||||
finally {
|
||||
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public void testMethodReference() {
|
||||
try {
|
||||
Registry.get("similarity.find.usages.fast.clustering").setValue(false);
|
||||
myFixture.configureByFile("MethodReference.java");
|
||||
|
||||
PsiElement elementAtCaret = myFixture.getReferenceAtCaretPosition().getElement();
|
||||
final Bag features = new JavaUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret);
|
||||
assertEquals(1, features.get("{CALL: aPackage.A.foo() ret:int arg0 type: <method reference>}"));
|
||||
}
|
||||
finally {
|
||||
Registry.get("similarity.find.usages.fast.clustering").resetToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public void testBag() {
|
||||
final Bag bag = new Bag("a", "b");
|
||||
assertEquals("a : 1\n" +
|
||||
"b : 1\n", bag.toString());
|
||||
bag.add("a");
|
||||
Bag toAdd = new Bag("a", "c");
|
||||
bag.addAll(toAdd);
|
||||
assertEquals("c : 1\n" +
|
||||
"a : 3\n" +
|
||||
"b : 1\n", bag.toString());
|
||||
}
|
||||
|
||||
public void testDistance() {
|
||||
assertEquals(0.6,
|
||||
BagsDistanceCalculator.jaccardSimilarity(new Bag("a", "b", "c", "d", "e", "f", "g", "h"),
|
||||
new Bag("a", "z", "c", "y", "e", "f", "g", "h")));
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ public abstract class ActionPlaces {
|
||||
public static final String METHOD_HIERARCHY_VIEW_TOOLBAR = "MethodHierarchyViewToolbar";
|
||||
public static final String CALL_HIERARCHY_VIEW_POPUP = "CallHierarchyViewPopup";
|
||||
public static final String CALL_HIERARCHY_VIEW_TOOLBAR = "CallHierarchyViewToolbar";
|
||||
public static final String SIMILAR_USAGES_PREVIEW_TOOLBAR = "SimilarUsagesPreviewToolbar";
|
||||
public static final String J2EE_ATTRIBUTES_VIEW_POPUP = "J2EEAttributesViewPopup";
|
||||
public static final String J2EE_VIEW_POPUP = "J2EEViewPopup";
|
||||
public static final String RUNNER_TOOLBAR = "RunnerToolbar";
|
||||
@@ -200,7 +201,7 @@ public abstract class ActionPlaces {
|
||||
UNKNOWN, KEYBOARD_SHORTCUT, MOUSE_SHORTCUT, FORCE_TOUCH,
|
||||
TOOLBAR, MAIN_MENU, MAIN_TOOLBAR, EDITOR_TOOLBAR, TABS_MORE_TOOLBAR, EDITOR_TAB, COMMANDER_TOOLBAR, CONTEXT_TOOLBAR, TOOLWINDOW_TITLE,
|
||||
LEARN_TOOLWINDOW, PROJECT_VIEW_TOOLBAR, STATUS_BAR_PLACE, ACTION_SEARCH, TESTTREE_VIEW_TOOLBAR, TYPE_HIERARCHY_VIEW_TOOLBAR,
|
||||
METHOD_HIERARCHY_VIEW_TOOLBAR, CALL_HIERARCHY_VIEW_TOOLBAR, RUNNER_TOOLBAR, DEBUGGER_TOOLBAR, USAGE_VIEW_TOOLBAR,
|
||||
METHOD_HIERARCHY_VIEW_TOOLBAR, CALL_HIERARCHY_VIEW_TOOLBAR, SIMILAR_USAGES_PREVIEW_TOOLBAR, RUNNER_TOOLBAR, DEBUGGER_TOOLBAR, USAGE_VIEW_TOOLBAR,
|
||||
SHOW_USAGES_POPUP_TOOLBAR,
|
||||
STRUCTURE_VIEW_TOOLBAR, NAVIGATION_BAR_TOOLBAR, TODO_VIEW_TOOLBAR, COMPILER_MESSAGES_TOOLBAR,
|
||||
ANT_MESSAGES_TOOLBAR, ANT_EXPLORER_TOOLBAR, CODE_INSPECTION, JAVADOC_TOOLBAR, JAVADOC_INPLACE_SETTINGS,
|
||||
|
||||
@@ -587,4 +587,4 @@ documentation.file.modification.datetime.label=Modified:
|
||||
documentation.file.scope.label=Scope:
|
||||
documentation.file.vcs.status.label=VCS Status:
|
||||
notification.group.dependencies=Module dependencies analysis completed
|
||||
notification.group.parameter.name.hints=Parameter name hints settings changed
|
||||
notification.group.parameter.name.hints=Parameter name hints settings changed
|
||||
@@ -47,6 +47,7 @@ import com.intellij.usageView.UsageViewContentManager;
|
||||
import com.intellij.usageView.UsageViewUtil;
|
||||
import com.intellij.usages.*;
|
||||
import com.intellij.usages.impl.UsageViewStatisticsCollector;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.CommonProcessors;
|
||||
import com.intellij.util.Processor;
|
||||
@@ -357,6 +358,7 @@ public final class FindUsagesManager {
|
||||
});
|
||||
|
||||
FindUsagesOptions optionsClone = options.clone();
|
||||
ClusteringSearchSession clusteringSearchSession = ClusteringSearchSession.createClusteringSessionIfEnabled();
|
||||
return processor -> {
|
||||
Project project = ReadAction.compute(() -> scopeFile != null ? scopeFile.getProject() : primaryTargets[0].getProject());
|
||||
ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
|
||||
@@ -375,7 +377,11 @@ public final class FindUsagesManager {
|
||||
optionsClone.searchScope = new LocalSearchScope(scopeFile);
|
||||
}
|
||||
Processor<UsageInfo> usageInfoProcessor = new CommonProcessors.UniqueProcessor<>(usageInfo -> {
|
||||
Usage usage = ReadAction.compute(() -> UsageInfoToUsageConverter.convert(primaryElements, usageInfo));
|
||||
Usage usage = ReadAction.compute(
|
||||
() -> clusteringSearchSession != null
|
||||
? UsageInfoToUsageConverter.convertToSimilarUsage(primaryElements, usageInfo, clusteringSearchSession)
|
||||
: UsageInfoToUsageConverter.convert(primaryElements, usageInfo)
|
||||
);
|
||||
return processor.process(usage);
|
||||
});
|
||||
PsiElement[] elements = ArrayUtil.mergeArrays(primaryElements, secondaryElements, PsiElement.ARRAY_FACTORY);
|
||||
|
||||
@@ -2465,6 +2465,7 @@ group.Internal.Java.text=Java
|
||||
action.CollectFUStatisticsAction.text=Collect Statistics from Collector...
|
||||
action.TestParseEventsSchemeAction.text=Test Parsing Events Scheme...
|
||||
action.ValidateLocalHistory.text=Validate Local History Storage
|
||||
action.ShowUsageFeatures.text=Show Usage Features
|
||||
action.FixLineSeparators.text=Fix Line Separators
|
||||
action.PruneEmptyDirectories.text=Prune Empty Directories
|
||||
action.LoadAllVFSContent.text=Load All VFS Stored Files Content
|
||||
|
||||
@@ -399,6 +399,7 @@
|
||||
|
||||
<extensionPoint name="modelScopeItemPresenter" interface="com.intellij.analysis.dialog.ModelScopeItemPresenter" />
|
||||
|
||||
<extensionPoint name="usageFeaturesProvider" interface="com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider" dynamic="true"/>
|
||||
<extensionPoint name="usageFilteringRuleProvider" interface="com.intellij.usages.rules.UsageFilteringRuleProvider" dynamic="true"/>
|
||||
<extensionPoint name="importFilteringRule" interface="com.intellij.usages.rules.ImportFilteringRule" dynamic="true"/>
|
||||
<extensionPoint name="usageGroupingRuleProvider" interface="com.intellij.usages.rules.UsageGroupingRuleProvider" dynamic="true"/>
|
||||
|
||||
@@ -1165,6 +1165,7 @@
|
||||
<postStartupActivity implementation="com.intellij.codeInsight.highlighting.BackgroundHighlighter"/>
|
||||
<usageContextPanelProvider implementation="com.intellij.usages.impl.UsagePreviewPanel$Provider"/>
|
||||
<usageContextPanelProvider implementation="com.intellij.usageView.impl.UsageContextCallHierarchyPanel$Provider"/>
|
||||
<usageContextPanelProvider implementation="com.intellij.find.findUsages.similarity.SimilarUsagesContextPanel$Provider"/>
|
||||
<fileDocumentSynchronizationVetoer implementation="com.intellij.codeInsight.lookup.impl.LookupDocumentSavingVetoer"/>
|
||||
<toolsProvider implementation="com.intellij.tools.LocalToolsProvider"/>
|
||||
|
||||
|
||||
@@ -1707,5 +1707,6 @@
|
||||
<separator/>
|
||||
<add-to-group group-id="Internal" anchor="last"/>
|
||||
</group>
|
||||
<action internal="true" id="ShowUsageFeatures" class ="com.intellij.usages.similarity.internal.ShowUsageFeaturesInternalAction"/>
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.openapi.util.Iconable;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.ui.components.ActionLink;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.IconUtil;
|
||||
import com.intellij.util.PsiNavigateUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
public class LocationLinkComponent {
|
||||
|
||||
@NotNull
|
||||
private final ActionLink myLabel;
|
||||
|
||||
public LocationLinkComponent(@NotNull UsageInfo item) {
|
||||
PsiFile file = item.getFile();
|
||||
myLabel = new ActionLink(Objects.requireNonNull(file).getName(), new AbstractAction() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
PsiNavigateUtil.navigate(item.getElement());
|
||||
}
|
||||
});
|
||||
|
||||
myLabel.setBackground(UIUtil.TRANSPARENT_COLOR);
|
||||
myLabel.setOpaque(false);
|
||||
myLabel.setIcon(IconUtil.getIcon(item.getFile().getVirtualFile(), Iconable.ICON_FLAG_READ_STATUS, item.getProject()));
|
||||
}
|
||||
|
||||
public @NotNull JComponent getComponent() {
|
||||
return myLabel;
|
||||
}
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.ActivityTracker;
|
||||
import com.intellij.ide.IdeBundle;
|
||||
import com.intellij.ide.actions.RefreshAction;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.SimpleToolWindowPanel;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.ui.Gray;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.ScrollPaneFactory;
|
||||
import com.intellij.ui.components.ActionLink;
|
||||
import com.intellij.ui.components.JBPanelWithEmptyText;
|
||||
import com.intellij.ui.components.panels.VerticalLayout;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.UsageView;
|
||||
import com.intellij.usages.impl.UsageViewImpl;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.usages.similarity.clustering.UsageCluster;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.scroll.BoundedRangeModelThresholdListener;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.openapi.actionSystem.ActionPlaces.SIMILAR_USAGES_PREVIEW_TOOLBAR;
|
||||
|
||||
public class MostCommonUsagePatternsComponent extends SimpleToolWindowPanel implements Disposable {
|
||||
private static final int CLUSTER_LIMIT = 20;
|
||||
private final @NotNull Project myProject;
|
||||
private final @NotNull Collection<Collection<? extends UsageGroup>> myGroups;
|
||||
private final @NotNull UsageViewImpl myUsageView;
|
||||
private final @NotNull JBPanelWithEmptyText myMainPanel;
|
||||
private final @NotNull JScrollPane myScrollPane;
|
||||
private @Nullable ClusteringSearchSession mySession;
|
||||
private boolean myIsShowingSimilarUsages;
|
||||
|
||||
private boolean isDisposed = false;
|
||||
private @Nullable BackgroundableProcessIndicator myProcessIndicator;
|
||||
private int myAlreadyRenderedSnippets;
|
||||
|
||||
public MostCommonUsagePatternsComponent(@NotNull UsageViewImpl usageView,
|
||||
@NotNull Collection<@NotNull Collection<? extends UsageGroup>> groups) {
|
||||
super(false);
|
||||
myUsageView = usageView;
|
||||
myProject = usageView.getProject();
|
||||
myGroups = groups;
|
||||
setToolbar((JComponent)createToolbar(SIMILAR_USAGES_PREVIEW_TOOLBAR));
|
||||
myMainPanel = new JBPanelWithEmptyText();
|
||||
myMainPanel.setLayout(new VerticalLayout(0));
|
||||
addMostCommonUsagesForSelectedGroups(myGroups);
|
||||
myScrollPane = ScrollPaneFactory.createScrollPane(myMainPanel);
|
||||
myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
revalidate();
|
||||
setContent(myScrollPane);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ClusteringSearchSession getSession() {
|
||||
if (mySession == null) {
|
||||
mySession = findClusteringSessionInUsageView(myUsageView);
|
||||
}
|
||||
return mySession;
|
||||
}
|
||||
|
||||
public ActionToolbar createToolbar(@NotNull String place) {
|
||||
DefaultActionGroup actionGroup = new DefaultActionGroup();
|
||||
actionGroup.add(
|
||||
new RefreshAction(IdeBundle.messagePointer("action.refresh"), IdeBundle.messagePointer("action.refresh"), AllIcons.Actions.Refresh) {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
myMainPanel.removeAll();
|
||||
myMainPanel.revalidate();
|
||||
myIsShowingSimilarUsages = false;
|
||||
addMostCommonUsagesForSelectedGroups(myGroups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent event) {
|
||||
Presentation presentation = event.getPresentation();
|
||||
presentation.setEnabled(true);
|
||||
}
|
||||
});
|
||||
actionGroup.add(
|
||||
new AnAction(IdeBundle.messagePointer("action.Anonymous.text.back"), IdeBundle.messagePointer("action.Anonymous.text.back"),
|
||||
AllIcons.Actions.Back) {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
if (myIsShowingSimilarUsages) {
|
||||
myIsShowingSimilarUsages = false;
|
||||
ActivityTracker.getInstance().inc();
|
||||
setContent(myScrollPane);
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent event) {
|
||||
Presentation presentation = event.getPresentation();
|
||||
presentation.setEnabled(myIsShowingSimilarUsages);
|
||||
}
|
||||
});
|
||||
ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(place, actionGroup, false);
|
||||
actionToolbar.setTargetComponent(myMainPanel);
|
||||
return actionToolbar;
|
||||
}
|
||||
|
||||
private @NotNull ActionLink createOpenSimilarUsagesActionLink(UsageInfo info, @NotNull Set<SimilarityUsage> usagesToRender) {
|
||||
final ActionLink actionLink = new ActionLink(UsageViewBundle.message("show.0.similar.usages", usagesToRender.size() - 1), e -> {
|
||||
myIsShowingSimilarUsages = true;
|
||||
ActivityTracker.getInstance().inc();
|
||||
final SimilarUsagesComponent mySimilarComponent = new SimilarUsagesComponent(info, this);
|
||||
JScrollPane scroll = ScrollPaneFactory.createScrollPane(mySimilarComponent);
|
||||
final JScrollBar scrollBar = scroll.getVerticalScrollBar();
|
||||
mySimilarComponent.renderOriginalUsage();
|
||||
mySimilarComponent.renderSimilarUsages(usagesToRender);
|
||||
BoundedRangeModelThresholdListener.Companion.install(scrollBar, () -> {
|
||||
mySimilarComponent.renderSimilarUsages(usagesToRender);
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
setContent(scroll);
|
||||
revalidate();
|
||||
});
|
||||
actionLink.setLinkIcon();
|
||||
return actionLink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
isDisposed = true;
|
||||
if (myProcessIndicator != null && myProcessIndicator.isRunning()) {
|
||||
myProcessIndicator.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private @NotNull JPanel createSummaryComponent(@Nullable ClusteringSearchSession session,
|
||||
@NotNull List<UsageCluster> topClusters) {
|
||||
JPanel summaryPanel = new JPanel(new VerticalLayout(0));
|
||||
if (session == null) return summaryPanel;
|
||||
myAlreadyRenderedSnippets = 0;
|
||||
topClusters.stream().skip(myAlreadyRenderedSnippets).limit(CLUSTER_LIMIT).forEach(cluster -> {
|
||||
renderCluster(summaryPanel, cluster);
|
||||
});
|
||||
final JScrollBar verticalScrollBar = myScrollPane.getVerticalScrollBar();
|
||||
BoundedRangeModelThresholdListener.Companion.install(verticalScrollBar, () -> {
|
||||
topClusters.stream().skip(myAlreadyRenderedSnippets).limit(CLUSTER_LIMIT).forEach(cluster -> {
|
||||
renderCluster(summaryPanel, cluster);
|
||||
});
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
return summaryPanel;
|
||||
}
|
||||
|
||||
private void renderCluster(@NotNull JPanel summaryPanel,
|
||||
@NotNull UsageCluster cluster) {
|
||||
final Set<SimilarityUsage> usageFilteredByGroup = new HashSet<>();
|
||||
ApplicationManager.getApplication().runReadAction(() -> {
|
||||
usageFilteredByGroup.addAll(cluster.getUsageFilteredByGroup(myGroups));
|
||||
});
|
||||
SimilarityUsage usage = ContainerUtil.getFirstItem(usageFilteredByGroup);
|
||||
if (usage instanceof UsageInfo2UsageAdapter) {
|
||||
final UsageInfo2UsageAdapter usageInfoAdapter = (UsageInfo2UsageAdapter)usage;
|
||||
UsageCodeSnippetComponent summaryRendererComponent =
|
||||
UsageCodeSnippetComponent.createUsageCodeSnippet(usageInfoAdapter.getUsageInfo());
|
||||
if (!isDisposed) {
|
||||
Disposer.register(this, summaryRendererComponent);
|
||||
}
|
||||
final JPanel headerPanel =
|
||||
createHeaderPanel(usageInfoAdapter.getUsageInfo(), summaryRendererComponent.getEditor().getBackgroundColor());
|
||||
if (usageFilteredByGroup.size() > 1) {
|
||||
headerPanel.add(createOpenSimilarUsagesActionLink(usageInfoAdapter.getUsageInfo(), usageFilteredByGroup));
|
||||
}
|
||||
summaryPanel.add(headerPanel);
|
||||
summaryPanel.add(summaryRendererComponent);
|
||||
myAlreadyRenderedSnippets++;
|
||||
}
|
||||
}
|
||||
|
||||
private void addMostCommonUsagesForSelectedGroups(@NotNull Collection<Collection<? extends UsageGroup>> selectedGroups) {
|
||||
Ref<List<UsageCluster>> sortedClusters = new Ref<>();
|
||||
Task.Backgroundable loadMostCommonUsagePatternsTask =
|
||||
new Task.Backgroundable(myProject, UsageViewBundle.message("loading.most.common.usage.patterns")) {
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (!sortedClusters.isNull()) {
|
||||
myMainPanel.add(createSummaryComponent(getSession(), sortedClusters.get()));
|
||||
myMainPanel.revalidate();
|
||||
myIsShowingSimilarUsages = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
ClusteringSearchSession session = getSession();
|
||||
if (session != null) {
|
||||
final List<UsageCluster> groups = new ArrayList<>();
|
||||
ApplicationManager.getApplication().runReadAction(() -> {
|
||||
groups.addAll(session.getClustersFilteredByGroups(indicator, selectedGroups));
|
||||
});
|
||||
sortedClusters.set(groups);
|
||||
}
|
||||
}
|
||||
};
|
||||
myProcessIndicator = new BackgroundableProcessIndicator(loadMostCommonUsagePatternsTask);
|
||||
ProgressManager.getInstance().runProcessWithProgressAsynchronously(loadMostCommonUsagePatternsTask,
|
||||
myProcessIndicator);
|
||||
}
|
||||
|
||||
public static @Nullable ClusteringSearchSession findClusteringSessionInUsageView(@NotNull UsageView usageView) {
|
||||
Optional<Usage> usage = usageView.getUsages().stream().findFirst();
|
||||
if (usage.isPresent()) {
|
||||
Usage firstUsage = usage.get();
|
||||
if (firstUsage instanceof SimilarityUsage) {
|
||||
return ((SimilarityUsage)firstUsage).getClusteringSession();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @NotNull JPanel createHeaderPanel(@NotNull UsageInfo info, @NotNull Color backgroundColor) {
|
||||
final LocationLinkComponent component = new LocationLinkComponent(info);
|
||||
final JPanel header = new JPanel();
|
||||
header.setBackground(backgroundColor);
|
||||
header.setLayout(new FlowLayout(FlowLayout.LEFT));
|
||||
header.add(component.getComponent());
|
||||
header.setBorder(JBUI.Borders.customLineTop(new JBColor(Gray.xCD, Gray.x51)));
|
||||
return header;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.components.panels.VerticalLayout;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.Collection;
|
||||
|
||||
public class SimilarUsagesComponent extends JPanel implements Disposable {
|
||||
|
||||
public static final int SNIPPET_LIMIT = 10;
|
||||
private int alreadyShown = 0;
|
||||
private final @NotNull UsageInfo myOriginalUsage;
|
||||
|
||||
public SimilarUsagesComponent(@NotNull UsageInfo originalUsage, @NotNull Disposable parent) {
|
||||
myOriginalUsage = originalUsage;
|
||||
setLayout(new VerticalLayout(0));
|
||||
TitlePanel titlePanel = new TitlePanel();
|
||||
titlePanel.getLabel().setHorizontalAlignment(SwingConstants.LEFT);
|
||||
titlePanel.setText(UsageViewBundle.message("label.similar.usages"));
|
||||
add(titlePanel);
|
||||
Disposer.register(parent, this);
|
||||
}
|
||||
|
||||
public void renderSimilarUsages(@NotNull Collection<SimilarityUsage> similarUsagesGroupUsages) {
|
||||
if (alreadyShown < similarUsagesGroupUsages.size() - 1) {
|
||||
similarUsagesGroupUsages.stream().skip(alreadyShown).limit(SNIPPET_LIMIT).forEach(usage -> {
|
||||
final UsageInfo info = ((UsageInfo2UsageAdapter)usage).getUsageInfo();
|
||||
if (myOriginalUsage != info) {
|
||||
renderUsage(info);
|
||||
alreadyShown++;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void renderUsage(@NotNull UsageInfo info) {
|
||||
PsiElement element = info.getElement();
|
||||
if (element == null) return;
|
||||
final UsageCodeSnippetComponent codeSnippet = UsageCodeSnippetComponent.createUsageCodeSnippet(info);
|
||||
Disposer.register(this, codeSnippet);
|
||||
Color color = codeSnippet.getEditor().getBackgroundColor();
|
||||
add(getHeaderPanelForUsage(myOriginalUsage, info, color));
|
||||
add(codeSnippet);
|
||||
}
|
||||
|
||||
public void renderOriginalUsage() {
|
||||
renderUsage(myOriginalUsage);
|
||||
}
|
||||
|
||||
public static @NotNull JPanel getHeaderPanelForUsage(@NotNull UsageInfo originalUsage,
|
||||
@NotNull UsageInfo usageInfo, @NotNull Color backGroundColor) {
|
||||
final JPanel header = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
header.setBackground(backGroundColor);
|
||||
final JComponent link = new LocationLinkComponent(usageInfo).getComponent();
|
||||
header.add(link);
|
||||
if (usageInfo == originalUsage) {
|
||||
final SimpleColoredComponent component = new SimpleColoredComponent();
|
||||
component.append(UsageViewBundle.message("label.the.original.usage"), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
|
||||
header.add(component);
|
||||
}
|
||||
final Color color = new JBColor(Gray.xCD, Gray.x51);
|
||||
header.setBorder(JBUI.Borders.customLineTop(color));
|
||||
return header;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.ui.ScrollPaneFactory;
|
||||
import com.intellij.ui.components.JBPanelWithEmptyText;
|
||||
import com.intellij.ui.components.panels.VerticalLayout;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.usages.PsiElementUsageTarget;
|
||||
import com.intellij.usages.UsageContextPanel;
|
||||
import com.intellij.usages.UsageTarget;
|
||||
import com.intellij.usages.UsageView;
|
||||
import com.intellij.usages.impl.UsageContextPanelBase;
|
||||
import com.intellij.usages.impl.UsageViewImpl;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.usages.similarity.clustering.UsageCluster;
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
|
||||
import com.intellij.util.RunnableCallable;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.ui.scroll.BoundedRangeModelThresholdListener;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.find.findUsages.similarity.MostCommonUsagePatternsComponent.findClusteringSessionInUsageView;
|
||||
|
||||
public class SimilarUsagesContextPanel extends UsageContextPanelBase {
|
||||
|
||||
private @NotNull final UsageViewImpl myUsageView;
|
||||
private @Nullable JScrollPane myScrollPane;
|
||||
private @Nullable ClusteringSearchSession mySession;
|
||||
private @Nullable SimilarUsagesComponent mySimilarUsagesComponent;
|
||||
|
||||
public SimilarUsagesContextPanel(@NotNull Project project,
|
||||
@NotNull UsageViewImpl usageView) {
|
||||
super(project, usageView.getPresentation());
|
||||
myUsageView = usageView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
super.dispose();
|
||||
myScrollPane = null;
|
||||
if (mySimilarUsagesComponent != null) {
|
||||
Disposer.dispose(mySimilarUsagesComponent);
|
||||
}
|
||||
mySimilarUsagesComponent = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateLayoutLater(@Nullable List<? extends UsageInfo> infos) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
removeAll();
|
||||
if (mySimilarUsagesComponent != null) {
|
||||
Disposer.dispose(mySimilarUsagesComponent);
|
||||
}
|
||||
JBPanelWithEmptyText mainPanel = new JBPanelWithEmptyText();
|
||||
add(mainPanel);
|
||||
if (infos == null || infos.size() == 0) return;
|
||||
UsageInfo info = infos.get(0);
|
||||
Ref<PsiElement> psiElementRef = new Ref<>();
|
||||
ReadAction.nonBlocking(new RunnableCallable(() -> psiElementRef.set(info.getElement()))).finishOnUiThread(
|
||||
ModalityState.NON_MODAL, context -> {
|
||||
PsiElement psiElement = psiElementRef.get();
|
||||
if (psiElement == null) return;
|
||||
PsiFile psiFile = psiElement.getContainingFile();
|
||||
if (psiFile == null) return;
|
||||
ClusteringSearchSession session = findSessionInUsages();
|
||||
if (session == null) return;
|
||||
final UsageCluster cluster = session.findCluster(info);
|
||||
if (cluster == null) return;
|
||||
removeAll();
|
||||
mainPanel.setLayout(new VerticalLayout(0));
|
||||
mySimilarUsagesComponent = new SimilarUsagesComponent(info, this);
|
||||
mainPanel.add(mySimilarUsagesComponent);
|
||||
mySimilarUsagesComponent.renderOriginalUsage();
|
||||
mySimilarUsagesComponent.renderSimilarUsages(cluster.getUsages());
|
||||
myScrollPane = ScrollPaneFactory.createScrollPane(mainPanel);
|
||||
myScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
|
||||
BoundedRangeModelThresholdListener.Companion.install(myScrollPane.getVerticalScrollBar(), () -> {
|
||||
mySimilarUsagesComponent.renderSimilarUsages(cluster.getUsages());
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
add(myScrollPane, BorderLayout.CENTER);
|
||||
}).expireWith(this).submit(AppExecutorUtil.getAppExecutorService());
|
||||
}
|
||||
|
||||
private @Nullable ClusteringSearchSession findSessionInUsages() {
|
||||
if (mySession == null) {
|
||||
mySession = findClusteringSessionInUsageView(myUsageView);
|
||||
}
|
||||
return mySession;
|
||||
}
|
||||
|
||||
public static class Provider implements UsageContextPanel.Provider {
|
||||
|
||||
@Override
|
||||
public @NotNull UsageContextPanel create(@NotNull UsageView usageView) {
|
||||
return new SimilarUsagesContextPanel(((UsageViewImpl)usageView).getProject(), (UsageViewImpl)usageView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailableFor(@NotNull UsageView usageView) {
|
||||
if (!ClusteringSearchSession.isSimilarUsagesClusteringEnabled()) return false;
|
||||
UsageTarget[] targets = ((UsageViewImpl)usageView).getTargets();
|
||||
if (targets.length == 0) return false;
|
||||
UsageTarget target = targets[0];
|
||||
if (!(target instanceof PsiElementUsageTarget)) return false;
|
||||
return UsageSimilarityFeaturesProvider.EP_NAME.findFirstSafe(provider -> {
|
||||
return provider.isAvailable((PsiElementUsageTarget)target);
|
||||
}) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getTabTitle() {
|
||||
return UsageViewBundle.message("similar.usages.tab");
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.find.findUsages.similarity;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.EditorSettings;
|
||||
import com.intellij.openapi.editor.LineNumberConverter;
|
||||
import com.intellij.openapi.editor.colors.EditorColors;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.editor.ex.MarkupModelEx;
|
||||
import com.intellij.openapi.editor.markup.HighlighterLayer;
|
||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.ui.EditorTextFieldCellRenderer;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class UsageCodeSnippetComponent extends EditorTextFieldCellRenderer.SimpleWithGutterRendererComponent {
|
||||
public static final int CONTEXT_LINE_NUMBER = 3;
|
||||
|
||||
public UsageCodeSnippetComponent(@NotNull PsiElement element) {
|
||||
super(element.getProject(), element.getLanguage(), false);
|
||||
setupEditor();
|
||||
}
|
||||
|
||||
private void setupEditor() {
|
||||
EditorEx editor = getEditor();
|
||||
JScrollPane scrollPane = editor.getScrollPane();
|
||||
editor.setBorder(JBUI.Borders.empty());
|
||||
editor.getMarkupModel().removeAllHighlighters();
|
||||
editor.getGutterComponentEx().setPaintBackground(false);
|
||||
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||
EditorSettings settings = editor.getSettings();
|
||||
settings.setLineNumbersShown(true);
|
||||
settings.setAdditionalLinesCount(0);
|
||||
settings.setLineCursorWidth(1);
|
||||
settings.setDndEnabled(false);
|
||||
settings.setUseSoftWraps(false);
|
||||
}
|
||||
|
||||
private void highlightRange(@NotNull TextRange rangeToHighlight) {
|
||||
final MarkupModelEx markupModel = getEditor().getMarkupModel();
|
||||
markupModel.addRangeHighlighter(EditorColors.SEARCH_RESULT_ATTRIBUTES, rangeToHighlight.getStartOffset(),
|
||||
rangeToHighlight.getEndOffset(), HighlighterLayer.ADDITIONAL_SYNTAX, HighlighterTargetArea.EXACT_RANGE);
|
||||
}
|
||||
|
||||
public void addUsagePreview(@NotNull UsageInfo usage) {
|
||||
PsiElement element = usage.getElement();
|
||||
if (element == null) return;
|
||||
PsiDocumentManager docManager = PsiDocumentManager.getInstance(element.getProject());
|
||||
Document doc = docManager.getDocument(element.getContainingFile());
|
||||
if (doc == null) return;
|
||||
int usageStartLineNumber = doc.getLineNumber(usage.getElement().getTextRange().getStartOffset());
|
||||
int usageEndLineNumber = doc.getLineNumber(usage.getElement().getTextRange().getEndOffset());
|
||||
final int contextStartLineNumber = Math.max(0, usageStartLineNumber - CONTEXT_LINE_NUMBER);
|
||||
int startOffset = doc.getLineStartOffset(contextStartLineNumber);
|
||||
int endOffset = doc.getLineEndOffset(Math.min(usageEndLineNumber + CONTEXT_LINE_NUMBER, doc.getLineCount() - 1));
|
||||
getEditor().getGutterComponentEx().setLineNumberConverter(
|
||||
contextStartLineNumber == 0 ? LineNumberConverter.DEFAULT : new LineNumberConverter.Increasing() {
|
||||
@Override
|
||||
public Integer convert(@NotNull Editor editor, int lineNumber) {
|
||||
return lineNumber + contextStartLineNumber;
|
||||
}
|
||||
});
|
||||
setText(doc.getText(new TextRange(startOffset, endOffset)));
|
||||
getEditor().getGutterComponentEx().updateUI();
|
||||
highlightRange(element.getTextRange().shiftLeft(startOffset));
|
||||
}
|
||||
|
||||
public static @NotNull UsageCodeSnippetComponent createUsageCodeSnippet(@NotNull UsageInfo usage) {
|
||||
PsiElement element = usage.getElement();
|
||||
assert element != null;
|
||||
UsageCodeSnippetComponent usageCodeSnippetComponent = new UsageCodeSnippetComponent(element);
|
||||
usageCodeSnippetComponent.addUsagePreview(usage);
|
||||
return usageCodeSnippetComponent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.UsageTarget;
|
||||
import com.intellij.usages.rules.UsageFilteringRule;
|
||||
import com.intellij.usages.rules.UsageGroupingRule;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -58,6 +59,9 @@ class UsageNodeTreeBuilder {
|
||||
if (dumb && !DumbService.isDumbAware(rule)) continue;
|
||||
|
||||
List<UsageGroup> groups = rule.getParentGroupsFor(usage, myTargets);
|
||||
if (usage instanceof SimilarityUsage) {
|
||||
((SimilarityUsage)usage).addUsageGroupData(groups);
|
||||
}
|
||||
for (UsageGroup group : groups) {
|
||||
groupNode = groupNode.addOrGetGroup(group, i, edtModelToSwingNodeChangesQueue);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.usages.impl;
|
||||
|
||||
import com.intellij.find.FindManager;
|
||||
import com.intellij.find.FindModel;
|
||||
import com.intellij.find.findUsages.similarity.MostCommonUsagePatternsComponent;
|
||||
import com.intellij.ide.IdeTooltipManager;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
@@ -33,8 +34,10 @@ import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.usages.UsageContextPanel;
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.UsageView;
|
||||
import com.intellij.usages.UsageViewPresentation;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.util.ui.PositionTracker;
|
||||
import com.intellij.util.ui.StatusText;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -45,6 +48,7 @@ import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -60,6 +64,8 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
|
||||
private Pattern myCachedSearchPattern;
|
||||
private Pattern myCachedReplacePattern;
|
||||
private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
|
||||
private @Nullable UsageViewImpl myUsageView;
|
||||
private @Nullable MostCommonUsagePatternsComponent myCommonUsagePatternsComponent;
|
||||
|
||||
public UsagePreviewPanel(@NotNull Project project, @NotNull UsageViewPresentation presentation) {
|
||||
this(project, presentation, false);
|
||||
@@ -72,6 +78,16 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
|
||||
myIsEditor = isEditor;
|
||||
}
|
||||
|
||||
|
||||
public void setUsageView(@Nullable UsageViewImpl usageView) {
|
||||
myUsageView = usageView;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UsageViewImpl getUsageView() {
|
||||
return myUsageView;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Object getData(@NotNull @NonNls String dataId) {
|
||||
@@ -88,11 +104,14 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static class Provider implements UsageContextPanel.Provider {
|
||||
@NotNull
|
||||
@Override
|
||||
public UsageContextPanel create(@NotNull UsageView usageView) {
|
||||
return new UsagePreviewPanel(((UsageViewImpl)usageView).getProject(), usageView.getPresentation(), true);
|
||||
UsagePreviewPanel previewPanel = new UsagePreviewPanel(((UsageViewImpl)usageView).getProject(), usageView.getPresentation(), true);
|
||||
previewPanel.setUsageView((UsageViewImpl)usageView);
|
||||
return previewPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -326,6 +345,7 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
|
||||
public void dispose() {
|
||||
isDisposed = true;
|
||||
releaseEditor();
|
||||
disposeMostCommonUsageComponent();
|
||||
for (Editor editor : EditorFactory.getInstance().getAllEditors()) {
|
||||
if (editor.getProject() == myProject && editor.getUserData(PREVIEW_EDITOR_FLAG) == this) {
|
||||
LOG.error("Editor was not released:" + editor);
|
||||
@@ -333,6 +353,13 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
|
||||
}
|
||||
}
|
||||
|
||||
private void disposeMostCommonUsageComponent() {
|
||||
if (myCommonUsagePatternsComponent != null) {
|
||||
Disposer.dispose(myCommonUsagePatternsComponent);
|
||||
}
|
||||
myCommonUsagePatternsComponent = null;
|
||||
}
|
||||
|
||||
private void releaseEditor() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
if (myEditor != null) {
|
||||
@@ -371,8 +398,30 @@ public class UsagePreviewPanel extends UsageContextPanelBase implements DataProv
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLayoutLater(@Nullable final List<? extends UsageInfo> infos) {
|
||||
public void updateLayoutLater(List<? extends UsageInfo> infos, @NotNull Collection<@NotNull Collection<? extends UsageGroup>> groups) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
if (ClusteringSearchSession.isSimilarUsagesClusteringEnabled() && selectedGroupNodes(infos, groups) && myUsageView != null) {
|
||||
releaseEditor();
|
||||
disposeMostCommonUsageComponent();
|
||||
removeAll();
|
||||
myCommonUsagePatternsComponent = new MostCommonUsagePatternsComponent(myUsageView, groups);
|
||||
add(myCommonUsagePatternsComponent);
|
||||
} else {
|
||||
previewUsages(infos);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean selectedGroupNodes(List<? extends UsageInfo> infos, Collection<Collection<? extends UsageGroup>> groups) {
|
||||
return (infos == null || infos.isEmpty()) && !groups.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateLayoutLater(@Nullable List<? extends UsageInfo> infos) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
previewUsages(infos);
|
||||
}
|
||||
|
||||
private void previewUsages(@Nullable List<? extends UsageInfo> infos) {
|
||||
String cannotPreviewMessage = cannotPreviewMessage(infos);
|
||||
if (cannotPreviewMessage == null) {
|
||||
resetEditor(infos);
|
||||
|
||||
@@ -2230,7 +2230,7 @@ public class UsageViewImpl implements UsageViewEx {
|
||||
return USAGE_INFO_LIST_KEY.getData(DataManager.getInstance().getDataContext(myRootPanel));
|
||||
}
|
||||
|
||||
private @NotNull Collection<Collection<? extends UsageGroup>> getSelectedGroups() {
|
||||
private @NotNull Collection<@NotNull Collection<? extends UsageGroup>> getSelectedGroups() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
Collection<Collection<? extends UsageGroup>> groupPaths = new ArrayList<>();
|
||||
for (TreeNode node : selectedNodes()) {
|
||||
|
||||
@@ -9,6 +9,18 @@
|
||||
<postStartupActivity implementation="com.intellij.usages.impl.rules.UsageFilteringRulesActivity"/>
|
||||
<usages.usageReferenceClassProvider implementation="com.intellij.usages.impl.PsiElementUsageReferenceClassProvider"/>
|
||||
<generatedSourceUsageFilter implementation="com.intellij.usages.impl.rules.DefaultGeneratedSourceUsageFilter"/>
|
||||
<registryKey key="similarity.find.usages.enable" defaultValue="false" description="Enables grouping usages by similarity"/>
|
||||
<registryKey key="similarity.find.usages.groups.threshold" defaultValue="0.9"
|
||||
description="Similarity threshold for groups in find usages"/>
|
||||
<registryKey key="similarity.find.usages.use.parent.features.with.child.number" defaultValue="false"
|
||||
description="Take in account what child number is in parent node"/>
|
||||
<registryKey defaultValue="true" description="Use information from parent nodes" key="similarity.find.usages.use.parent.features"/>
|
||||
<registryKey defaultValue="true" description="Enable similarity clustering for Kotlin usages"
|
||||
key="similarity.find.usages.kotlin.clustering.enable"/>
|
||||
<registryKey defaultValue="true" description="Clustering without resolve" key="similarity.find.usages.fast.clustering"/>
|
||||
<registryKey defaultValue="false" description="Parent statement condition feature"
|
||||
key="similarity.find.usages.parent.statement.condition.feature"/>
|
||||
<registryKey defaultValue="true" description="Use information from sibling nodes" key="similarity.find.usages.use.sibling.features"/>
|
||||
</extensions>
|
||||
<actions resource-bundle="messages.UsageViewBundle">
|
||||
<group id="UsageGroupingActionGroup">
|
||||
|
||||
@@ -11,6 +11,8 @@ action.UsageFiltering.WriteAccess.text=Show Write Access
|
||||
action.UsageFiltering.WriteAccess.description=Show usages that write the corresponding target
|
||||
action.UsageFiltering.GeneratedCode.text=Show Results In Generated Code
|
||||
action.UsageFiltering.Imports.text=Show Import Statements
|
||||
0.similar.results.found.in.project={0, number} similar {0, choice, 1#result|2#results} found in project
|
||||
similar.usages.tab=Similar Usages
|
||||
group.UsageGroupingActionGroup.text=Usage View Grouping
|
||||
action.group.by.title=Group By
|
||||
action.group.by.file.structure=File Structure
|
||||
@@ -127,4 +129,12 @@ usage.view.results.node.prefix=[{0}] {1}
|
||||
usage.view.results.node.scope.in=in
|
||||
references.to.be.changed=References to be changed {0}
|
||||
occurrences.found.in.comments.strings.and.non.java.files=Occurrences found in comments, strings and non-code files
|
||||
occurrences.found.in.comments.strings.non.java.files.and.generated.code=Occurrences found in comments, strings, non-code files and generated code
|
||||
occurrences.found.in.comments.strings.non.java.files.and.generated.code=Occurrences found in comments, strings, non-code files and generated code
|
||||
label.similar.usages=Similar usages
|
||||
label.the.original.usage=(the original usage)
|
||||
loading.most.common.usage.patterns=Loading most common usage patterns
|
||||
show.0.similar.usages=Show {0, number} similar {0, choice, 1#usage|2#usages}
|
||||
no.similar.usages.found=No Similar usages found
|
||||
back=Back
|
||||
refresh=Refresh
|
||||
action.calculating.usage.features=Calculating Usage Features
|
||||
@@ -7,6 +7,13 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.SmartPointerManager;
|
||||
import com.intellij.psi.SmartPsiElementPointer;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.usages.similarity.clustering.UsageCluster;
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityReadWriteUsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsageInfo2UsageAdapter;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -109,6 +116,33 @@ public final class UsageInfoToUsageConverter {
|
||||
return new UsageInfo2UsageAdapter(usageInfo);
|
||||
}
|
||||
|
||||
|
||||
public static @NotNull Usage convertToSimilarUsage(PsiElement @NotNull [] primaryElements,
|
||||
@NotNull UsageInfo usageInfo,
|
||||
@NotNull ClusteringSearchSession session) {
|
||||
PsiElement usageElement = usageInfo.getElement();
|
||||
if (usageElement != null && primaryElements.length != 0) {
|
||||
Bag features = new Bag();
|
||||
UsageSimilarityFeaturesProvider.EP_NAME.forEachExtensionSafe(provider -> {
|
||||
features.addAll(provider.getFeatures(usageElement));
|
||||
});
|
||||
final UsageCluster cluster = session.findOrCreateCluster(features);
|
||||
if (cluster != null) {
|
||||
final ReadWriteAccessDetector.Access readWriteAccess = ReadWriteUtil.getReadWriteAccess(primaryElements, usageElement);
|
||||
final Usage similarityUsageAdapter;
|
||||
if (readWriteAccess != null) {
|
||||
similarityUsageAdapter = new SimilarityReadWriteUsageInfo2UsageAdapter(usageInfo, readWriteAccess, features, session, cluster);
|
||||
}
|
||||
else {
|
||||
similarityUsageAdapter = new SimilarityUsageInfo2UsageAdapter(usageInfo, features, session, cluster);
|
||||
}
|
||||
cluster.addUsage((SimilarityUsage)similarityUsageAdapter);
|
||||
return similarityUsageAdapter;
|
||||
}
|
||||
}
|
||||
return convert(primaryElements, usageInfo);
|
||||
}
|
||||
|
||||
public static Usage @NotNull [] convert(@NotNull TargetElementsDescriptor descriptor, UsageInfo @NotNull [] usageInfos) {
|
||||
Usage[] usages = new Usage[usageInfos.length];
|
||||
for (int i = 0; i < usages.length; i++) {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.bag;
|
||||
|
||||
import com.intellij.util.containers.Interner;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class Bag {
|
||||
|
||||
public static final Bag EMPTY_BAG = new Bag();
|
||||
private static final Interner<String> ourInterner = Interner.createWeakInterner();
|
||||
private final @NotNull Object2IntMap<String> myBag;
|
||||
private int myCardinality;
|
||||
|
||||
public Bag() {
|
||||
myBag = new Object2IntOpenHashMap<>();
|
||||
myBag.defaultReturnValue(0);
|
||||
}
|
||||
|
||||
public Bag(String @NotNull ... words) {
|
||||
myBag = new Object2IntOpenHashMap<>();
|
||||
myBag.defaultReturnValue(0);
|
||||
for (String word : words) {
|
||||
final String internedWord = ourInterner.intern(word);
|
||||
myBag.put(internedWord, myBag.getInt(internedWord) + 1);
|
||||
myCardinality += 1;
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull Object2IntMap<String> getBag() {
|
||||
return myBag;
|
||||
}
|
||||
|
||||
public void addAll(@NotNull Bag bag) {
|
||||
for (Object2IntMap.Entry<String> entry : bag.myBag.object2IntEntrySet()) {
|
||||
final String key = entry.getKey();
|
||||
myBag.put(key, myBag.getInt(key) + entry.getIntValue());
|
||||
myCardinality += entry.getIntValue();
|
||||
}
|
||||
}
|
||||
|
||||
public int getCardinality() {
|
||||
return myCardinality;
|
||||
}
|
||||
|
||||
public void add(@NotNull String key) {
|
||||
final String internedKey = ourInterner.intern(key);
|
||||
myBag.put(internedKey, myBag.getInt(internedKey) + 1);
|
||||
}
|
||||
|
||||
public int get(@NotNull String word) {
|
||||
return myBag.getInt(word);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return myBag.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder toReturn = new StringBuilder();
|
||||
for (Object2IntMap.Entry<String> entry : getBag().object2IntEntrySet()) {
|
||||
toReturn.append(entry.getKey()).append(" : ").append(entry.getIntValue()).append("\n");
|
||||
}
|
||||
return toReturn.toString();
|
||||
}
|
||||
|
||||
public static int intersectionSize(@NotNull Bag bag1, @NotNull Bag bag2) {
|
||||
Bag toIterate = (bag1.getBag().size() > bag2.getBag().size()) ? bag2 : bag1;
|
||||
Bag toProcess = (bag1.getBag().size() > bag2.getBag().size()) ? bag1 : bag2;
|
||||
int cardinality = 0;
|
||||
for (Object2IntMap.Entry<String> entry : toIterate.getBag().object2IntEntrySet()) {
|
||||
cardinality += Math.min(toProcess.getBag().getInt(entry.getKey()), toIterate.getBag().getInt(entry.getKey()));
|
||||
}
|
||||
return cardinality;
|
||||
}
|
||||
|
||||
public static @NotNull Bag concat(Bag @NotNull ... bags) {
|
||||
Bag bag = new Bag();
|
||||
for (Bag it : bags) {
|
||||
bag.addAll(it);
|
||||
}
|
||||
return bag;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.bag;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class BagsDistanceCalculator {
|
||||
private final double mySimilarityThreshold;
|
||||
private final @NotNull Bag myExistingUsageFeatures;
|
||||
|
||||
public BagsDistanceCalculator(@NotNull Bag existingUsageFeatures, double similarityThreshold) {
|
||||
this.myExistingUsageFeatures = existingUsageFeatures;
|
||||
this.mySimilarityThreshold = similarityThreshold;
|
||||
}
|
||||
|
||||
public double similarity(@NotNull Bag usageFeaturesToCheck) {
|
||||
final double similarity = jaccardSimilarity(usageFeaturesToCheck, myExistingUsageFeatures);
|
||||
if (similarity >= mySimilarityThreshold) {
|
||||
return similarity;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
public static double jaccardSimilarity(@NotNull Bag bag1, @NotNull Bag bag2) {
|
||||
final int cardinality1 = bag1.getCardinality();
|
||||
final int cardinality2 = bag2.getCardinality();
|
||||
int intersectionSize = Bag.intersectionSize(bag1, bag2);
|
||||
return intersectionSize / (double)(cardinality1 + cardinality2 - intersectionSize);
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.clustering;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.bag.BagsDistanceCalculator;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* Does usage clustering during the find usage process. Clusters are used on find usages results presentation.
|
||||
*/
|
||||
public class ClusteringSearchSession {
|
||||
private final @NotNull List<UsageCluster> myClusters;
|
||||
private final double mySimilarityThreshold;
|
||||
|
||||
public ClusteringSearchSession() {
|
||||
myClusters = new CopyOnWriteArrayList<>();
|
||||
mySimilarityThreshold = Registry.doubleValue("similarity.find.usages.groups.threshold");
|
||||
}
|
||||
|
||||
public @NotNull List<UsageCluster> getClusters() {
|
||||
return myClusters;
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
public synchronized @Nullable UsageCluster findOrCreateCluster(@NotNull Bag usageFeatures) {
|
||||
if (usageFeatures.getBag().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
UsageCluster cluster = getTheMostSimilarCluster(usageFeatures);
|
||||
if (cluster == null) {
|
||||
cluster = createNewCluster();
|
||||
}
|
||||
return cluster;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is designed to use from {@link com.intellij.usages.UsageContextPanel#updateLayout(List)}
|
||||
*/
|
||||
public @Nullable UsageCluster findCluster(@Nullable UsageInfo usageInfo) {
|
||||
for (UsageCluster cluster : myClusters) {
|
||||
for (SimilarityUsage usage : cluster.getUsages()) {
|
||||
if (usage instanceof UsageInfo2UsageAdapter && ((UsageInfo2UsageAdapter)usage).getUsageInfo().equals(usageInfo)) {
|
||||
return cluster;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
@RequiresReadLock
|
||||
public @NotNull List<UsageCluster> getClustersFilteredByGroups(@NotNull ProgressIndicator indicator,
|
||||
@NotNull Collection<Collection<? extends UsageGroup>> selectedGroups) {
|
||||
//create new ArrayList from clusters to avoid a comparator contract violation during search
|
||||
return new ArrayList<>(getClusters()).stream()
|
||||
.sorted((o1, o2) -> {
|
||||
indicator.checkCanceled();
|
||||
return Integer.compare(o2.getUsageFilteredByGroup(selectedGroups).size(), o1.getUsageFilteredByGroup(selectedGroups).size());
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private @NotNull UsageCluster createNewCluster() {
|
||||
final UsageCluster newCluster = new UsageCluster(myClusters.size());
|
||||
myClusters.add(newCluster);
|
||||
return newCluster;
|
||||
}
|
||||
|
||||
private @Nullable UsageCluster getTheMostSimilarCluster(@NotNull Bag features) {
|
||||
if (myClusters.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
UsageCluster mostUsageCluster = null;
|
||||
double maxSimilarity = Double.MIN_VALUE;
|
||||
for (UsageCluster currentGroup : myClusters) {
|
||||
double similarity = findMinimalSimilarity(currentGroup, features, mySimilarityThreshold);
|
||||
if (maxSimilarity < similarity) {
|
||||
if (similarity == 1.0) {
|
||||
return currentGroup;
|
||||
}
|
||||
mostUsageCluster = currentGroup;
|
||||
maxSimilarity = similarity;
|
||||
}
|
||||
}
|
||||
return mostUsageCluster;
|
||||
}
|
||||
|
||||
private static double findMinimalSimilarity(@NotNull UsageCluster usageCluster, Bag newUsageFeatures, double threshold) {
|
||||
final BagsDistanceCalculator bagsDistanceCalculator = new BagsDistanceCalculator(newUsageFeatures, threshold);
|
||||
double min = Double.MAX_VALUE;
|
||||
for (SimilarityUsage usage : usageCluster.getUsages()) {
|
||||
final double similarity = bagsDistanceCalculator.similarity(usage.getFeatures());
|
||||
if (similarity < min) {
|
||||
min = similarity;
|
||||
}
|
||||
if (min == 0.0) {
|
||||
return min;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ClusteringSearchSession createClusteringSessionIfEnabled() {
|
||||
return isSimilarUsagesClusteringEnabled() ? (new ClusteringSearchSession()) : null;
|
||||
}
|
||||
|
||||
public static boolean isSimilarUsagesClusteringEnabled() {
|
||||
return Registry.is("similarity.find.usages.enable") && ApplicationManager.getApplication().isInternal();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.clustering;
|
||||
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.similarity.usageAdapter.SimilarityUsage;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class UsageCluster {
|
||||
|
||||
private final int myIndex;
|
||||
private final Set<SimilarityUsage> myUsages = new CopyOnWriteArraySet<>();
|
||||
|
||||
public UsageCluster(int index) {
|
||||
this.myIndex = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return myIndex;
|
||||
}
|
||||
|
||||
public synchronized void addUsage(@NotNull SimilarityUsage usage) {
|
||||
myUsages.add(usage);
|
||||
}
|
||||
|
||||
public @NotNull Set<SimilarityUsage> getUsages() {
|
||||
return myUsages;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof UsageCluster)) return false;
|
||||
|
||||
return myIndex == ((UsageCluster)o).myIndex;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<SimilarityUsage> getUsageFilteredByGroup(@NotNull Collection<@NotNull Collection<? extends UsageGroup>> groups) {
|
||||
return getUsages().stream().filter(e -> belongsToGroup(e, groups)).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
public static boolean belongsToGroup(@NotNull SimilarityUsage info,
|
||||
@NotNull Collection<@NotNull Collection<? extends UsageGroup>> selectedGroupPaths) {
|
||||
for (Collection<? extends UsageGroup> selectedGroupPath : selectedGroupPaths) {
|
||||
if (ContainerUtil.intersection(selectedGroupPath, info.getUsageGroupData()).size() == selectedGroupPath.size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{" +
|
||||
"id=" + myIndex + "\n" +
|
||||
", myUsages=" + myUsages.stream().map(usage -> usage.toString()).collect(Collectors.joining(",\n")) +
|
||||
"}\n";
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.features;
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.usages.PsiElementUsageTarget;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ApiStatus.Experimental
|
||||
public interface UsageSimilarityFeaturesProvider {
|
||||
@ApiStatus.Experimental
|
||||
ExtensionPointName<UsageSimilarityFeaturesProvider> EP_NAME = ExtensionPointName.create("com.intellij.usageFeaturesProvider");
|
||||
|
||||
@RequiresReadLock
|
||||
@RequiresBackgroundThread
|
||||
@NotNull Bag getFeatures(@NotNull PsiElement usage);
|
||||
|
||||
@RequiresReadLock
|
||||
boolean isAvailable(@NotNull PsiElementUsageTarget usageTarget);
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.features;
|
||||
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class UsageSimilarityFeaturesRecorder {
|
||||
private final @NotNull Bag myFeatures;
|
||||
private final @Nullable PsiElement myInitialElement;
|
||||
|
||||
public UsageSimilarityFeaturesRecorder(@Nullable PsiElement initialContext) {
|
||||
myFeatures = new Bag();
|
||||
myInitialElement = initialContext;
|
||||
}
|
||||
|
||||
public @NotNull Bag getFeatures() {
|
||||
return myFeatures;
|
||||
}
|
||||
|
||||
public void addFeature(@NotNull String tokenFeature) {
|
||||
myFeatures.add(tokenFeature);
|
||||
}
|
||||
|
||||
public void addAllFeatures(@NotNull PsiElement element, @Nullable String tokenFeature) {
|
||||
if (tokenFeature != null) {
|
||||
myFeatures.add(tokenFeature);
|
||||
if (element != myInitialElement) {
|
||||
addStructuralFeatures(element, tokenFeature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addParentFeatures(@NotNull PsiElement element, @Nullable String tokenFeature) {
|
||||
if (!Registry.is("similarity.find.usages.use.parent.features")) {
|
||||
return;
|
||||
}
|
||||
PsiElement parent = element.getParent();
|
||||
PsiElement grandParent;
|
||||
PsiElement grandGrandParent;
|
||||
if (parent != null) {
|
||||
grandParent = parent.getParent();
|
||||
if (grandParent != null) {
|
||||
myFeatures.add("P:" + tokenFeature + " " + PsiUtilCore.getElementType(parent) + " " + getChildNumber(parent, element));
|
||||
grandGrandParent = grandParent.getParent();
|
||||
if (grandGrandParent != null) {
|
||||
myFeatures.add(
|
||||
"GP:" + tokenFeature + " " + PsiUtilCore.getElementType(grandParent) + " " + getChildNumber(grandParent, parent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addSiblingFeatures(@NotNull PsiElement element, @Nullable String tokenFeature) {
|
||||
if (!Registry.is("similarity.find.usages.use.sibling.features")) {
|
||||
return;
|
||||
}
|
||||
myFeatures.add("PREV: " + getPrevMeaningfulSibling(element) + " " + tokenFeature);
|
||||
myFeatures.add("NEXT: " + tokenFeature + " " + getNextMeaningfulSibling(element));
|
||||
}
|
||||
|
||||
public void addStructuralFeatures(@NotNull PsiElement element, @NotNull String tokenFeature) {
|
||||
addParentFeatures(element, tokenFeature);
|
||||
addSiblingFeatures(element, tokenFeature);
|
||||
}
|
||||
|
||||
private static int getChildNumber(@NotNull PsiElement parent, @NotNull PsiElement child) {
|
||||
if (!Registry.is("similarity.find.usages.use.parent.features.with.child.number")) return -1;
|
||||
PsiElement[] children = Arrays.stream(parent.getChildren()).filter(e -> !(e instanceof PsiWhiteSpace)).toArray(PsiElement[]::new);
|
||||
for (int i = 0; i < children.length; i++) {
|
||||
if (children[i] == child) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement getNextMeaningfulSibling(@NotNull PsiElement element) {
|
||||
PsiElement nextSibling = element.getNextSibling();
|
||||
if (nextSibling != null) {
|
||||
while (nextSibling instanceof PsiWhiteSpace || nextSibling instanceof PsiComment) {
|
||||
nextSibling = nextSibling.getNextSibling();
|
||||
}
|
||||
}
|
||||
return nextSibling;
|
||||
}
|
||||
|
||||
private static @Nullable PsiElement getPrevMeaningfulSibling(@NotNull PsiElement element) {
|
||||
PsiElement prevSibling = element.getPrevSibling();
|
||||
if (prevSibling != null) {
|
||||
while (prevSibling instanceof PsiWhiteSpace || prevSibling instanceof PsiComment) {
|
||||
prevSibling = prevSibling.getPrevSibling();
|
||||
}
|
||||
}
|
||||
return prevSibling;
|
||||
}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.internal;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.fileTypes.PlainTextFileType;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.Task;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.util.Ref;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.usageView.UsageViewBundle;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider;
|
||||
import com.intellij.util.LocalTimeCounter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.intellij.openapi.application.ActionsKt.runWriteAction;
|
||||
|
||||
public class ShowUsageFeaturesInternalAction extends AnAction {
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
Editor editor = e.getData(CommonDataKeys.EDITOR);
|
||||
if (editor == null) return;
|
||||
final PsiFile file = e.getData(CommonDataKeys.PSI_FILE);
|
||||
if (file == null) return;
|
||||
final Project project = e.getProject();
|
||||
assert project != null;
|
||||
final VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
|
||||
assert projectDir != null;
|
||||
PsiDirectory directory = PsiManager.getInstance(project).findDirectory(projectDir);
|
||||
assert directory != null;
|
||||
final Ref<PsiFile> featuresDump = new Ref<>();
|
||||
calculateFeaturesForUsage(editor, file, project, featuresDump);
|
||||
createFileWithFeatures(directory, featuresDump).navigate(true);
|
||||
}
|
||||
|
||||
private static void calculateFeaturesForUsage(@NotNull Editor editor,
|
||||
@NotNull PsiFile file,
|
||||
@NotNull Project project,
|
||||
@NotNull Ref<PsiFile> featuresDump) {
|
||||
ProgressManager.getInstance().run(new Task.Modal(project, UsageViewBundle.message("action.calculating.usage.features"), true) {
|
||||
@Override
|
||||
public void run(@NotNull ProgressIndicator indicator) {
|
||||
final Bag features = new Bag();
|
||||
ApplicationManager.getApplication().runReadAction(() -> {
|
||||
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
|
||||
if (element != null) {
|
||||
UsageSimilarityFeaturesProvider.EP_NAME.forEachExtensionSafe(provider -> {
|
||||
features.addAll(provider.getFeatures(element));
|
||||
});
|
||||
}
|
||||
});
|
||||
featuresDump.set(
|
||||
PsiFileFactory.getInstance(project).createFileFromText("features" + System.currentTimeMillis(), PlainTextFileType.INSTANCE,
|
||||
StringUtil.join(
|
||||
features.getBag().object2IntEntrySet(), ",\n"),
|
||||
LocalTimeCounter.currentTime(), false, false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static @NotNull PsiFile createFileWithFeatures(@NotNull PsiDirectory directory, @NotNull Ref<PsiFile> featuresDumpFile) {
|
||||
return runWriteAction(() ->
|
||||
{
|
||||
final PsiElement featureDumpFile = directory.add(featuresDumpFile.get());
|
||||
assert featureDumpFile instanceof PsiFile;
|
||||
return (PsiFile)featureDumpFile;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.usageAdapter;
|
||||
|
||||
import com.intellij.codeInsight.highlighting.ReadWriteAccessDetector;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.ReadWriteAccessUsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.usages.similarity.clustering.UsageCluster;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SimilarityReadWriteUsageInfo2UsageAdapter extends ReadWriteAccessUsageInfo2UsageAdapter implements SimilarityUsage {
|
||||
private final @NotNull Bag myFeatures;
|
||||
private final @NotNull ClusteringSearchSession mySession;
|
||||
|
||||
private final @Nullable UsageCluster myCluster;
|
||||
private final @NotNull List<UsageGroup> myGroups;
|
||||
|
||||
public SimilarityReadWriteUsageInfo2UsageAdapter(@NotNull UsageInfo usageInfo,
|
||||
@NotNull ReadWriteAccessDetector.Access rwAccess,
|
||||
@NotNull Bag features,
|
||||
@NotNull ClusteringSearchSession session, @Nullable UsageCluster cluster) {
|
||||
super(usageInfo, rwAccess);
|
||||
myFeatures = features;
|
||||
mySession = session;
|
||||
myGroups = new ArrayList<>();
|
||||
myCluster = cluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Bag getFeatures() {
|
||||
return myFeatures;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ClusteringSearchSession getClusteringSession() {
|
||||
return mySession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable UsageCluster getCluster() {
|
||||
return myCluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<UsageGroup> getUsageGroupData() {
|
||||
return myGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUsageGroupData(@NotNull List<UsageGroup> groups) {
|
||||
myGroups.addAll(groups);
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.usageAdapter;
|
||||
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.usages.similarity.clustering.UsageCluster;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This interface marks UsageInfo2UsageAdapter implementation as a subject of clustering
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
public interface SimilarityUsage {
|
||||
|
||||
/**
|
||||
* Each usage provides a {@link Bag} of features (string tokens) collected by {@link com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider} implementation
|
||||
*
|
||||
* @return a Bag of words {@link Bag} with string features related to usage context usage
|
||||
*/
|
||||
@NotNull Bag getFeatures();
|
||||
|
||||
@NotNull ClusteringSearchSession getClusteringSession();
|
||||
|
||||
@Nullable UsageCluster getCluster();
|
||||
|
||||
/**
|
||||
* Pre-cached {@link UsageGroup} data from {@link com.intellij.usages.rules.UsageGroupingRule} which is calculated on Usage tree building
|
||||
*
|
||||
* @return list of usage {@link UsageGroup} obtained from {@link com.intellij.usages.rules.UsageGroupingRule}
|
||||
*/
|
||||
@NotNull List<UsageGroup> getUsageGroupData();
|
||||
|
||||
/**
|
||||
* This method is use for adding group data
|
||||
*
|
||||
* @param groups
|
||||
*/
|
||||
void addUsageGroupData(@NotNull List<UsageGroup> groups);
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.usages.similarity.usageAdapter;
|
||||
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.UsageGroup;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.usages.similarity.bag.Bag;
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession;
|
||||
import com.intellij.usages.similarity.clustering.UsageCluster;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SimilarityUsageInfo2UsageAdapter extends UsageInfo2UsageAdapter implements SimilarityUsage {
|
||||
|
||||
private final @NotNull Bag myFeatures;
|
||||
private final @NotNull ClusteringSearchSession mySession;
|
||||
private final @NotNull List<UsageGroup> myGroups;
|
||||
private final @Nullable UsageCluster myCluster;
|
||||
|
||||
public SimilarityUsageInfo2UsageAdapter(@NotNull UsageInfo usageInfo, @NotNull Bag features, @NotNull ClusteringSearchSession session,
|
||||
@Nullable UsageCluster cluster) {
|
||||
super(usageInfo);
|
||||
myFeatures = features;
|
||||
mySession = session;
|
||||
myGroups = new ArrayList<>();
|
||||
myCluster = cluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ClusteringSearchSession getClusteringSession() {
|
||||
return mySession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable UsageCluster getCluster() {
|
||||
return myCluster;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<UsageGroup> getUsageGroupData() {
|
||||
return myGroups;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUsageGroupData(@NotNull List<UsageGroup> groups) {
|
||||
myGroups.addAll(groups);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Bag getFeatures() {
|
||||
return myFeatures;
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
package org.jetbrains.kotlin.idea.findUsages.similarity
|
||||
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.usages.similarity.bag.Bag
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesRecorder
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinSimilarityPropertiesExtractor(context: PsiElement) : KtTreeVisitorVoid() {
|
||||
private val myUsageSimilarityFeaturesRecorder =
|
||||
UsageSimilarityFeaturesRecorder(context)
|
||||
private val myContext = context
|
||||
|
||||
fun getFeatures(): Bag {
|
||||
myContext.accept(this)
|
||||
return myUsageSimilarityFeaturesRecorder.features
|
||||
}
|
||||
|
||||
override fun visitStringTemplateExpression(expression: KtStringTemplateExpression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "literal: string")
|
||||
super.visitStringTemplateExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitProperty(property: KtProperty) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(property, "VAR: ")
|
||||
super.visitProperty(property)
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(expression: KtBinaryExpression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.operationToken.toString())
|
||||
super.visitBinaryExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitIsExpression(expression: KtIsExpression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, "instanceof")
|
||||
super.visitIsExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(lambdaExpression, "lambda")
|
||||
super.visitLambdaExpression(lambdaExpression)
|
||||
}
|
||||
|
||||
override fun visitUnaryExpression(expression: KtUnaryExpression) {
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, expression.operationToken.toString())
|
||||
super.visitUnaryExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitReferenceExpression(expression: KtReferenceExpression) {
|
||||
var feature = "VAR:"
|
||||
if (!theFirstReferenceInQualifiedExpression(expression)) {
|
||||
if (expression is KtNameReferenceExpression) {
|
||||
feature = "{CALL: ${expression.getReferencedName()}}"
|
||||
}
|
||||
}
|
||||
myUsageSimilarityFeaturesRecorder.addAllFeatures(expression, feature)
|
||||
super.visitReferenceExpression(expression)
|
||||
}
|
||||
|
||||
private fun theFirstReferenceInQualifiedExpression(expression: KtReferenceExpression) =
|
||||
(expression.parent is KtQualifiedExpression && expression.parent.firstChild == expression)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
package org.jetbrains.kotlin.idea.findUsages.similarity
|
||||
|
||||
import com.intellij.openapi.util.Condition
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.usages.PsiElementUsageTarget
|
||||
import com.intellij.usages.similarity.bag.Bag
|
||||
import com.intellij.usages.similarity.features.UsageSimilarityFeaturesProvider
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.psi.KtStatementExpression
|
||||
|
||||
class KotlinUsageSimilarityFeaturesProvider : UsageSimilarityFeaturesProvider {
|
||||
@RequiresReadLock
|
||||
@RequiresBackgroundThread
|
||||
override fun getFeatures(usage: PsiElement): Bag {
|
||||
val properties = Bag()
|
||||
if (!Registry.`is`("similarity.find.usages.kotlin.clustering.enable")) {
|
||||
return properties
|
||||
}
|
||||
val statement = getContext(usage)
|
||||
if (statement != null) {
|
||||
properties.addAll(KotlinSimilarityPropertiesExtractor(statement).getFeatures())
|
||||
}
|
||||
|
||||
return properties
|
||||
}
|
||||
|
||||
@RequiresReadLock
|
||||
override fun isAvailable(usageTarget: PsiElementUsageTarget): Boolean {
|
||||
return usageTarget.element is KtFunction
|
||||
}
|
||||
|
||||
fun getContext(element: PsiElement): PsiElement? {
|
||||
return PsiTreeUtil.findFirstParent(
|
||||
element,
|
||||
false,
|
||||
Condition { e: PsiElement? -> e is KtStatementExpression || e?.parent is KtBlockExpression },
|
||||
)
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.findUsages
|
||||
|
||||
import com.intellij.testFramework.TestDataPath
|
||||
import com.intellij.testFramework.assertEqualsToFile
|
||||
import com.intellij.usages.UsageInfoToUsageConverter
|
||||
import com.intellij.usages.similarity.clustering.ClusteringSearchSession
|
||||
import org.jetbrains.kotlin.idea.findUsages.similarity.KotlinUsageSimilarityFeaturesProvider
|
||||
import org.jetbrains.kotlin.idea.test.TestRoot
|
||||
import org.jetbrains.kotlin.test.TestMetadata
|
||||
import org.junit.internal.runners.JUnit38ClassRunner
|
||||
import org.junit.runner.RunWith
|
||||
import java.io.File
|
||||
|
||||
@TestRoot("idea/tests")
|
||||
@TestDataPath("\$CONTENT_ROOT")
|
||||
@TestMetadata("testData/findUsages/similarity")
|
||||
@RunWith(
|
||||
JUnit38ClassRunner::class
|
||||
)
|
||||
class KotlinGroupUsagesBySimilarityTest : AbstractFindUsagesTest() {
|
||||
fun testSimpleFeatures() {
|
||||
myFixture.configureByFile(getTestName(true)+".kt")
|
||||
val elementAtCaret = myFixture.getReferenceAtCaretPosition()!!.element
|
||||
val features = KotlinUsageSimilarityFeaturesProvider().getFeatures(elementAtCaret)
|
||||
assertEquals(1, features["VAR: "])
|
||||
assertEquals(1, features["{CALL: inc}"])
|
||||
}
|
||||
|
||||
@TestMetadata("general.kt")
|
||||
@Throws(Exception::class)
|
||||
fun testGeneral() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
@TestMetadata("chainCalls")
|
||||
fun testChainCalls() {
|
||||
myFixture.configureByFile(getTestName(true)+".kt")
|
||||
val findUsages = findUsages(myFixture.elementAtCaret, null, false, myFixture.project)
|
||||
val session = ClusteringSearchSession();
|
||||
findUsages.forEach { usage -> UsageInfoToUsageConverter.convertToSimilarUsage(arrayOf(myFixture.elementAtCaret), usage, session) }
|
||||
val file = File(testDataDirectory, getTestName(true)+".results.txt")
|
||||
assertEqualsToFile("", file, session.clusters.toString())
|
||||
}
|
||||
|
||||
fun testListAdd() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
fun testFilter() {
|
||||
doTest()
|
||||
}
|
||||
|
||||
private fun doTest() {
|
||||
myFixture.configureByFile(getTestName(true)+".kt")
|
||||
val findUsages = findUsages(myFixture.elementAtCaret, null, false, myFixture.project)
|
||||
val session = ClusteringSearchSession();
|
||||
findUsages.forEach { usage -> UsageInfoToUsageConverter.convertToSimilarUsage(arrayOf(myFixture.elementAtCaret), usage, session) }
|
||||
val file = File(testDataDirectory, getTestName(true) + ".results.txt")
|
||||
assertEqualsToFile("", file, session.clusters.toString())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
class PsiElement() {
|
||||
|
||||
val operationReference = PsiElement()
|
||||
lateinit var myTextRange: TextRange
|
||||
|
||||
public constructor(textRange: TextRange) : this() {
|
||||
myTextRange = textRange;
|
||||
}
|
||||
|
||||
public fun getTex<caret>tRange(): TextRange {
|
||||
return myTextRange;
|
||||
}
|
||||
}
|
||||
|
||||
class TextRange(val startOffset: Int, val endOffset: Int) {
|
||||
|
||||
fun containsOffset(caretOffset: Int): Boolean {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ToTest {
|
||||
fun f() {
|
||||
val element = PsiElement(TextRange(1, 5))
|
||||
val opRef = element.operationReference
|
||||
if (!opRef.getTextRange().containsOffset(2)) return
|
||||
|
||||
System.out.println(element.getTextRange())
|
||||
System.out.println(opRef.getTextRange().containsOffset(2))
|
||||
System.out.println(opRef.getTextRange().containsOffset(5))
|
||||
System.out.println(element.getTextRange().startOffset + 1)
|
||||
System.out.println(element.getTextRange().startOffset + 2)
|
||||
if (!opRef.getTextRange().containsOffset(3)) return
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[{id=0
|
||||
, myUsages=28|if| (!opRef.|getTextRange|().containsOffset(|2|)) |return,
|
||||
35|if| (!opRef.|getTextRange|().containsOffset(|3|)) |return}
|
||||
, {id=1
|
||||
, myUsages=30|System.out.println(element.|getTextRange|())}
|
||||
, {id=2
|
||||
, myUsages=31|System.out.println(opRef.|getTextRange|().containsOffset(|2|)),
|
||||
32|System.out.println(opRef.|getTextRange|().containsOffset(|5|))}
|
||||
, {id=3
|
||||
, myUsages=33|System.out.println(element.|getTextRange|().startOffset + |1|),
|
||||
34|System.out.println(element.|getTextRange|().startOffset + |2|)}
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
fun f() {
|
||||
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
|
||||
|
||||
var filteredMap = "";
|
||||
filteredMap = numbersMap.<caret>filter { (key, value) -> value > 10 && key.endsWith("1")}
|
||||
filteredMap = numbersMap.filter { (key, value) -> key.endsWith("5") && value > 1}
|
||||
filteredMap = numbersMap.filter { (key, value) -> false }
|
||||
filteredMap = numbersMap.filter { (key, value) -> true }
|
||||
filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") }
|
||||
filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") }
|
||||
println(filteredMap)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
[{id=0
|
||||
, myUsages=5|filteredMap = numbersMap.|filter| { (key, value) -> value > |10| && key.endsWith(|"1"|)},
|
||||
6|filteredMap = numbersMap.|filter| { (key, value) -> key.endsWith(|"5"|) && value > |1|}}
|
||||
, {id=1
|
||||
, myUsages=7|filteredMap = numbersMap.|filter| { (key, value) -> |false| },
|
||||
8|filteredMap = numbersMap.|filter| { (key, value) -> |true| }}
|
||||
, {id=2
|
||||
, myUsages=9|filteredMap = numbersMap.|filter| { (key, value) -> key.endsWith(|"1"|) },
|
||||
10|filteredMap = numbersMap.|filter| { (key, value) -> key.endsWith(|"1"|) }}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
import java.lang.System
|
||||
|
||||
// PSI_ELEMENT: org.jetbrains.kotlin.psi.KtNamedFunction
|
||||
// OPTIONS: usages
|
||||
|
||||
class Foo {
|
||||
fun getFive(){
|
||||
return 5
|
||||
}
|
||||
fun <caret>off(a){
|
||||
|
||||
}
|
||||
|
||||
fun m() {
|
||||
java.lang.System.out.println(off(1))
|
||||
off(1)
|
||||
off(1)
|
||||
if (off(getFive())) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[{id=0
|
||||
, myUsages=15|java.lang.System.out.println(|off|(|1|))}
|
||||
, {id=1
|
||||
, myUsages=16|off|(|1|),
|
||||
17|off|(|1|)}
|
||||
, {id=2
|
||||
, myUsages=18|if| (|off|(getFive())) {}
|
||||
]
|
||||
@@ -0,0 +1,8 @@
|
||||
fun f() {
|
||||
val numbers = mutableListOf("one", "two", "three", "four")
|
||||
numbers.a<caret>dd ("five")
|
||||
numbers.add("zero")
|
||||
numbers.add(getStr())
|
||||
numbers.add(getStr() + "a")
|
||||
numbers.add(getStr() + "b")
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[{id=0
|
||||
, myUsages=3|numbers.|add| (|"five"|),
|
||||
4|numbers.|add|(|"zero"|)}
|
||||
, {id=1
|
||||
, myUsages=5|numbers.|add|(getStr())}
|
||||
, {id=2
|
||||
, myUsages=6|numbers.|add|(getStr() + |"a"|),
|
||||
7|numbers.|add|(getStr() + |"b"|)}
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
var a = Char()
|
||||
var length = a.<caret>inc()
|
||||
@@ -381,6 +381,8 @@
|
||||
|
||||
<usageToPsiElementProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinUsageToPsiElementProvider"/>
|
||||
|
||||
<usageFeaturesProvider implementation="org.jetbrains.kotlin.idea.findUsages.similarity.KotlinUsageSimilarityFeaturesProvider"/>
|
||||
|
||||
<readWriteAccessDetector implementation="org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinReadWriteAccessDetector" id="kotlin"/>
|
||||
|
||||
<dataflowIRProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.inspections.dfa.KotlinDataFlowIRProvider"/>
|
||||
|
||||
Reference in New Issue
Block a user