mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
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:
committed by
intellij-monorepo-bot
parent
d636f37d4e
commit
c6e9c60193
@@ -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.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.inspections.thread.run.display.name=Call to 'Thread.run()'
|
||||
|
||||
@@ -3,12 +3,9 @@ package com.intellij.analysis.customization.console
|
||||
|
||||
import com.intellij.execution.filters.Filter
|
||||
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.TextAttributes
|
||||
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.util.ui.NamedColorUtil
|
||||
import org.jetbrains.annotations.NonNls
|
||||
@@ -16,21 +13,21 @@ import org.jetbrains.annotations.NonNls
|
||||
internal data class ProbableClassName(val from: Int,
|
||||
val to: Int,
|
||||
val fullLine: String,
|
||||
val shortClassName: String,
|
||||
val packageName: String,
|
||||
val virtualFiles: List<VirtualFile>)
|
||||
val fullClassName: String)
|
||||
|
||||
private val EXCEPTION_IN_THREAD: @NonNls String = "Exception in thread \""
|
||||
private val CAUSED_BY: @NonNls String = "Caused by: "
|
||||
private val AT: @NonNls String = "\tat "
|
||||
private const val EXCEPTION_IN_THREAD: @NonNls String = "Exception in thread \""
|
||||
private const val CAUSED_BY: @NonNls String = "Caused by: "
|
||||
private const val AT: @NonNls String = "\tat "
|
||||
|
||||
private const val POINT_CODE = '.'.code
|
||||
|
||||
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? {
|
||||
val textStartOffset = entireLength - line.length
|
||||
|
||||
val expectedClasses = findProbableClasses(line, cache)
|
||||
val expectedClasses = findProbableClasses(line)
|
||||
val results: MutableList<Filter.Result> = ArrayList()
|
||||
for (probableClass in expectedClasses) {
|
||||
val attributes = hyperLinkAttributes()
|
||||
@@ -57,40 +54,53 @@ internal class ClassFinderFilter(private val myProject: Project, myScope: Global
|
||||
|
||||
|
||||
private fun getHyperLink(probableClassName: ProbableClassName): HyperlinkInfo {
|
||||
return HyperlinkInfoFactory.getInstance()
|
||||
.createMultipleFilesHyperlinkInfo(probableClassName.virtualFiles, 0, myProject, LogFinderHyperlinkHandler(probableClassName))
|
||||
return OnFlyMultipleFilesHyperlinkInfo(cache, probableClassName,0, myProject, LogFinderHyperlinkHandler(probableClassName))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun findProbableClasses(line: String, cache: ClassInfoCache): List<ProbableClassName> {
|
||||
if (line.startsWith(EXCEPTION_IN_THREAD) || line.startsWith(CAUSED_BY) || line.startsWith(AT)) {
|
||||
private fun findProbableClasses(line: String): List<ProbableClassName> {
|
||||
if (line.isBlank() || line.startsWith(EXCEPTION_IN_THREAD) || line.startsWith(CAUSED_BY) || line.startsWith(AT)) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val result = mutableListOf<ProbableClassName>()
|
||||
var start = -1
|
||||
var pointCount = 0
|
||||
for (i in line.indices) {
|
||||
val c = line.codePointAt(i)
|
||||
if (start == -1 && isJavaIdentifierStart(c)) {
|
||||
var i = 0
|
||||
var first = true
|
||||
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
|
||||
continue
|
||||
}
|
||||
if (start != -1 && c == '.'.code) {
|
||||
if (start != -1 && point == POINT_CODE) {
|
||||
pointCount++
|
||||
continue
|
||||
}
|
||||
if (start != -1 &&
|
||||
((line[i - 1] == '.' && isJavaIdentifierStart(c)) ||
|
||||
(line[i - 1] != '.' && isJavaIdentifierPart(c)))) {
|
||||
if (i == line.lastIndex && pointCount >= 2) {
|
||||
addProbableClass(line, start, i + 1, cache, result)
|
||||
((line.codePointAt(i - 1) == POINT_CODE && isJavaIdentifierStart(point)) ||
|
||||
(line.codePointAt(i - 1) != POINT_CODE && isJavaIdentifierPart(point)))) {
|
||||
val charCount = Character.charCount(point)
|
||||
if (i + charCount >= line.length && pointCount >= 2) {
|
||||
addProbableClass(line, start, line.length, result)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (pointCount >= 2) {
|
||||
addProbableClass(line, start, i, cache, result)
|
||||
addProbableClass(line, start, i, result)
|
||||
}
|
||||
pointCount = 0
|
||||
start = -1
|
||||
@@ -111,7 +121,6 @@ internal class ClassFinderFilter(private val myProject: Project, myScope: Global
|
||||
private fun addProbableClass(line: String,
|
||||
startInclusive: Int,
|
||||
endExclusive: Int,
|
||||
cache: ClassInfoCache,
|
||||
result: MutableList<ProbableClassName>) {
|
||||
var actualEndExclusive = endExclusive
|
||||
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)
|
||||
if (canBeShortenedFullyQualifiedClassName(fullClassName) && isJavaStyle(fullClassName)) {
|
||||
val packageName = StringUtil.getPackageName(fullClassName)
|
||||
val className = fullClassName.substring(packageName.length + 1)
|
||||
val resolvedClasses = cache.resolveClasses(className, packageName)
|
||||
if (resolvedClasses.classes.isNotEmpty()) {
|
||||
val probableClassName = ProbableClassName(startInclusive + fullClassName.lastIndexOf(".") + 1,
|
||||
startInclusive + fullClassName.length,
|
||||
line, className, packageName, resolvedClasses.classes.values.toList())
|
||||
result.add(probableClassName)
|
||||
}
|
||||
val probableClassName = ProbableClassName(startInclusive + fullClassName.lastIndexOf(".") + 1,
|
||||
startInclusive + fullClassName.length, line, fullClassName)
|
||||
result.add(probableClassName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,8 @@ import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
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 {
|
||||
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 {
|
||||
val key = "$packageName.$className"
|
||||
val cached = myCache[key]
|
||||
if (cached != null && cached.isValid) {
|
||||
return cached
|
||||
}
|
||||
|
||||
if (isDumb(project)) {
|
||||
return ClassResolveInfo.EMPTY
|
||||
}
|
||||
@@ -77,7 +68,6 @@ class ClassInfoCache(val project: Project, private val mySearchScope: GlobalSear
|
||||
.toMap()
|
||||
|
||||
val result = if (mapWithClasses.isEmpty()) ClassResolveInfo.EMPTY else ClassResolveInfo(mapWithClasses)
|
||||
myCache[key] = result
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jetbrains.uast.*
|
||||
|
||||
internal class LogFinderHyperlinkHandler(private val probableClassName: ProbableClassName) : HyperlinkInfoFactory.HyperlinkHandler {
|
||||
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)
|
||||
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 similarCalls = mutableSetOf<UCallExpression>()
|
||||
val shortClassName = probableClassName.fullClassName.substringAfterLast('.')
|
||||
|
||||
override fun visitElement(element: PsiElement) {
|
||||
val uClass = element.toUElementOfType<UClass>()
|
||||
if (uClass != null &&
|
||||
probableClassName.shortClassName == uClass.javaPsi.name) {
|
||||
if (uClass != null && shortClassName == uClass.javaPsi.name) {
|
||||
similarClasses.add(uClass)
|
||||
}
|
||||
val uCall = element.toUElementOfType<UCallExpression>()
|
||||
@@ -143,10 +144,9 @@ private class LogVisitor(private val probableClassName: ProbableClassName) : Psi
|
||||
if (logStringArgument == null) return false
|
||||
val calculateValue = LoggingStringPartEvaluator.calculateValue(logStringArgument) ?: return false
|
||||
val fullLine = probableClassName.fullLine
|
||||
val classFullName = probableClassName.packageName + "." + probableClassName.shortClassName
|
||||
var startPoint = probableClassName.fullLine.indexOf(classFullName)
|
||||
var startPoint = probableClassName.fullLine.indexOf(probableClassName.fullClassName)
|
||||
if (startPoint == -1) return false
|
||||
startPoint += classFullName.length
|
||||
startPoint += probableClassName.fullClassName.length
|
||||
if (calculateValue.none { it.isConstant && it.text != null }) return false
|
||||
for (value in calculateValue) {
|
||||
if (value.isConstant && value.text != null) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.intellij.jvm.analysis.internal.testFramework.internal
|
||||
|
||||
import com.intellij.analysis.customization.console.ClassLoggingConsoleFilterProvider
|
||||
import com.intellij.analysis.customization.console.OnFlyMultipleFilesHyperlinkInfo
|
||||
import com.intellij.jvm.analysis.testFramework.LightJvmCodeInsightFixtureTestCase
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.options.advanced.AdvancedSettings
|
||||
@@ -41,7 +42,14 @@ abstract class LogFinderHyperlinkTestBase : LightJvmCodeInsightFixtureTestCase()
|
||||
len += logLine.length
|
||||
val result = filter.applyFilter(logLine, len)
|
||||
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
|
||||
}
|
||||
Assert.assertNotNull(logItem.toString(), result)
|
||||
|
||||
@@ -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() {
|
||||
LoggingTestUtils.addSlf4J(myFixture)
|
||||
checkColumnFinderJava(
|
||||
|
||||
@@ -15,43 +15,24 @@
|
||||
*/
|
||||
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.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.ui.awt.RelativePoint;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implements FileHyperlinkInfo {
|
||||
public final class MultipleFilesHyperlinkInfo extends MultipleFilesHyperlinkInfoBase {
|
||||
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) {
|
||||
this(virtualFiles, lineNumber, project, null);
|
||||
@@ -61,38 +42,13 @@ public final class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implemen
|
||||
int lineNumber,
|
||||
@NotNull Project project,
|
||||
@Nullable HyperlinkInfoFactory.HyperlinkHandler action) {
|
||||
super(lineNumber, project, action);
|
||||
myVirtualFiles = virtualFiles;
|
||||
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);
|
||||
|
||||
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
|
||||
private List<PsiFile> getFiles(@NotNull Project project) {
|
||||
public List<PsiFile> getFiles(@NotNull Project project) {
|
||||
List<PsiFile> currentFiles = new ArrayList<>();
|
||||
for (VirtualFile file : myVirtualFiles) {
|
||||
if (!file.isValid()) continue;
|
||||
@@ -110,30 +66,6 @@ public final class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implemen
|
||||
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
|
||||
@Override
|
||||
public OpenFileDescriptor getDescriptor() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user