IDEA-127197 Remote debug: easy way to change library used to display sources
authorEgor.Ushakov <egor.ushakov@jetbrains.com>
Wed, 27 May 2015 12:27:43 +0000 (15:27 +0300)
committerEgor.Ushakov <egor.ushakov@jetbrains.com>
Wed, 27 May 2015 12:28:41 +0000 (15:28 +0300)
java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java
java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java
java/debugger/impl/src/com/intellij/debugger/ui/AlternativeSourceNotificationProvider.java [new file with mode: 0644]
resources-en/src/messages/DebuggerBundle.properties
resources/src/idea/RichPlatformPlugin.xml

index 7dd68b3cc546827f7661efeb5a1b5782c3407840..30f778c159c6d796f4522f3945a12bec48e35445 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
 import com.intellij.debugger.impl.*;
 import com.intellij.debugger.jdi.StackFrameProxyImpl;
 import com.intellij.debugger.settings.DebuggerSettings;
+import com.intellij.debugger.ui.AlternativeSourceNotificationProvider;
 import com.intellij.debugger.ui.DebuggerContentInfo;
 import com.intellij.debugger.ui.breakpoints.Breakpoint;
 import com.intellij.debugger.ui.impl.ThreadsPanel;
@@ -43,6 +44,8 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Disposer;
 import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.EditorNotifications;
 import com.intellij.ui.content.Content;
 import com.intellij.ui.content.ContentManagerAdapter;
 import com.intellij.ui.content.ContentManagerEvent;
@@ -154,17 +157,29 @@ public class JavaDebugProcess extends XDebugProcess {
       @Override
       public void sessionPaused() {
         saveNodeHistory();
+        showAlternativeNotification(session.getCurrentStackFrame());
       }
 
       @Override
       public void stackFrameChanged() {
         XStackFrame frame = session.getCurrentStackFrame();
         if (frame instanceof JavaStackFrame) {
+          showAlternativeNotification(frame);
           StackFrameProxyImpl frameProxy = ((JavaStackFrame)frame).getStackFrameProxy();
           DebuggerContextUtil.setStackFrame(javaSession.getContextManager(), frameProxy);
           saveNodeHistory(frameProxy);
         }
       }
+
+      private void showAlternativeNotification(XStackFrame frame) {
+        XSourcePosition position = frame.getSourcePosition();
+        if (position != null) {
+          VirtualFile file = position.getFile();
+          if (!AlternativeSourceNotificationProvider.fileProcessed(file)) {
+            EditorNotifications.getInstance(session.getProject()).updateNotifications(file);
+          }
+        }
+      }
     });
   }
 
index 02883527f75b97728fe24fde3f2b310527dd6662..c645439010ee72c9ec575900459c51be91a7a8ab 100644 (file)
@@ -700,6 +700,13 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils {
     return new JavaXSourcePosition(position, file);
   }
 
