IJ-CR-123098 [java-console] IDEA-331307 Provide navigation for logs for jvm languages

- resolve only on links
- reuse `codePointAt`

GitOrigin-RevId: 207034ef37fe94d4cbb6bbe5e3f68c7ce8c0cbdd
This commit is contained in:
Mikhail Pyltsin
2024-01-29 16:48:19 +01:00
committed by intellij-monorepo-bot
parent d636f37d4e
commit c6e9c60193
9 changed files with 262 additions and 124 deletions

View File

@@ -183,6 +183,8 @@ jvm.inspection.logging.condition.disagrees.with.log.statement.problem.descriptor
jvm.inspection.logging.condition.disagrees.with.log.statement.fix.name=Change level of the {0, choice, 0#condition|1#call} jvm.inspection.logging.condition.disagrees.with.log.statement.fix.name=Change level of the {0, choice, 0#condition|1#call}
jvm.inspection.logging.condition.disagrees.with.log.statement.fix.family.name=Change log level jvm.inspection.logging.condition.disagrees.with.log.statement.fix.family.name=Change log level
action.find.similar.stack.call.methods.not.found=Similar classes are not found
jvm.inspection.test.failed.line.display.name=Failed line in test jvm.inspection.test.failed.line.display.name=Failed line in test
jvm.inspections.thread.run.display.name=Call to 'Thread.run()' jvm.inspections.thread.run.display.name=Call to 'Thread.run()'

View File

@@ -3,12 +3,9 @@ package com.intellij.analysis.customization.console
import com.intellij.execution.filters.Filter import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.HyperlinkInfo import com.intellij.execution.filters.HyperlinkInfo
import com.intellij.execution.filters.HyperlinkInfoFactory
import com.intellij.openapi.editor.markup.EffectType import com.intellij.openapi.editor.markup.EffectType
import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ui.NamedColorUtil import com.intellij.util.ui.NamedColorUtil
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
@@ -16,21 +13,21 @@ import org.jetbrains.annotations.NonNls
internal data class ProbableClassName(val from: Int, internal data class ProbableClassName(val from: Int,
val to: Int, val to: Int,
val fullLine: String, val fullLine: String,
val shortClassName: String, val fullClassName: String)
val packageName: String,
val virtualFiles: List<VirtualFile>)
private val EXCEPTION_IN_THREAD: @NonNls String = "Exception in thread \"" private const val EXCEPTION_IN_THREAD: @NonNls String = "Exception in thread \""
private val CAUSED_BY: @NonNls String = "Caused by: " private const val CAUSED_BY: @NonNls String = "Caused by: "
private val AT: @NonNls String = "\tat " private const val AT: @NonNls String = "\tat "
private const val POINT_CODE = '.'.code
internal class ClassFinderFilter(private val myProject: Project, myScope: GlobalSearchScope) : Filter { internal class ClassFinderFilter(private val myProject: Project, myScope: GlobalSearchScope) : Filter {
private val cache = ClassInfoCache(myProject, myScope) private val cache = ClassInfoResolver(myProject, myScope)
override fun applyFilter(line: String, entireLength: Int): Filter.Result? { override fun applyFilter(line: String, entireLength: Int): Filter.Result? {
val textStartOffset = entireLength - line.length val textStartOffset = entireLength - line.length
val expectedClasses = findProbableClasses(line, cache) val expectedClasses = findProbableClasses(line)
val results: MutableList<Filter.Result> = ArrayList() val results: MutableList<Filter.Result> = ArrayList()
for (probableClass in expectedClasses) { for (probableClass in expectedClasses) {
val attributes = hyperLinkAttributes() val attributes = hyperLinkAttributes()
@@ -57,40 +54,53 @@ internal class ClassFinderFilter(private val myProject: Project, myScope: Global
private fun getHyperLink(probableClassName: ProbableClassName): HyperlinkInfo { private fun getHyperLink(probableClassName: ProbableClassName): HyperlinkInfo {
return HyperlinkInfoFactory.getInstance() return OnFlyMultipleFilesHyperlinkInfo(cache, probableClassName,0, myProject, LogFinderHyperlinkHandler(probableClassName))
.createMultipleFilesHyperlinkInfo(probableClassName.virtualFiles, 0, myProject, LogFinderHyperlinkHandler(probableClassName))
} }
companion object { companion object {
private fun findProbableClasses(line: String, cache: ClassInfoCache): List<ProbableClassName> { private fun findProbableClasses(line: String): List<ProbableClassName> {
if (line.startsWith(EXCEPTION_IN_THREAD) || line.startsWith(CAUSED_BY) || line.startsWith(AT)) { if (line.isBlank() || line.startsWith(EXCEPTION_IN_THREAD) || line.startsWith(CAUSED_BY) || line.startsWith(AT)) {
return emptyList() return emptyList()
} }
val result = mutableListOf<ProbableClassName>() val result = mutableListOf<ProbableClassName>()
var start = -1 var start = -1
var pointCount = 0 var pointCount = 0
for (i in line.indices) { var i = 0
val c = line.codePointAt(i) var first = true
if (start == -1 && isJavaIdentifierStart(c)) { while (true) {
if (!first) {
val previousPoint = line.codePointAt(i)
i += Character.charCount(previousPoint)
if (i >= line.length) {
break
}
}
else {
first = false
}
val point = line.codePointAt(i)
if (start == -1 && isJavaIdentifierStart(point)) {
start = i start = i
continue continue
} }
if (start != -1 && c == '.'.code) { if (start != -1 && point == POINT_CODE) {
pointCount++ pointCount++
continue continue
} }
if (start != -1 && if (start != -1 &&
((line[i - 1] == '.' && isJavaIdentifierStart(c)) || ((line.codePointAt(i - 1) == POINT_CODE && isJavaIdentifierStart(point)) ||
(line[i - 1] != '.' && isJavaIdentifierPart(c)))) { (line.codePointAt(i - 1) != POINT_CODE && isJavaIdentifierPart(point)))) {
if (i == line.lastIndex && pointCount >= 2) { val charCount = Character.charCount(point)
addProbableClass(line, start, i + 1, cache, result) if (i + charCount >= line.length && pointCount >= 2) {
addProbableClass(line, start, line.length, result)
} }
continue continue
} }
if (pointCount >= 2) { if (pointCount >= 2) {
addProbableClass(line, start, i, cache, result) addProbableClass(line, start, i, result)
} }
pointCount = 0 pointCount = 0
start = -1 start = -1
@@ -111,7 +121,6 @@ internal class ClassFinderFilter(private val myProject: Project, myScope: Global
private fun addProbableClass(line: String, private fun addProbableClass(line: String,
startInclusive: Int, startInclusive: Int,
endExclusive: Int, endExclusive: Int,
cache: ClassInfoCache,
result: MutableList<ProbableClassName>) { result: MutableList<ProbableClassName>) {
var actualEndExclusive = endExclusive var actualEndExclusive = endExclusive
if (actualEndExclusive > 0 && line[actualEndExclusive - 1] == '.') { if (actualEndExclusive > 0 && line[actualEndExclusive - 1] == '.') {
@@ -119,15 +128,9 @@ internal class ClassFinderFilter(private val myProject: Project, myScope: Global
} }
val fullClassName = line.substring(startInclusive, actualEndExclusive) val fullClassName = line.substring(startInclusive, actualEndExclusive)
if (canBeShortenedFullyQualifiedClassName(fullClassName) && isJavaStyle(fullClassName)) { if (canBeShortenedFullyQualifiedClassName(fullClassName) && isJavaStyle(fullClassName)) {
val packageName = StringUtil.getPackageName(fullClassName) val probableClassName = ProbableClassName(startInclusive + fullClassName.lastIndexOf(".") + 1,
val className = fullClassName.substring(packageName.length + 1) startInclusive + fullClassName.length, line, fullClassName)
val resolvedClasses = cache.resolveClasses(className, packageName) result.add(probableClassName)
if (resolvedClasses.classes.isNotEmpty()) {
val probableClassName = ProbableClassName(startInclusive + fullClassName.lastIndexOf(".") + 1,
startInclusive + fullClassName.length,
line, className, packageName, resolvedClasses.classes.values.toList())
result.add(probableClassName)
}
} }
} }

View File

@@ -9,9 +9,8 @@ import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.PsiShortNamesCache import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.util.containers.ContainerUtil
class ClassInfoCache(val project: Project, private val mySearchScope: GlobalSearchScope) { class ClassInfoResolver(val project: Project, private val mySearchScope: GlobalSearchScope) {
companion object { companion object {
private fun findClasses(project: Project, private fun findClasses(project: Project,
@@ -56,15 +55,7 @@ class ClassInfoCache(val project: Project, private val mySearchScope: GlobalSear
} }
} }
private val myCache = ContainerUtil.createConcurrentSoftValueMap<String, ClassResolveInfo>()
fun resolveClasses(className: String, packageName: String): ClassResolveInfo { fun resolveClasses(className: String, packageName: String): ClassResolveInfo {
val key = "$packageName.$className"
val cached = myCache[key]
if (cached != null && cached.isValid) {
return cached
}
if (isDumb(project)) { if (isDumb(project)) {
return ClassResolveInfo.EMPTY return ClassResolveInfo.EMPTY
} }
@@ -77,7 +68,6 @@ class ClassInfoCache(val project: Project, private val mySearchScope: GlobalSear
.toMap() .toMap()
val result = if (mapWithClasses.isEmpty()) ClassResolveInfo.EMPTY else ClassResolveInfo(mapWithClasses) val result = if (mapWithClasses.isEmpty()) ClassResolveInfo.EMPTY else ClassResolveInfo(mapWithClasses)
myCache[key] = result
return result return result
} }

View File

@@ -23,7 +23,7 @@ import org.jetbrains.uast.*
internal class LogFinderHyperlinkHandler(private val probableClassName: ProbableClassName) : HyperlinkInfoFactory.HyperlinkHandler { internal class LogFinderHyperlinkHandler(private val probableClassName: ProbableClassName) : HyperlinkInfoFactory.HyperlinkHandler {
override fun onLinkFollowed(project: Project, file: VirtualFile, targetEditor: Editor, originalEditor: Editor?) { override fun onLinkFollowed(project: Project, file: VirtualFile, targetEditor: Editor, originalEditor: Editor?) {
LogConsoleLogHandlerCollectors.logHandleClass(project, probableClassName.virtualFiles.size) LogConsoleLogHandlerCollectors.logHandleClass(project, 1)
val psiFile = PsiManager.getInstance(project).findFile(file) val psiFile = PsiManager.getInstance(project).findFile(file)
if (psiFile == null) return if (psiFile == null) return
@@ -102,13 +102,14 @@ internal class LogFinderHyperlinkHandler(private val probableClassName: Probable
} }
} }
private class LogVisitor(private val probableClassName: ProbableClassName) : PsiRecursiveElementVisitor() { internal class LogVisitor(private val probableClassName: ProbableClassName) : PsiRecursiveElementVisitor() {
val similarClasses = mutableSetOf<UClass>() val similarClasses = mutableSetOf<UClass>()
val similarCalls = mutableSetOf<UCallExpression>() val similarCalls = mutableSetOf<UCallExpression>()
val shortClassName = probableClassName.fullClassName.substringAfterLast('.')
override fun visitElement(element: PsiElement) { override fun visitElement(element: PsiElement) {
val uClass = element.toUElementOfType<UClass>() val uClass = element.toUElementOfType<UClass>()
if (uClass != null && if (uClass != null && shortClassName == uClass.javaPsi.name) {
probableClassName.shortClassName == uClass.javaPsi.name) {
similarClasses.add(uClass) similarClasses.add(uClass)
} }
val uCall = element.toUElementOfType<UCallExpression>() val uCall = element.toUElementOfType<UCallExpression>()
@@ -143,10 +144,9 @@ private class LogVisitor(private val probableClassName: ProbableClassName) : Psi
if (logStringArgument == null) return false if (logStringArgument == null) return false
val calculateValue = LoggingStringPartEvaluator.calculateValue(logStringArgument) ?: return false val calculateValue = LoggingStringPartEvaluator.calculateValue(logStringArgument) ?: return false
val fullLine = probableClassName.fullLine val fullLine = probableClassName.fullLine
val classFullName = probableClassName.packageName + "." + probableClassName.shortClassName var startPoint = probableClassName.fullLine.indexOf(probableClassName.fullClassName)
var startPoint = probableClassName.fullLine.indexOf(classFullName)
if (startPoint == -1) return false if (startPoint == -1) return false
startPoint += classFullName.length startPoint += probableClassName.fullClassName.length
if (calculateValue.none { it.isConstant && it.text != null }) return false if (calculateValue.none { it.isConstant && it.text != null }) return false
for (value in calculateValue) { for (value in calculateValue) {
if (value.isConstant && value.text != null) { if (value.isConstant && value.text != null) {

View File

@@ -0,0 +1,60 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.analysis.customization.console
import com.intellij.analysis.JvmAnalysisBundle
import com.intellij.codeInsight.hint.HintUtil
import com.intellij.execution.filters.HyperlinkInfoFactory.HyperlinkHandler
import com.intellij.execution.filters.impl.MultipleFilesHyperlinkInfoBase
import com.intellij.openapi.fileEditor.OpenFileDescriptor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.ui.awt.RelativePoint
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
class OnFlyMultipleFilesHyperlinkInfo internal constructor(private val myInfoCache: ClassInfoResolver,
private val probableClassName: ProbableClassName,
lineNumber: Int,
project: Project,
action: HyperlinkHandler?) :
MultipleFilesHyperlinkInfoBase(lineNumber, project, action) {
override fun getFiles(project: Project): List<PsiFile> {
val packageName = StringUtil.getPackageName(probableClassName.fullClassName)
if (packageName.length == probableClassName.fullClassName.length) return emptyList()
val className = probableClassName.fullClassName.substring(packageName.length + 1)
val resolvedClasses = myInfoCache.resolveClasses(className, packageName)
val currentFiles: MutableList<PsiFile> = ArrayList()
for (file in resolvedClasses.classes.values) {
if (!file.isValid) continue
val psiFile = PsiManager.getInstance(project).findFile(file)
if (psiFile != null) {
val navigationElement = psiFile.navigationElement // Sources may be downloaded.
if (navigationElement is PsiFile) {
currentFiles.add(navigationElement)
continue
}
currentFiles.add(psiFile)
}
}
return currentFiles
}
override fun showNotFound(project: Project, hyperlinkLocationPoint: RelativePoint?) {
if (hyperlinkLocationPoint == null) return
val message = JvmAnalysisBundle.message("action.find.similar.stack.call.methods.not.found")
val label = HintUtil.createWarningLabel(message)
JBPopupFactory.getInstance().createBalloonBuilder(label)
.setFadeoutTime(4000)
.setFillColor(HintUtil.getWarningColor())
.createBalloon()
.show(RelativePoint(hyperlinkLocationPoint.screenPoint), Balloon.Position.above)
}
override fun getDescriptor(): OpenFileDescriptor? {
return null
}
}

View File

@@ -1,6 +1,7 @@
package com.intellij.jvm.analysis.internal.testFramework.internal package com.intellij.jvm.analysis.internal.testFramework.internal
import com.intellij.analysis.customization.console.ClassLoggingConsoleFilterProvider import com.intellij.analysis.customization.console.ClassLoggingConsoleFilterProvider
import com.intellij.analysis.customization.console.OnFlyMultipleFilesHyperlinkInfo
import com.intellij.jvm.analysis.testFramework.LightJvmCodeInsightFixtureTestCase import com.intellij.jvm.analysis.testFramework.LightJvmCodeInsightFixtureTestCase
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.options.advanced.AdvancedSettings import com.intellij.openapi.options.advanced.AdvancedSettings
@@ -41,7 +42,14 @@ abstract class LogFinderHyperlinkTestBase : LightJvmCodeInsightFixtureTestCase()
len += logLine.length len += logLine.length
val result = filter.applyFilter(logLine, len) val result = filter.applyFilter(logLine, len)
if (logItem.position == null) { if (logItem.position == null) {
Assert.assertNull(logItem.toString(), result) if (result == null) {
continue
}
val hyperlinkInfo = result.firstHyperlinkInfo
if (hyperlinkInfo is OnFlyMultipleFilesHyperlinkInfo) {
val files = hyperlinkInfo.getFiles(project)
Assert.assertTrue(logItem.toString(), files.isEmpty())
}
continue continue
} }
Assert.assertNotNull(logItem.toString(), result) Assert.assertNotNull(logItem.toString(), result)

View File

@@ -211,6 +211,47 @@ public final class Slf4JFluent {
) )
} }
fun testUnicode() {
LoggingTestUtils.addSlf4J(myFixture)
checkColumnFinderJava(
fileName = "Slf4JFluent",
classText = """
package com.example.úsh.čas;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class UčenjaУченья {
private final static Logger log = LoggerFactory.getLogger(UčenjaУченья.class);
public static void main(String[] args) {
System.out.println(2);
log1(1);
log2(2);
}
private static void log1(int i) {
String msg = "test" + i;
log.atInfo()
.setMessage(msg)
.log();
}
private static void log2(int i) {
String msg = "log2" + i;
log.atInfo()
.log(msg);
}
}
""".trimIndent(),
logItems = listOf(
LogItem("java.exe", null),
LogItem("1", null),
LogItem("20:13:25.878 [main] INFO com.example.úsh.čas.UčenjaУченья - test1", LogicalPosition(16, 8)),
)
)
}
fun testSkipException() { fun testSkipException() {
LoggingTestUtils.addSlf4J(myFixture) LoggingTestUtils.addSlf4J(myFixture)
checkColumnFinderJava( checkColumnFinderJava(

View File

@@ -15,43 +15,24 @@
*/ */
package com.intellij.execution.filters.impl; package com.intellij.execution.filters.impl;
import com.intellij.codeInsight.navigation.PsiTargetNavigator;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.filters.FileHyperlinkInfo;
import com.intellij.execution.filters.HyperlinkInfoBase;
import com.intellij.execution.filters.HyperlinkInfoFactory; import com.intellij.execution.filters.HyperlinkInfoFactory;
import com.intellij.ide.DataManager;
import com.intellij.ide.util.gotoByName.GotoFileCellRenderer;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager; import com.intellij.psi.PsiManager;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ApiStatus.Internal @ApiStatus.Internal
public final class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implements FileHyperlinkInfo { public final class MultipleFilesHyperlinkInfo extends MultipleFilesHyperlinkInfoBase {
private final List<? extends VirtualFile> myVirtualFiles; private final List<? extends VirtualFile> myVirtualFiles;
private final int myLineNumber;
private final Project myProject;
private final HyperlinkInfoFactory.@Nullable HyperlinkHandler myAction;
MultipleFilesHyperlinkInfo(@NotNull List<? extends VirtualFile> virtualFiles, int lineNumber, @NotNull Project project) { MultipleFilesHyperlinkInfo(@NotNull List<? extends VirtualFile> virtualFiles, int lineNumber, @NotNull Project project) {
this(virtualFiles, lineNumber, project, null); this(virtualFiles, lineNumber, project, null);
@@ -61,38 +42,13 @@ public final class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implemen
int lineNumber, int lineNumber,
@NotNull Project project, @NotNull Project project,
@Nullable HyperlinkInfoFactory.HyperlinkHandler action) { @Nullable HyperlinkInfoFactory.HyperlinkHandler action) {
super(lineNumber, project, action);
myVirtualFiles = virtualFiles; myVirtualFiles = virtualFiles;
myLineNumber = lineNumber;
myProject = project;
myAction = action;
} }
@Override @Override
public void navigate(@NotNull final Project project, @Nullable RelativePoint hyperlinkLocationPoint) {
Editor originalEditor;
if (hyperlinkLocationPoint != null) {
DataManager dataManager = DataManager.getInstance();
DataContext dataContext = dataManager.getDataContext(hyperlinkLocationPoint.getOriginalComponent());
originalEditor = CommonDataKeys.EDITOR.getData(dataContext);
} else {
originalEditor = null;
}
JFrame frame = WindowManager.getInstance().getFrame(project);
int width = frame != null ? frame.getSize().width : 200;
GotoFileCellRenderer renderer = new GotoFileCellRenderer(width);
new PsiTargetNavigator<>(() -> getFiles(project))
.title(ExecutionBundle.message("popup.title.choose.target.file"))
.presentationProvider(element -> renderer.computePresentation(element))
.navigate(hyperlinkLocationPoint, ExecutionBundle.message("popup.title.choose.target.file"), project, file -> {
open(file.getVirtualFile(), originalEditor);
return true;
});
}
@NotNull @NotNull
private List<PsiFile> getFiles(@NotNull Project project) { public List<PsiFile> getFiles(@NotNull Project project) {
List<PsiFile> currentFiles = new ArrayList<>(); List<PsiFile> currentFiles = new ArrayList<>();
for (VirtualFile file : myVirtualFiles) { for (VirtualFile file : myVirtualFiles) {
if (!file.isValid()) continue; if (!file.isValid()) continue;
@@ -110,30 +66,6 @@ public final class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implemen
return currentFiles; return currentFiles;
} }
private void open(@NotNull VirtualFile file, Editor originalEditor) {
Document document = FileDocumentManager.getInstance().getDocument(file, myProject);
int offset = 0;
if (document != null && myLineNumber >= 0 && myLineNumber < document.getLineCount()) {
offset = document.getLineStartOffset(myLineNumber);
}
OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, file, offset);
Editor editor = FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true);
if (myAction != null && editor != null) {
if (editor instanceof EditorEx) {
((EditorEx)editor).setCaretEnabled(false);
try {
myAction.onLinkFollowed(myProject, file, editor, originalEditor);
}
finally {
((EditorEx)editor).setCaretEnabled(true);
}
}
else {
myAction.onLinkFollowed(myProject, file, editor, originalEditor);
}
}
}
@Nullable @Nullable
@Override @Override
public OpenFileDescriptor getDescriptor() { public OpenFileDescriptor getDescriptor() {

View File

@@ -0,0 +1,102 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.execution.filters.impl;
import com.intellij.codeInsight.navigation.PsiTargetNavigator;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.filters.FileHyperlinkInfo;
import com.intellij.execution.filters.HyperlinkInfoBase;
import com.intellij.execution.filters.HyperlinkInfoFactory;
import com.intellij.ide.DataManager;
import com.intellij.ide.util.gotoByName.GotoFileCellRenderer;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.PsiFile;
import com.intellij.ui.awt.RelativePoint;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
@ApiStatus.Internal
public abstract class MultipleFilesHyperlinkInfoBase extends HyperlinkInfoBase implements FileHyperlinkInfo {
protected final int myLineNumber;
protected final Project myProject;
private final HyperlinkInfoFactory.@Nullable HyperlinkHandler myAction;
public MultipleFilesHyperlinkInfoBase(int lineNumber,
@NotNull Project project,
@Nullable HyperlinkInfoFactory.HyperlinkHandler action) {
myLineNumber = lineNumber;
myProject = project;
myAction = action;
}
@Override
public void navigate(@NotNull final Project project, @Nullable RelativePoint hyperlinkLocationPoint) {
Editor originalEditor;
if (hyperlinkLocationPoint != null) {
DataManager dataManager = DataManager.getInstance();
DataContext dataContext = dataManager.getDataContext(hyperlinkLocationPoint.getOriginalComponent());
originalEditor = CommonDataKeys.EDITOR.getData(dataContext);
}
else {
originalEditor = null;
}
JFrame frame = WindowManager.getInstance().getFrame(project);
int width = frame != null ? frame.getSize().width : 200;
GotoFileCellRenderer renderer = new GotoFileCellRenderer(width);
boolean navigated = new PsiTargetNavigator<>(() -> getFiles(project))
.title(ExecutionBundle.message("popup.title.choose.target.file"))
.presentationProvider(element -> renderer.computePresentation(element))
.navigate(hyperlinkLocationPoint, ExecutionBundle.message("popup.title.choose.target.file"), project, file -> {
open(file.getVirtualFile(), originalEditor);
return true;
});
if (!navigated) {
showNotFound(project, hyperlinkLocationPoint);
}
}
protected void showNotFound(@NotNull final Project project, @Nullable RelativePoint hyperlinkLocationPoint) {
}
@NotNull
public abstract List<PsiFile> getFiles(@NotNull Project project);
private void open(@NotNull VirtualFile file, Editor originalEditor) {
Document document = FileDocumentManager.getInstance().getDocument(file, myProject);
int offset = 0;
if (document != null && myLineNumber >= 0 && myLineNumber < document.getLineCount()) {
offset = document.getLineStartOffset(myLineNumber);
}
OpenFileDescriptor descriptor = new OpenFileDescriptor(myProject, file, offset);
Editor editor = FileEditorManager.getInstance(myProject).openTextEditor(descriptor, true);
if (myAction != null && editor != null) {
if (editor instanceof EditorEx) {
((EditorEx)editor).setCaretEnabled(false);
try {
myAction.onLinkFollowed(myProject, file, editor, originalEditor);
}
finally {
((EditorEx)editor).setCaretEnabled(true);
}
}
else {
myAction.onLinkFollowed(myProject, file, editor, originalEditor);
}
}
}
}