From 1bd4963517559ed477e04f7e3d694cae7ba0d3f5 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Fri, 17 Apr 2020 12:59:13 +0700 Subject: [PATCH] Unifying code for exception navigation and breakpoint creation Fixes IDEA-237888 Exceptions incorrectly detected for unrelated input in docker log console AdvancedExceptionFilter extracted because Kotlin plugin creates ExceptionFilter internally and performs double work for some reason. This is not that visible for normal links as intersecting links are ignored, but inlays like 'Create breakpoint' get duplicated GitOrigin-RevId: 892d2e7ec2d9152c93d8f328b655106789b7082a --- ...aDebuggerAddExceptionBreakpointFilter.java | 69 +++++++++++++++++++ .../JavaDebuggerConsoleFilterProvider.java | 61 +--------------- .../filters/AdvancedExceptionFilter.java | 42 +++++++++++ .../filters/ExceptionBaseFilterFactory.java | 2 +- .../execution/filters/ExceptionFilter.java | 28 +++++--- .../filters/JvmExceptionOccurrenceFilter.java | 29 ++++++++ .../NavigateToExceptionClassFilter.java | 26 +++++++ java/java-impl/src/META-INF/JavaPlugin.xml | 4 ++ .../execution/filters/ExceptionInfo.java | 57 +++++++-------- .../execution/filters/ExceptionInfoCache.java | 4 +- .../filters/ExceptionLineRefiner.java | 16 +++++ .../execution/filters/ExceptionWorker.java | 12 ++-- .../filters/HyperlinkInfoFactory.java | 8 +-- .../impl/HyperlinkInfoFactoryImpl.java | 6 +- .../impl/MultiPsiElementHyperlinkInfo.java | 9 ++- 15 files changed, 252 insertions(+), 121 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerAddExceptionBreakpointFilter.java create mode 100644 java/execution/openapi/src/com/intellij/execution/filters/AdvancedExceptionFilter.java create mode 100644 java/execution/openapi/src/com/intellij/execution/filters/JvmExceptionOccurrenceFilter.java create mode 100644 java/execution/openapi/src/com/intellij/execution/filters/NavigateToExceptionClassFilter.java create mode 100644 java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerAddExceptionBreakpointFilter.java b/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerAddExceptionBreakpointFilter.java new file mode 100644 index 000000000000..f4e7f7d881b9 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerAddExceptionBreakpointFilter.java @@ -0,0 +1,69 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.debugger.impl.attach; + +import com.intellij.codeInsight.hints.presentation.InlayPresentation; +import com.intellij.codeInsight.hints.presentation.PresentationFactory; +import com.intellij.codeInsight.hints.presentation.PresentationRenderer; +import com.intellij.debugger.DebuggerManagerEx; +import com.intellij.debugger.ui.breakpoints.ExceptionBreakpoint; +import com.intellij.debugger.ui.breakpoints.JavaExceptionBreakpointType; +import com.intellij.execution.filters.Filter.ResultItem; +import com.intellij.execution.filters.JvmExceptionOccurrenceFilter; +import com.intellij.execution.impl.InlayProvider; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorCustomElementRenderer; +import com.intellij.openapi.editor.impl.EditorImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.PsiClass; +import com.intellij.xdebugger.XDebuggerManager; +import com.intellij.xdebugger.breakpoints.XBreakpoint; +import com.intellij.xdebugger.impl.breakpoints.ui.BreakpointsDialogFactory; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.java.debugger.breakpoints.properties.JavaExceptionBreakpointProperties; + +import java.util.List; +import java.util.Objects; + +public class JavaDebuggerAddExceptionBreakpointFilter implements JvmExceptionOccurrenceFilter { + @Override + public @Nullable ResultItem applyFilter(@NotNull String exceptionClassName, + @NotNull List classes, + int exceptionStartOffset) { + return new CreateExceptionBreakpointResult(exceptionStartOffset, exceptionStartOffset + exceptionClassName.length(), + exceptionClassName); + } + + private static class CreateExceptionBreakpointResult extends ResultItem implements InlayProvider { + private final String myExceptionFqn; + + CreateExceptionBreakpointResult(int highlightStartOffset, int highlightEndOffset, String exceptionFqn) { + super(highlightStartOffset, highlightEndOffset, null); + myExceptionFqn = exceptionFqn; + } + + @Override + public EditorCustomElementRenderer createInlayRenderer(Editor editor) { + PresentationFactory factory = new PresentationFactory((EditorImpl)editor); + InlayPresentation presentation = + factory.referenceOnHover(factory.roundWithBackground(factory.smallText("Create breakpoint")), (event, point) -> { + Project project = editor.getProject(); + XBreakpoint breakpoint = + XDebuggerManager.getInstance(project).getBreakpointManager().getBreakpoints(JavaExceptionBreakpointType.class).stream() + .filter(b -> Objects.equals(myExceptionFqn, b.getProperties().myQualifiedName)).findFirst().orElse(null); + if (breakpoint == null) { + ExceptionBreakpoint ebpt = DebuggerManagerEx.getInstanceEx(project).getBreakpointManager() + .addExceptionBreakpoint(myExceptionFqn, StringUtil.getPackageName(myExceptionFqn)); + if (ebpt != null) { + breakpoint = ebpt.getXBreakpoint(); + } + } + if (breakpoint != null) { + BreakpointsDialogFactory.getInstance(project).showDialog(breakpoint); + } + }); + return new PresentationRenderer(presentation); + } + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerConsoleFilterProvider.java b/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerConsoleFilterProvider.java index cc10e303dae4..51610a1f5009 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerConsoleFilterProvider.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/attach/JavaDebuggerConsoleFilterProvider.java @@ -4,34 +4,24 @@ package com.intellij.debugger.impl.attach; import com.intellij.codeInsight.hints.presentation.InlayPresentation; import com.intellij.codeInsight.hints.presentation.PresentationFactory; import com.intellij.codeInsight.hints.presentation.PresentationRenderer; -import com.intellij.debugger.DebuggerManagerEx; -import com.intellij.debugger.ui.breakpoints.ExceptionBreakpoint; -import com.intellij.debugger.ui.breakpoints.JavaExceptionBreakpointType; import com.intellij.execution.filters.ConsoleFilterProvider; -import com.intellij.execution.filters.ExceptionInfo; import com.intellij.execution.filters.Filter; import com.intellij.execution.impl.InlayProvider; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.EditorCustomElementRenderer; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.xdebugger.XDebuggerManager; -import com.intellij.xdebugger.breakpoints.XBreakpoint; -import com.intellij.xdebugger.impl.breakpoints.ui.BreakpointsDialogFactory; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.java.debugger.breakpoints.properties.JavaExceptionBreakpointProperties; import java.util.Arrays; -import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; public class JavaDebuggerConsoleFilterProvider implements ConsoleFilterProvider { @Override public Filter @NotNull [] getDefaultFilters(@NotNull Project project) { - return new Filter[]{new JavaDebuggerAttachFilter(), new JavaDebuggerExceptionFilter()}; + return new Filter[]{new JavaDebuggerAttachFilter()}; } private static class JavaDebuggerAttachFilter implements Filter { @@ -75,53 +65,4 @@ public class JavaDebuggerConsoleFilterProvider implements ConsoleFilterProvider return new PresentationRenderer(presentation); } } - - private static class JavaDebuggerExceptionFilter implements Filter { - @Override - public @Nullable Result applyFilter(@NotNull String line, int entireLength) { - ExceptionInfo exceptionInfo = ExceptionInfo.parseMessage(line); - if (exceptionInfo == null) { - return null; - } - String exceptionFqn = exceptionInfo.getExceptionClassName(); - int start = entireLength - line.length() + exceptionInfo.getClassNameOffset(); - - // to trick the code unwrapping single results in com.intellij.execution.filters.CompositeFilter#createFinalResult - return new Result(Arrays.asList( - new CreateExceptionBreakpointResult(start, start + exceptionFqn.length(), exceptionFqn), - new ResultItem(0, 0, null))); - } - } - - private static class CreateExceptionBreakpointResult extends Filter.ResultItem implements InlayProvider { - private final String myExceptionFqn; - - CreateExceptionBreakpointResult(int highlightStartOffset, int highlightEndOffset, String exceptionFqn) { - super(highlightStartOffset, highlightEndOffset, null); - myExceptionFqn = exceptionFqn; - } - - @Override - public EditorCustomElementRenderer createInlayRenderer(Editor editor) { - PresentationFactory factory = new PresentationFactory((EditorImpl)editor); - InlayPresentation presentation = - factory.referenceOnHover(factory.roundWithBackground(factory.smallText("Create breakpoint")), (event, point) -> { - Project project = editor.getProject(); - XBreakpoint breakpoint = - XDebuggerManager.getInstance(project).getBreakpointManager().getBreakpoints(JavaExceptionBreakpointType.class).stream() - .filter(b -> Objects.equals(myExceptionFqn, b.getProperties().myQualifiedName)).findFirst().orElse(null); - if (breakpoint == null) { - ExceptionBreakpoint ebpt = DebuggerManagerEx.getInstanceEx(project).getBreakpointManager() - .addExceptionBreakpoint(myExceptionFqn, StringUtil.getPackageName(myExceptionFqn)); - if (ebpt != null) { - breakpoint = ebpt.getXBreakpoint(); - } - } - if (breakpoint != null) { - BreakpointsDialogFactory.getInstance(project).showDialog(breakpoint); - } - }); - return new PresentationRenderer(presentation); - } - } } diff --git a/java/execution/openapi/src/com/intellij/execution/filters/AdvancedExceptionFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/AdvancedExceptionFilter.java new file mode 100644 index 000000000000..9a9526cc4682 --- /dev/null +++ b/java/execution/openapi/src/com/intellij/execution/filters/AdvancedExceptionFilter.java @@ -0,0 +1,42 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.filters; + +import com.intellij.psi.CommonClassNames; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.util.InheritanceUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * This filter includes decorations not only for stack-trace lines, but also for exception names + */ +class AdvancedExceptionFilter extends ExceptionFilter { + AdvancedExceptionFilter(@NotNull GlobalSearchScope scope) { + super(scope); + } + + @Override + @NotNull List getExceptionClassNameItems(ExceptionInfo prevLineException) { + ExceptionInfoCache.ClassResolveInfo info = myCache.resolveClass(prevLineException.getExceptionClassName()); + List classMap = new ArrayList<>(); + info.myClasses.forEach((key, value) -> { + PsiClass psiClass = ObjectUtils.tryCast(value, PsiClass.class); + if (InheritanceUtil.isInheritor(psiClass, CommonClassNames.JAVA_LANG_THROWABLE)) { + classMap.add(psiClass); + } + }); + List exceptionResults = new ArrayList<>(); + if (!classMap.isEmpty()) { + JvmExceptionOccurrenceFilter.EP_NAME.forEachExtensionSafe(filter -> { + ResultItem res = filter.applyFilter(prevLineException.getExceptionClassName(), classMap, prevLineException.getClassNameOffset()); + ContainerUtil.addIfNotNull(exceptionResults, res); + }); + } + return exceptionResults; + } +} diff --git a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionBaseFilterFactory.java b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionBaseFilterFactory.java index 897adca6ce62..2e9602e185e5 100644 --- a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionBaseFilterFactory.java +++ b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionBaseFilterFactory.java @@ -22,6 +22,6 @@ public class ExceptionBaseFilterFactory implements ExceptionFilterFactory { @NotNull @Override public Filter create(@NotNull GlobalSearchScope searchScope) { - return new ExceptionFilter(searchScope); + return new AdvancedExceptionFilter(searchScope); } } diff --git a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java index 80672a9bb9c5..e628ed0e4f31 100644 --- a/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java +++ b/java/execution/openapi/src/com/intellij/execution/filters/ExceptionFilter.java @@ -16,15 +16,15 @@ package com.intellij.execution.filters; import com.intellij.openapi.project.DumbAware; -import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; -import java.util.function.Predicate; +import java.util.Collections; +import java.util.List; public class ExceptionFilter implements Filter, DumbAware { - private final ExceptionInfoCache myCache; - private Predicate myNextLineRefiner; + final ExceptionInfoCache myCache; + private ExceptionLineRefiner myNextLineRefiner; public ExceptionFilter(@NotNull final GlobalSearchScope scope) { myCache = new ExceptionInfoCache(scope); @@ -35,12 +35,24 @@ public class ExceptionFilter implements Filter, DumbAware { ExceptionWorker worker = new ExceptionWorker(myCache); Result result = worker.execute(line, textEndOffset, myNextLineRefiner); if (result == null) { - ExceptionInfo exceptionInfo = ExceptionInfo.parseMessage(line); - if (exceptionInfo == null) return null; - myNextLineRefiner = exceptionInfo.getPositionRefiner(); - return exceptionInfo.makeClassLink(myCache, textEndOffset - line.length()); + ExceptionInfo exceptionInfo = ExceptionInfo.parseMessage(line, textEndOffset); + myNextLineRefiner = exceptionInfo == null ? null : exceptionInfo.getPositionRefiner(); + return null; } + ExceptionInfo prevLineException = myNextLineRefiner == null ? null : myNextLineRefiner.getExceptionInfo(); myNextLineRefiner = worker.getLocationRefiner(); + if (prevLineException != null) { + List exceptionResults = getExceptionClassNameItems(prevLineException); + if (!exceptionResults.isEmpty()) { + exceptionResults.add(result); + return new Result(exceptionResults); + } + } return result; } + + @NotNull + List getExceptionClassNameItems(ExceptionInfo prevLineException) { + return Collections.emptyList(); + } } diff --git a/java/execution/openapi/src/com/intellij/execution/filters/JvmExceptionOccurrenceFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/JvmExceptionOccurrenceFilter.java new file mode 100644 index 000000000000..172a5f2234cd --- /dev/null +++ b/java/execution/openapi/src/com/intellij/execution/filters/JvmExceptionOccurrenceFilter.java @@ -0,0 +1,29 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.filters; + +import com.intellij.execution.filters.Filter.ResultItem; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiClass; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * Filters occurrence of JVM exception (e.g. provide link to source code) + */ +@ApiStatus.Experimental +public interface JvmExceptionOccurrenceFilter { + ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.jvm.exceptionFilter"); + + /** + * @param exceptionClassName exception class name that occurs in the log + * @param classes non-empty list of resolved class candidates + * @param exceptionStartOffset exception class name start offset in the log + * @return new filtering result item or null if nothing should be returned + */ + @Nullable ResultItem applyFilter(@NotNull String exceptionClassName, + @NotNull List classes, + int exceptionStartOffset); +} diff --git a/java/execution/openapi/src/com/intellij/execution/filters/NavigateToExceptionClassFilter.java b/java/execution/openapi/src/com/intellij/execution/filters/NavigateToExceptionClassFilter.java new file mode 100644 index 000000000000..4d5ec41be446 --- /dev/null +++ b/java/execution/openapi/src/com/intellij/execution/filters/NavigateToExceptionClassFilter.java @@ -0,0 +1,26 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.filters; + +import com.intellij.execution.filters.Filter.ResultItem; +import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.psi.PsiClass; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public class NavigateToExceptionClassFilter implements JvmExceptionOccurrenceFilter { + @Override + public @Nullable ResultItem applyFilter(@NotNull String exceptionClassName, + @NotNull List classes, + int exceptionStartOffset) { + PsiClass psiClass = classes.get(0); + boolean inContent = + ProjectRootManager.getInstance(psiClass.getProject()).getFileIndex().isInContent(psiClass.getContainingFile().getVirtualFile()); + TextAttributes attributes = ExceptionInfoCache.ClassResolveInfo.getLinkAttributes(!inContent); + HyperlinkInfo hyperlink = HyperlinkInfoFactory.getInstance().createMultiplePsiElementHyperlinkInfo(classes); + return new Filter.Result(exceptionStartOffset, exceptionStartOffset + exceptionClassName.length(), + hyperlink, attributes, attributes); + } +} diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 619b2228e498..7aa2264fd5b0 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -270,6 +270,8 @@ + + @@ -714,6 +716,8 @@ + + diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java b/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java index 2721daf539c0..aeac8ca7e436 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionInfo.java @@ -1,7 +1,6 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.execution.filters; -import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; @@ -18,7 +17,7 @@ import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; -public class ExceptionInfo { +class ExceptionInfo { private static final Pattern AIOOBE_MESSAGE = Pattern.compile("(?:Index )?(\\d{1,9})(?: out of bounds for length \\d+)?"); private static final Pattern CCE_MESSAGE = Pattern.compile("(?:class )?(\\S+) cannot be cast to (?:class )?(\\S+)(?: \\(.+\\))?"); // See JEP 358 Helpful NullPointerExceptions for details @@ -46,41 +45,35 @@ public class ExceptionInfo { myExceptionMessage = exceptionMessage; } - @NotNull ExceptionInfo withMessage(@Nullable String message) { + @NotNull ExceptionInfo adjust(@Nullable String message, int offset) { message = StringUtil.notNullize(message); - return message.equals(myExceptionMessage) ? this : new ExceptionInfo(myClassNameOffset, myExceptionClassName, message); + return new ExceptionInfo(myClassNameOffset + offset, myExceptionClassName, message); } - /** - * @param cache cache to use - * @param offset line start offset - * @return a hyperlink to exception class; null if class cannot be resolved - */ - @Nullable Filter.Result makeClassLink(ExceptionInfoCache cache, int offset) { - ExceptionInfoCache.ClassResolveInfo info = cache.resolveClass(getExceptionClassName()); - if (info.myClasses.isEmpty()) return null; - TextAttributes attributes = info.getLinkAttributes(); - HyperlinkInfo hyperlink = HyperlinkInfoFactory.getInstance().createMultiplePsiElementHyperlinkInfo(info.myClasses); - int base = myClassNameOffset + offset; - return new Filter.Result(base, base + getExceptionClassName().length(), hyperlink, attributes, attributes); - } - - /** * @return a predicate that matches an element within the source line that is likely an exception source */ - @NotNull Predicate getPositionRefiner() { - Predicate exceptionCreationFilter = e -> { - // We look for new Exception() expression rather than throw statement, because stack-trace is filled in exception constructor - if (!(e instanceof PsiKeyword) || !(e.textMatches(PsiKeyword.NEW))) return false; - PsiNewExpression newExpression = ObjectUtils.tryCast(e.getParent(), PsiNewExpression.class); - if (newExpression == null) return false; - PsiType type = newExpression.getType(); - return type != null && type.equalsToText(getExceptionClassName()); + @NotNull ExceptionLineRefiner getPositionRefiner() { + Predicate specificFilter = getExceptionSpecificFilter(); + return new ExceptionLineRefiner() { + @Override + public boolean test(PsiElement element) { + // We look for new Exception() expression rather than throw statement, because stack-trace is filled in exception constructor + if (element instanceof PsiKeyword && element.textMatches(PsiKeyword.NEW)) { + PsiNewExpression newExpression = ObjectUtils.tryCast(element.getParent(), PsiNewExpression.class); + if (newExpression != null) { + PsiType type = newExpression.getType(); + if (type != null && type.equalsToText(getExceptionClassName())) return true; + } + } + return specificFilter != null && specificFilter.test(element); + } + + @Override + public ExceptionInfo getExceptionInfo() { + return ExceptionInfo.this; + } }; - Predicate specificFilter = this.getExceptionSpecificFilter(); - if (specificFilter == null) return exceptionCreationFilter; - return exceptionCreationFilter.or(specificFilter); } @Nullable @@ -369,7 +362,7 @@ public class ExceptionInfo { } @Nullable - public static ExceptionInfo parseMessage(String line) { + public static ExceptionInfo parseMessage(String line, int textEndOffset) { int firstSpace = line.indexOf(' '); int colonPos = -1; ExceptionInfo info = null; @@ -397,7 +390,7 @@ public class ExceptionInfo { } if (info == null) return null; String message = colonPos == -1 ? null : line.substring(colonPos + 1).trim(); - return info.withMessage(message); + return info.adjust(message, textEndOffset - line.length()); } private static int getLength(String line) { diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionInfoCache.java b/java/openapi/src/com/intellij/execution/filters/ExceptionInfoCache.java index 24b3fc822c84..84905be6b6a5 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionInfoCache.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionInfoCache.java @@ -82,9 +82,9 @@ public class ExceptionInfoCache { myInLibrary = library; } - TextAttributes getLinkAttributes() { + static TextAttributes getLinkAttributes(boolean inLibrary) { TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.HYPERLINK_ATTRIBUTES); - if (myInLibrary) { + if (inLibrary) { Color libTextColor = UIUtil.getInactiveTextColor(); attributes = attributes.clone(); attributes.setForegroundColor(libTextColor); diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java b/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java new file mode 100644 index 000000000000..bc9e05bc580a --- /dev/null +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionLineRefiner.java @@ -0,0 +1,16 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.execution.filters; + +import com.intellij.psi.PsiElement; + +import java.util.function.Predicate; + +/** + * Allows to refine the stacktrace navigation position based on the previous line + */ +@FunctionalInterface +public interface ExceptionLineRefiner extends Predicate { + default ExceptionInfo getExceptionInfo() { + return null; + } +} diff --git a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java index e7f33721ae26..ba31907918fc 100644 --- a/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java +++ b/java/openapi/src/com/intellij/execution/filters/ExceptionWorker.java @@ -50,7 +50,7 @@ public class ExceptionWorker { private String myMethod; private ParsedLine myInfo; private final ExceptionInfoCache myCache; - private Predicate myLocationRefiner; + private ExceptionLineRefiner myLocationRefiner; public ExceptionWorker(@NotNull ExceptionInfoCache cache) { myProject = cache.getProject(); @@ -61,7 +61,7 @@ public class ExceptionWorker { return execute(line, textEndOffset, null); } - public Filter.Result execute(@NotNull String line, final int textEndOffset, @Nullable Predicate elementMatcher) { + public Filter.Result execute(@NotNull String line, final int textEndOffset, @Nullable ExceptionLineRefiner elementMatcher) { myResult = null; myInfo = parseExceptionLine(line); if (myInfo == null || myProject.isDisposed()) { @@ -93,7 +93,7 @@ public class ExceptionWorker { int highlightEndOffset = textStartOffset + myInfo.fileLineRange.getEndOffset(); List virtualFiles = new ArrayList<>(myClassResolveInfo.myClasses.keySet()); - TextAttributes attributes = myClassResolveInfo.getLinkAttributes(); + TextAttributes attributes = ExceptionInfoCache.ClassResolveInfo.getLinkAttributes(myClassResolveInfo.myInLibrary); ToIntFunction columnFinder = elementMatcher == null || myInfo.lineNumber <= 0 ? null : new ExceptionColumnFinder(elementMatcher, myInfo.lineNumber - 1); HyperlinkInfo linkInfo = @@ -112,7 +112,7 @@ public class ExceptionWorker { return result; } - public Predicate getLocationRefiner() { + public ExceptionLineRefiner getLocationRefiner() { return myLocationRefiner; } @@ -309,7 +309,7 @@ public class ExceptionWorker { } } - private static class StackFrameMatcher implements Predicate { + private static class StackFrameMatcher implements ExceptionLineRefiner { private final String myMethodName; private final String myClassName; private final boolean myHasDollarInName; @@ -394,7 +394,7 @@ public class ExceptionWorker { } } - private static class FunctionCallMatcher implements Predicate { + private static class FunctionCallMatcher implements ExceptionLineRefiner { @Override public boolean test(@NotNull PsiElement element) { if (!(element instanceof PsiIdentifier)) return false; diff --git a/platform/lang-api/src/com/intellij/execution/filters/HyperlinkInfoFactory.java b/platform/lang-api/src/com/intellij/execution/filters/HyperlinkInfoFactory.java index ddf8816dd707..d44bfba37a19 100644 --- a/platform/lang-api/src/com/intellij/execution/filters/HyperlinkInfoFactory.java +++ b/platform/lang-api/src/com/intellij/execution/filters/HyperlinkInfoFactory.java @@ -22,8 +22,8 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; +import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.function.ToIntFunction; public abstract class HyperlinkInfoFactory { @@ -51,11 +51,11 @@ public abstract class HyperlinkInfoFactory { ToIntFunction columnFinder); /** - * Creates a hyperlink that points to several files with ability to navigate to specific element within the file + * Creates a hyperlink that points to elements with ability to navigate to specific element within the file * - * @param fileToElement map from files to elements + * @param elements elements list * @return newly create HyperlinkInfo that navigates to given psi elements */ @NotNull - public abstract HyperlinkInfo createMultiplePsiElementHyperlinkInfo(@NotNull Map fileToElement); + public abstract HyperlinkInfo createMultiplePsiElementHyperlinkInfo(@NotNull Collection elements); } diff --git a/platform/lang-impl/src/com/intellij/execution/filters/impl/HyperlinkInfoFactoryImpl.java b/platform/lang-impl/src/com/intellij/execution/filters/impl/HyperlinkInfoFactoryImpl.java index 6640309b19f3..c6218d0bda93 100644 --- a/platform/lang-impl/src/com/intellij/execution/filters/impl/HyperlinkInfoFactoryImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/filters/impl/HyperlinkInfoFactoryImpl.java @@ -24,8 +24,8 @@ import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.function.ToIntFunction; public class HyperlinkInfoFactoryImpl extends HyperlinkInfoFactory { @@ -47,7 +47,7 @@ public class HyperlinkInfoFactoryImpl extends HyperlinkInfoFactory { } @Override - public @NotNull HyperlinkInfo createMultiplePsiElementHyperlinkInfo(@NotNull Map fileToElement) { - return new MultiPsiElementHyperlinkInfo(fileToElement); + public @NotNull HyperlinkInfo createMultiplePsiElementHyperlinkInfo(@NotNull Collection elements) { + return new MultiPsiElementHyperlinkInfo(elements); } } diff --git a/platform/lang-impl/src/com/intellij/execution/filters/impl/MultiPsiElementHyperlinkInfo.java b/platform/lang-impl/src/com/intellij/execution/filters/impl/MultiPsiElementHyperlinkInfo.java index 61848d8137cb..d5408689b68d 100644 --- a/platform/lang-impl/src/com/intellij/execution/filters/impl/MultiPsiElementHyperlinkInfo.java +++ b/platform/lang-impl/src/com/intellij/execution/filters/impl/MultiPsiElementHyperlinkInfo.java @@ -20,22 +20,21 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; class MultiPsiElementHyperlinkInfo extends HyperlinkInfoBase { private final Map> myMap; - MultiPsiElementHyperlinkInfo(Map pointers) { + MultiPsiElementHyperlinkInfo(Collection elements) { SmartPointerManager manager = null; myMap = new LinkedHashMap<>(); - for (Map.Entry entry : pointers.entrySet()) { - VirtualFile file = entry.getKey(); - PsiElement element = entry.getValue(); + for (PsiElement element : elements) { if (manager == null) { manager = SmartPointerManager.getInstance(element.getProject()); } - myMap.put(file, manager.createSmartPsiElementPointer(element)); + myMap.put(element.getContainingFile().getVirtualFile(), manager.createSmartPsiElementPointer(element)); } }