+  private static final Key<VirtualFile> ALTERNATIVE_SOURCE_KEY = new Key<VirtualFile>("DEBUGGER_ALTERNATIVE_SOURCE");
+
+  public static void setAlternativeSource(VirtualFile source, VirtualFile dest) {
+    ALTERNATIVE_SOURCE_KEY.set(source, dest);
+    ALTERNATIVE_SOURCE_KEY.set(dest, null);
+  }
+
   private static class JavaXSourcePosition implements XSourcePosition, ExecutionPointHighlighter.HighlighterProvider {
     private final SourcePosition mySourcePosition;
     @NotNull private final VirtualFile myFile;
@@ -722,6 +729,10 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils {
     @NotNull
     @Override
     public VirtualFile getFile() {
+      VirtualFile file = ALTERNATIVE_SOURCE_KEY.get(myFile);
+      if (file != null) {
+        return file;
+      }
       return myFile;
     }
 
diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/AlternativeSourceNotificationProvider.java b/java/debugger/impl/src/com/intellij/debugger/ui/AlternativeSourceNotificationProvider.java
new file mode 100644 (file)
index 0000000..345b43b
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2000-2015 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.debugger.ui;
+
+import com.intellij.debugger.DebuggerBundle;
+import com.intellij.debugger.impl.DebuggerUtilsEx;
+import com.intellij.ide.highlighter.JavaClassFileType;
+import com.intellij.openapi.fileEditor.FileEditor;
+import com.intellij.openapi.fileEditor.FileEditorManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.ui.EditorNotificationPanel;
+import com.intellij.ui.EditorNotifications;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.xdebugger.XDebugSession;
+import com.intellij.xdebugger.XDebuggerManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+
+/**
+ * @author egor
+ */
+public class AlternativeSourceNotificationProvider extends EditorNotifications.Provider<EditorNotificationPanel> {
+  private static final Key<EditorNotificationPanel> KEY = Key.create("AlternativeSource");
+  private static final Key<Boolean> FILE_PROCESSED_KEY = Key.create("AlternativeSourceCheckDone");
+  private final Project myProject;
+
+  public AlternativeSourceNotificationProvider(Project project) {
+    myProject = project;
+  }
+
+  @NotNull
+  @Override
+  public Key<EditorNotificationPanel> getKey() {
+    return KEY;
+  }
+
+  @Nullable
+  @Override
+  public EditorNotificationPanel createNotificationPanel(@NotNull VirtualFile file, @NotNull FileEditor fileEditor) {
+    if (XDebuggerManager.getInstance(myProject).getCurrentSession() == null) {
+      FILE_PROCESSED_KEY.set(file, null);
+      return null;
+    }
+
+    if (file.getFileType() == JavaClassFileType.INSTANCE) return null;
+
+    final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file);
+    if (psiFile == null) return null;
+
+    if (!(psiFile instanceof PsiJavaFile)) return null;
+
+    PsiClass[] classes = ((PsiJavaFile)psiFile).getClasses();
+    if (classes.length == 0) return null;
+
+    PsiClass baseClass = classes[0];
+    String name = baseClass.getQualifiedName();
+
+    if (name == null) return null;
+
+    if (DumbService.getInstance(myProject).isDumb()) return null;
+
+    PsiClass[] alternatives = JavaPsiFacade.getInstance(myProject).findClasses(name, GlobalSearchScope.allScope(myProject));
+
+    FILE_PROCESSED_KEY.set(file, true);
+
+    if (alternatives.length > 1) {
+      ArrayList<PsiClass> alts = ContainerUtil.newArrayList(alternatives);
+      for (PsiClass cls : alts) {
+        if (cls.equals(baseClass) || cls.getNavigationElement().equals(baseClass)) {
+          alts.remove(cls);
+          break;
+        }
+      }
+      alts.add(0, baseClass);
+
+      ComboBoxClassElement[] elems = ContainerUtil.map2Array(alts,
+                                                             ComboBoxClassElement.class,
+                                                             new Function<PsiClass, ComboBoxClassElement>() {
+                                                               @Override
+                                                               public ComboBoxClassElement fun(PsiClass psiClass) {
+                                                                 return new ComboBoxClassElement(psiClass);
+                                                               }
+                                                             });
+
+      return new AlternativeSourceNotificationPanel(elems, baseClass, myProject, file);
+    }
+    return null;
+  }
+
+  private static class ComboBoxClassElement {
+    private final PsiClass myClass;
+    private String myText;
+
+    public ComboBoxClassElement(PsiClass aClass) {
+      myClass = aClass;
+    }
+
+    @Override
+    public String toString() {
+      if (myText == null) {
+        Module module = ModuleUtilCore.findModuleForPsiElement(myClass);
+        if (module != null) {
+          myText = module.getName();
+        }
+        else {
+          VirtualFile virtualFile = myClass.getContainingFile().getVirtualFile();
+          final ProjectFileIndex index = ProjectRootManager.getInstance(myClass.getProject()).getFileIndex();
+          VirtualFile root = index.getSourceRootForFile(virtualFile);
+          if (root == null) {
+            root = index.getClassRootForFile(virtualFile);
+          }
+          if (root != null) {
+            myText = root.getName();
+          }
+          else {
+            myText = virtualFile.getPath();
+          }
+        }
+      }
+      return myText;
+    }
+  }
+
+  public static boolean fileProcessed(VirtualFile file) {
+    return FILE_PROCESSED_KEY.get(file) != null;
+  }
+
+  private static class AlternativeSourceNotificationPanel extends EditorNotificationPanel {
+    public AlternativeSourceNotificationPanel(ComboBoxClassElement[] alternatives,
+                                              final PsiClass aClass,
+                                              final Project project,
+                                              final VirtualFile file) {
+      setText(DebuggerBundle.message("editor.notification.alternative.source", aClass.getQualifiedName()));
+      final ComboBox switcher = new ComboBox(alternatives);
+      switcher.addActionListener(new ActionListener() {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          FileEditorManager.getInstance(project).closeFile(file);
+          PsiClass item = ((ComboBoxClassElement)switcher.getSelectedItem()).myClass;
+          item = (PsiClass)item.getNavigationElement(); // go through compiled
+          DebuggerUtilsEx.setAlternativeSource(file, item.getContainingFile().getVirtualFile());
+          item.navigate(true);
+          XDebugSession session = XDebuggerManager.getInstance(project).getCurrentSession();
+          if (session != null) {
+            session.updateExecutionPosition();
+          }
+        }
+      });
+      myLinksPanel.add(switcher);
+    }
+  }
+}
index 717c257fc5f38a5f1c42c40ef3715375fcfd8cd5..cfe23b36e178d7d3103b8196ea850869dec147c8 100644 (file)
@@ -460,4 +460,5 @@ action.kill.process.description=Forcibly terminate debugged application
 evaluation.error.unknown.method.return.type=Cannot resolve method return type: {0}
 rule.name.group.by.class=Group by class
 rule.name.group.by.package=Group by package
-error.context.has.changed=Context has changed, operation is not possible
\ No newline at end of file
+error.context.has.changed=Context has changed, operation is not possible
+editor.notification.alternative.source=Alternative source available for the class {0}
index 3f7e3b72f0d1d0e7a83ebdb818a1b795bf220d70..f5245a887992b5c0226236c1e4264484585da07c 100644 (file)
 
     <editorNotificationProvider implementation="com.intellij.codeInsight.daemon.impl.AttachSourcesNotificationProvider"/>
     <editorNotificationProvider implementation="com.intellij.codeInsight.daemon.impl.SetupSDKNotificationProvider"/>
+    <editorNotificationProvider implementation="com.intellij.debugger.ui.AlternativeSourceNotificationProvider"/>
 
     <attachSourcesProvider implementation="com.intellij.jarFinder.InternetAttachSourceProvider"/>