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
This commit is contained in:
Tagir Valeev
2020-04-17 06:14:17 +00:00
committed by intellij-monorepo-bot
parent f9f5528daf
commit 1bd4963517
15 changed files with 252 additions and 121 deletions
@@ -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<PsiClass> 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<JavaExceptionBreakpointProperties> 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);
}
}
}
@@ -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<JavaExceptionBreakpointProperties> 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);
}
}
}
@@ -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<ResultItem> getExceptionClassNameItems(ExceptionInfo prevLineException) {
ExceptionInfoCache.ClassResolveInfo info = myCache.resolveClass(prevLineException.getExceptionClassName());
List<PsiClass> 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<ResultItem> 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;
}
}
@@ -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);
}
}
@@ -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<PsiElement> 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<ResultItem> exceptionResults = getExceptionClassNameItems(prevLineException);
if (!exceptionResults.isEmpty()) {
exceptionResults.add(result);
return new Result(exceptionResults);
}
}
return result;
}
@NotNull
List<ResultItem> getExceptionClassNameItems(ExceptionInfo prevLineException) {
return Collections.emptyList();
}
}
@@ -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<JvmExceptionOccurrenceFilter> 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<PsiClass> classes,
int exceptionStartOffset);
}
@@ -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<PsiClass> 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);
}
}
@@ -270,6 +270,8 @@
<!--<extensionPoint qualifiedName="com.intellij.expectedTypesProvider" interface="com.intellij.codeInsight.ExpectedTypesProviderExtension"/>-->
<extensionPoint qualifiedName="com.intellij.library.javaSourceRootDetector" interface="com.intellij.openapi.roots.libraries.ui.RootDetector"/>
<extensionPoint qualifiedName="com.intellij.jvm.exceptionFilter" interface="com.intellij.execution.filters.JvmExceptionOccurrenceFilter"/>
</extensionPoints>
@@ -714,6 +716,8 @@
<xdebugger.localAttachDebuggerProvider implementation="com.intellij.debugger.impl.attach.JavaSAAttachDebuggerProvider"/>
<consoleFilterProvider implementation="com.intellij.debugger.impl.attach.JavaDebuggerConsoleFilterProvider"/>
<jvm.exceptionFilter implementation="com.intellij.debugger.impl.attach.JavaDebuggerAddExceptionBreakpointFilter"/>
<jvm.exceptionFilter implementation="com.intellij.execution.filters.NavigateToExceptionClassFilter"/>
<xdebugger.breakpointType implementation="com.intellij.debugger.ui.breakpoints.JavaMethodBreakpointType"/>
<xdebugger.breakpointType implementation="com.intellij.debugger.ui.breakpoints.JavaWildcardMethodBreakpointType"/>
@@ -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<PsiElement> getPositionRefiner() {
Predicate<PsiElement> 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<PsiElement> 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<PsiElement> 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) {
@@ -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);
@@ -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<PsiElement> {
default ExceptionInfo getExceptionInfo() {
return null;
}
}
@@ -50,7 +50,7 @@ public class ExceptionWorker {
private String myMethod;
private ParsedLine myInfo;
private final ExceptionInfoCache myCache;
private Predicate<PsiElement> 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<PsiElement> 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<VirtualFile> virtualFiles = new ArrayList<>(myClassResolveInfo.myClasses.keySet());
TextAttributes attributes = myClassResolveInfo.getLinkAttributes();
TextAttributes attributes = ExceptionInfoCache.ClassResolveInfo.getLinkAttributes(myClassResolveInfo.myInLibrary);
ToIntFunction<PsiFile> 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<PsiElement> getLocationRefiner() {
public ExceptionLineRefiner getLocationRefiner() {
return myLocationRefiner;
}
@@ -309,7 +309,7 @@ public class ExceptionWorker {
}
}
private static class StackFrameMatcher implements Predicate<PsiElement> {
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<PsiElement> {
private static class FunctionCallMatcher implements ExceptionLineRefiner {
@Override
public boolean test(@NotNull PsiElement element) {
if (!(element instanceof PsiIdentifier)) return false;
@@ -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<? super PsiFile> 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<? extends VirtualFile, ? extends PsiElement> fileToElement);
public abstract HyperlinkInfo createMultiplePsiElementHyperlinkInfo(@NotNull Collection<? extends PsiElement> elements);
}
@@ -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<? extends VirtualFile, ? extends PsiElement> fileToElement) {
return new MultiPsiElementHyperlinkInfo(fileToElement);
public @NotNull HyperlinkInfo createMultiplePsiElementHyperlinkInfo(@NotNull Collection<? extends PsiElement> elements) {
return new MultiPsiElementHyperlinkInfo(elements);
}
}
@@ -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<VirtualFile, SmartPsiElementPointer<?>> myMap;
MultiPsiElementHyperlinkInfo(Map<? extends VirtualFile, ? extends PsiElement> pointers) {
MultiPsiElementHyperlinkInfo(Collection<? extends PsiElement> elements) {
SmartPointerManager manager = null;
myMap = new LinkedHashMap<>();
for (Map.Entry<? extends VirtualFile, ? extends PsiElement> 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));
}
}