fixed clicking on stacktrace links in console when several classes are found: prefer modules over libraries, show popup if necessary (IDEA-52913)

This commit is contained in:
nik
2013-11-27 09:49:51 +04:00
parent 691e7e270f
commit 414a185e2e
9 changed files with 307 additions and 88 deletions
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.markup.EffectType;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.*;
@@ -84,10 +85,16 @@ public class ExceptionExFilterFactory implements ExceptionFilterFactory {
worker.execute(text, lineEndOffset);
Result result = worker.getResult();
if (result == null) continue;
OpenFileHyperlinkInfo hyperlinkInfo = ExceptionWorker.getOpenFileHyperlinkInfo(result);
int offset = hyperlinkInfo == null? -1 : hyperlinkInfo.getDescriptor().getOffset();
HyperlinkInfo hyperlinkInfo = result.hyperlinkInfo;
if (!(hyperlinkInfo instanceof FileHyperlinkInfo)) continue;
OpenFileDescriptor descriptor = ((FileHyperlinkInfo)hyperlinkInfo).getDescriptor();
if (descriptor == null) continue;
int offset = descriptor.getOffset();
PsiFile psiFile = worker.getFile();
if (offset <= 0 || psiFile == null) continue;
PsiElement element = psiFile.findElementAt(offset);
PsiTryStatement parent = PsiTreeUtil.getParentOfType(element, PsiTryStatement.class, true, PsiClass.class);
PsiCodeBlock tryBlock = parent != null? parent.getTryBlock() : null;
@@ -15,26 +15,29 @@
*/
package com.intellij.execution.filters;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.colors.CodeInsightColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.Trinity;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* User: Irina.Chernushina
@@ -61,8 +64,8 @@ public class ExceptionWorker {
private final Project myProject;
private final GlobalSearchScope mySearchScope;
private Filter.Result myResult;
private PsiClass myClass;
private PsiFile myFile;
private PsiClass[] myClasses = PsiClass.EMPTY_ARRAY;
private PsiFile[] myFiles = PsiFile.EMPTY_ARRAY;
private String myMethod;
private Trinity<TextRange, TextRange, TextRange> myInfo;
@@ -90,16 +93,17 @@ public class ExceptionWorker {
final String lineString = fileAndLine.substring(colonIndex + 1);
try {
final int lineNumber = Integer.parseInt(lineString);
myClass = findPositionClass(line);
myFile = myClass == null ? null : (PsiFile)myClass.getContainingFile().getNavigationElement();
if (myFile == null) {
// try find the file with the required name
PsiFile[] files = PsiShortNamesCache.getInstance(myProject).getFilesByName(fileAndLine.substring(0, colonIndex).trim());
if (files.length > 0) {
myFile = files[0];
}
myClasses = findPositionClasses(line);
myFiles = new PsiFile[myClasses.length];
for (int i = 0; i < myClasses.length; i++) {
myFiles[i] = (PsiFile)myClasses[i].getContainingFile().getNavigationElement();
}
if (myFile == null) return;
if (myFiles.length == 0) {
// try find the file with the required name
//todo[nik] it would be better to use FilenameIndex here to honor the scope by it isn't accessible in Open API
myFiles = PsiShortNamesCache.getInstance(myProject).getFilesByName(fileAndLine.substring(0, colonIndex).trim());
}
if (myFiles.length == 0) return;
/*
IDEADEV-4976: Some scramblers put something like SourceFile mock instead of real class name.
@@ -113,12 +117,31 @@ public class ExceptionWorker {
final int highlightStartOffset = textStartOffset + lparenthIndex + 1;
final int highlightEndOffset = textStartOffset + rparenthIndex;
final VirtualFile virtualFile = myFile.getVirtualFile();
HyperlinkInfo linkInfo = new MyHyperlinkInfo(myProject, virtualFile, lineNumber);
ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex();
List<VirtualFile> virtualFilesInLibraries = new ArrayList<VirtualFile>();
List<VirtualFile> virtualFilesInContent = new ArrayList<VirtualFile>();
for (PsiFile file : myFiles) {
VirtualFile virtualFile = file.getVirtualFile();
if (index.isInContent(virtualFile)) {
virtualFilesInContent.add(virtualFile);
}
else {
virtualFilesInLibraries.add(virtualFile);
}
}
boolean inContent = ProjectRootManager.getInstance(myProject).getFileIndex().isInContent(virtualFile);
TextAttributes attributes = inContent ? HYPERLINK_ATTRIBUTES : LIBRARY_HYPERLINK_ATTRIBUTES;
List<VirtualFile> virtualFiles;
TextAttributes attributes;
if (virtualFilesInContent.isEmpty()) {
attributes = LIBRARY_HYPERLINK_ATTRIBUTES;
virtualFiles = virtualFilesInLibraries;
}
else {
attributes = HYPERLINK_ATTRIBUTES;
virtualFiles = virtualFilesInContent;
}
HyperlinkInfo linkInfo = HyperlinkInfoFactory.getInstance().createMultipleFilesHyperlinkInfo(virtualFiles, lineNumber - 1, myProject);
myResult = new Filter.Result(highlightStartOffset, highlightEndOffset, linkInfo, attributes);
}
catch (NumberFormatException e) {
@@ -126,22 +149,23 @@ public class ExceptionWorker {
}
}
private PsiClass findPositionClass(String line) {
private PsiClass[] findPositionClasses(String line) {
String className = myInfo.first.substring(line).trim();
PsiClass result = findClassPreferringMyScope(className);
if (result == null) {
PsiClass[] result = findClassesPreferringMyScope(className);
if (result.length == 0) {
final int dollarIndex = className.indexOf('$');
if (dollarIndex >= 0) {
result = findClassPreferringMyScope(className.substring(0, dollarIndex));
result = findClassesPreferringMyScope(className.substring(0, dollarIndex));
}
}
return result;
}
private PsiClass findClassPreferringMyScope(String className) {
@NotNull
private PsiClass[] findClassesPreferringMyScope(String className) {
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(myProject);
PsiClass result = psiFacade.findClass(className, mySearchScope);
return result != null ? result : psiFacade.findClass(className, GlobalSearchScope.allScope(myProject));
PsiClass[] result = psiFacade.findClasses(className, mySearchScope);
return result.length != 0 ? result : psiFacade.findClasses(className, GlobalSearchScope.allScope(myProject));
}
public Filter.Result getResult() {
@@ -149,7 +173,7 @@ public class ExceptionWorker {
}
public PsiClass getPsiClass() {
return myClass;
return ArrayUtil.getFirstElement(myClasses);
}
public String getMethod() {
@@ -157,7 +181,7 @@ public class ExceptionWorker {
}
public PsiFile getFile() {
return myFile;
return ArrayUtil.getFirstElement(myFiles);
}
public Trinity<TextRange, TextRange, TextRange> getInfo() {
@@ -205,59 +229,4 @@ public class ExceptionWorker {
}
return pos;
}
@Nullable
static OpenFileHyperlinkInfo getOpenFileHyperlinkInfo(Filter.Result result) {
if (result.hyperlinkInfo instanceof MyHyperlinkInfo) {
MyHyperlinkInfo info = (MyHyperlinkInfo)result.hyperlinkInfo;
return new OpenFileHyperlinkInfo(info.myProject, info.myVirtualFile, info.myLineNumber);
}
return null;
}
private static class MyHyperlinkInfo implements FileHyperlinkInfo {
private final VirtualFile myVirtualFile;
private final int myLineNumber;
private final Project myProject;
public MyHyperlinkInfo(@NotNull Project project, @NotNull VirtualFile virtualFile, int lineNumber) {
myProject = project;
myVirtualFile = virtualFile;
myLineNumber = lineNumber;
}
@Override
public void navigate(Project project) {
VirtualFile currentVirtualFile = null;
AccessToken accessToken = ReadAction.start();
try {
if (!myVirtualFile.isValid()) return;
PsiFile psiFile = PsiManager.getInstance(project).findFile(myVirtualFile);
if (psiFile != null) {
PsiElement navigationElement = psiFile.getNavigationElement(); // Sources may be downloaded.
if (navigationElement instanceof PsiFile) {
currentVirtualFile = ((PsiFile)navigationElement).getVirtualFile();
}
}
if (currentVirtualFile == null) {
currentVirtualFile = myVirtualFile;
}
}
finally {
accessToken.finish();
}
new OpenFileHyperlinkInfo(myProject, currentVirtualFile, myLineNumber - 1).navigate(project);
}
@Nullable
@Override
public OpenFileDescriptor getDescriptor() {
return new OpenFileDescriptor(myProject, myVirtualFile, myLineNumber - 1, 0);
}
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.filters;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author nik
*/
public abstract class HyperlinkInfoFactory {
@NotNull
public static HyperlinkInfoFactory getInstance() {
return ServiceManager.getService(HyperlinkInfoFactory.class);
}
@NotNull
public abstract HyperlinkInfo createMultipleFilesHyperlinkInfo(@NotNull List<VirtualFile> files,
int line, @NotNull Project project);
}
@@ -0,0 +1,36 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.filters.impl;
import com.intellij.execution.filters.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author nik
*/
public class HyperlinkInfoFactoryImpl extends HyperlinkInfoFactory {
@NotNull
@Override
public HyperlinkInfo createMultipleFilesHyperlinkInfo(@NotNull List<VirtualFile> files,
int line, @NotNull Project project) {
return new MultipleFilesHyperlinkInfo(files, line, project);
}
}
@@ -0,0 +1,119 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.filters.impl;
import com.intellij.execution.filters.FileHyperlinkInfo;
import com.intellij.execution.filters.HyperlinkInfoBase;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.ide.util.gotoByName.GotoFileCellRenderer;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
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.ui.components.JBList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author nik
*/
class MultipleFilesHyperlinkInfo extends HyperlinkInfoBase implements FileHyperlinkInfo {
private final List<VirtualFile> myVirtualFiles;
private final int myLineNumber;
private final Project myProject;
public MultipleFilesHyperlinkInfo(@NotNull List<VirtualFile> virtualFiles, int lineNumber, @NotNull Project project) {
myVirtualFiles = virtualFiles;
myLineNumber = lineNumber;
myProject = project;
}
@Override
public void navigate(@NotNull final Project project, @Nullable RelativePoint hyperlinkLocationPoint) {
List<PsiFile> currentFiles = new ArrayList<PsiFile>();
AccessToken accessToken = ReadAction.start();
try {
for (VirtualFile file : myVirtualFiles) {
if (!file.isValid()) continue;
PsiFile psiFile = PsiManager.getInstance(project).findFile(file);
if (psiFile != null) {
PsiElement navigationElement = psiFile.getNavigationElement(); // Sources may be downloaded.
if (navigationElement instanceof PsiFile) {
currentFiles.add((PsiFile)navigationElement);
continue;
}
currentFiles.add(psiFile);
}
}
}
finally {
accessToken.finish();
}
if (currentFiles.isEmpty()) return;
if (currentFiles.size() == 1) {
new OpenFileHyperlinkInfo(myProject, currentFiles.get(0).getVirtualFile(), myLineNumber).navigate(project);
}
else {
final JBList list = new JBList(currentFiles);
int width = WindowManager.getInstance().getFrame(project).getSize().width;
list.setCellRenderer(new GotoFileCellRenderer(width));
JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(list)
.setTitle("Choose Target File")
.setItemChoosenCallback(new Runnable() {
@Override
public void run() {
VirtualFile file = ((PsiFile)list.getSelectedValue()).getVirtualFile();
new OpenFileHyperlinkInfo(myProject, file, myLineNumber).navigate(project);
}
})
.createPopup();
if (hyperlinkLocationPoint != null) {
popup.show(hyperlinkLocationPoint);
}
else {
popup.showInFocusCenter();
}
}
}
@Nullable
@Override
public OpenFileDescriptor getDescriptor() {
VirtualFile file = getPreferredFile();
return file != null ? new OpenFileDescriptor(myProject, file, myLineNumber, 0) : null;
}
@Nullable
private VirtualFile getPreferredFile() {
return ContainerUtil.getFirstItem(myVirtualFiles);
}
}
@@ -65,6 +65,7 @@ import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.ui.EditorNotificationPanel;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.EditorPopupHandler;
@@ -1384,10 +1385,18 @@ public class ConsoleViewImpl extends JPanel implements ConsoleView, ObservableCo
return EditorHyperlinkSupport.getNextOccurrence(myEditor, hyperlinks.getHyperlinks().keySet(), delta, new Consumer<RangeHighlighter>() {
@Override
public void consume(RangeHighlighter next) {
scrollTo(next.getStartOffset());
int offset = next.getStartOffset();
scrollTo(offset);
final HyperlinkInfo hyperlinkInfo = hyperlinks.getHyperlinks().get(next);
if (hyperlinkInfo != null) {
hyperlinkInfo.navigate(myProject);
if (hyperlinkInfo instanceof HyperlinkInfoBase) {
VisualPosition position = myEditor.offsetToVisualPosition(offset);
Point point = myEditor.visualPositionToXY(new VisualPosition(position.getLine() + 1, position.getColumn()));
((HyperlinkInfoBase)hyperlinkInfo).navigate(myProject, new RelativePoint(myEditor.getContentComponent(), point));
}
else {
hyperlinkInfo.navigate(myProject);
}
}
}
});
@@ -0,0 +1,33 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.execution.filters;
import com.intellij.openapi.project.Project;
import com.intellij.ui.awt.RelativePoint;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author nik
*/
public abstract class HyperlinkInfoBase implements HyperlinkInfo {
public abstract void navigate(@NotNull Project project, @Nullable RelativePoint hyperlinkLocationPoint);
@Override
public void navigate(Project project) {
navigate(project, null);
}
}
@@ -18,6 +18,7 @@ package com.intellij.execution.impl;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.FilterMixin;
import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.execution.filters.HyperlinkInfoBase;
import com.intellij.ide.OccurenceNavigator;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
@@ -36,6 +37,7 @@ import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.pom.NavigatableAdapter;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.BeforeAfter;
import com.intellij.util.Consumer;
import com.intellij.util.SmartList;
@@ -87,7 +89,12 @@ public class EditorHyperlinkSupport {
if (range != null) {
final HyperlinkInfo info = myHighlighterToMessageInfoMap.get(range);
if (info != null) {
info.navigate(project);
if (info instanceof HyperlinkInfoBase) {
((HyperlinkInfoBase)info).navigate(project, new RelativePoint(mouseEvent));
}
else {
info.navigate(project);
}
linkFollowed(editor, getHyperlinks().keySet(), range);
}
}
@@ -44,6 +44,8 @@
<applicationService serviceInterface="com.intellij.execution.filters.TextConsoleBuilderFactory"
serviceImplementation="com.intellij.execution.filters.TextConsoleBuilderFactoryImpl"/>
<applicationService serviceInterface="com.intellij.execution.filters.HyperlinkInfoFactory"
serviceImplementation="com.intellij.execution.filters.impl.HyperlinkInfoFactoryImpl"/>
<applicationService serviceInterface="com.intellij.lang.PsiBuilderFactory"
serviceImplementation="com.intellij.lang.impl.PsiBuilderFactoryImpl"/>