1. added "copy to clipboard" action to thread dump console

2. thread dumps from a debug session are now shown in the relevant debug tab
This commit is contained in:
Eugene Zhuravlev
2009-09-07 18:22:23 +04:00
parent 87fa2e13aa
commit fe334242ec
5 changed files with 222 additions and 121 deletions
@@ -11,6 +11,8 @@ import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.debugger.impl.DebuggerContextImpl;
import com.intellij.debugger.impl.DebuggerSession;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.ui.DebuggerPanelsManager;
import com.intellij.debugger.ui.DebuggerSessionTab;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
@@ -20,7 +22,6 @@ import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.project.Project;
import com.intellij.unscramble.ThreadDumpParser;
import com.intellij.unscramble.ThreadState;
import com.intellij.unscramble.UnscrambleDialog;
import com.sun.jdi.*;
import java.util.ArrayList;
@@ -47,7 +48,10 @@ public class ThreadDumpAction extends AnAction {
final List<ThreadState> threads = buildThreadStates(vm);
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
UnscrambleDialog.addConsole(project, threads);
final DebuggerSessionTab sessionTab = DebuggerPanelsManager.getInstance(project).getSessionTab();
if (sessionTab != null) {
sessionTab.addThreadDump(threads);
}
}
}, ModalityState.NON_MODAL);
}
@@ -18,6 +18,7 @@ import com.intellij.debugger.ui.impl.watch.*;
import com.intellij.execution.*;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.executors.DefaultDebugExecutor;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.execution.runners.RestartAction;
@@ -41,6 +42,8 @@ import com.intellij.ui.content.AlertIcon;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManagerAdapter;
import com.intellij.ui.content.ContentManagerEvent;
import com.intellij.unscramble.ThreadDumpPanel;
import com.intellij.unscramble.ThreadState;
import com.intellij.xdebugger.XDebuggerBundle;
import com.intellij.xdebugger.impl.actions.XDebuggerActions;
import com.intellij.xdebugger.impl.ui.DebuggerLogConsoleManagerBase;
@@ -50,6 +53,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.TreePath;
import java.util.Collection;
import java.util.List;
public class DebuggerSessionTab extends DebuggerLogConsoleManagerBase implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.DebuggerSessionTab");
@@ -75,6 +79,7 @@ public class DebuggerSessionTab extends DebuggerLogConsoleManagerBase implements
public static final String BREAKPOINT_CONDITION = "breakpoint";
private final ThreadsPanel myThreadsPanel;
private static final String THREAD_DUMP_CONTENT_PREFIX = "Dump";
public DebuggerSessionTab(Project project, String sessionName) {
super(project);
@@ -481,6 +486,49 @@ public class DebuggerSessionTab extends DebuggerLogConsoleManagerBase implements
myUi.selectAndFocus(myUi.findContent(DebuggerContentInfo.FRAME_CONTENT), false);
}
private int myThreadDumpsCount = 0;
private int myCurrentThreadDumpId = 1;
public void addThreadDump(List<ThreadState> threads) {
final Project project = getProject();
final ConsoleView consoleView = TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole();
final DefaultActionGroup toolbarActions = new DefaultActionGroup();
final ThreadDumpPanel panel = new ThreadDumpPanel(project, consoleView, toolbarActions, threads);
final Icon icon = null;
final String id = createThreadDumpContentId();
final Content content = myUi.createContent(id, panel, id, icon, null);
content.setCloseable(true);
content.setDescription("Thread Dump");
myUi.addListener(new ContentManagerAdapter() {
public void contentRemoved(ContentManagerEvent event) {
if (event.getContent() == content) {
myThreadDumpsCount -= 1;
if (myThreadDumpsCount == 0) {
myCurrentThreadDumpId = 1;
}
myUi.removeListener(this);
}
}
}, content);
myUi.addContent(content);
myThreadDumpsCount += 1;
myCurrentThreadDumpId += 1;
Disposer.register(this, new Disposable() {
public void dispose() {
myUi.removeContent(content, true);
}
});
myUi.selectAndFocus(content, false);
if (threads.size() > 0) {
panel.selectStackFrame(0);
}
}
private String createThreadDumpContentId() {
return THREAD_DUMP_CONTENT_PREFIX + " #" + myCurrentThreadDumpId;
}
private class MyDebuggerStateManager extends DebuggerStateManager {
public void fireStateChanged(DebuggerContextImpl newContext, int event) {
super.fireStateChanged(newContext, event);
@@ -0,0 +1,164 @@
package com.intellij.unscramble;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.ColoredListCellRenderer;
import com.intellij.ui.LightColors;
import com.intellij.ui.ListToolTipHandler;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.ui.UIUtil;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.util.List;
public class ThreadDumpPanel extends JPanel {
private static final Icon PAUSE_ICON = IconLoader.getIcon("/debugger/threadStates/paused.png");
private static final Icon LOCKED_ICON = IconLoader.getIcon("/debugger/threadStates/locked.png");
private static final Icon RUNNING_ICON = IconLoader.getIcon("/debugger/threadStates/running.png");
private static final Icon SOCKET_ICON = IconLoader.getIcon("/debugger/threadStates/socket.png");
private static final Icon IDLE_ICON = IconLoader.getIcon("/debugger/threadStates/idle.png");
private static final Icon EDT_BUSY_ICON = IconLoader.getIcon("/debugger/threadStates/edtBusy.png");
private static final Icon IO_ICON = IconLoader.getIcon("/debugger/threadStates/io.png");
private JList myThreadList;
public ThreadDumpPanel(Project project, final ConsoleView consoleView, final DefaultActionGroup toolbarActions, final List<ThreadState> threadDump) {
super(new BorderLayout());
myThreadList = new JList(threadDump.toArray(new ThreadState[threadDump.size()]));
myThreadList.setCellRenderer(new ThreadListCellRenderer());
myThreadList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myThreadList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(final ListSelectionEvent e) {
int index = myThreadList.getSelectedIndex();
if (index >= 0) {
ThreadState selection = threadDump.get(index);
AnalyzeStacktraceUtil.printStacktrace(consoleView, selection.getStackTrace());
}
else {
AnalyzeStacktraceUtil.printStacktrace(consoleView, "");
}
myThreadList.repaint();
}
});
ListToolTipHandler.install(myThreadList);
toolbarActions.add(new CopyToClipboardAction(threadDump, project));
add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions,false).getComponent(), BorderLayout.WEST);
final Splitter splitter = new Splitter(false, 0.3f);
splitter.setFirstComponent(new JScrollPane(myThreadList));
splitter.setSecondComponent(consoleView.getComponent());
add(splitter, BorderLayout.CENTER);
}
private static Icon getThreadStateIcon(final ThreadState threadState) {
if (threadState.isSleeping()) {
return PAUSE_ICON;
}
if (threadState.isWaiting()) {
return LOCKED_ICON;
}
if (threadState.getOperation() == ThreadOperation.Socket) {
return SOCKET_ICON;
}
if (threadState.getOperation() == ThreadOperation.IO) {
return IO_ICON;
}
if (threadState.isEDT()) {
if ("idle".equals(threadState.getThreadStateDetail())) {
return IDLE_ICON;
}
return EDT_BUSY_ICON;
}
return RUNNING_ICON;
}
private static SimpleTextAttributes getAttributes(final ThreadState threadState) {
if (threadState.isSleeping()) {
return SimpleTextAttributes.GRAY_ATTRIBUTES;
}
if (threadState.isEmptyStackTrace() || ThreadDumpParser.isKnownJdkThread(threadState)) {
return new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, Color.GRAY.brighter());
}
if (threadState.isEDT()) {
return SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES;
}
return SimpleTextAttributes.REGULAR_ATTRIBUTES;
}
private static class ThreadListCellRenderer extends ColoredListCellRenderer {
protected void customizeCellRenderer(final JList list, final Object value, final int index, final boolean selected, final boolean hasFocus) {
ThreadState threadState = (ThreadState) value;
setIcon(getThreadStateIcon(threadState));
if (!selected) {
ThreadState selectedThread = (ThreadState)list.getSelectedValue();
if (threadState.isDeadlocked()) {
setBackground(LightColors.RED);
}
else if (selectedThread != null && threadState.isAwaitedBy(selectedThread)) {
setBackground(Color.YELLOW);
}
else {
setBackground(UIUtil.getListBackground());
}
}
SimpleTextAttributes attrs = getAttributes(threadState);
append(threadState.getName() + " (", attrs);
String detail = threadState.getThreadStateDetail();
if (detail == null) {
detail = threadState.getState();
}
if (detail.length() > 30) {
detail = detail.substring(0, 30) + "...";
}
append(detail, attrs);
append(")", attrs);
if (threadState.getExtraState() != null) {
append(" [" + threadState.getExtraState() + "]", attrs);
}
}
}
public void selectStackFrame(int index) {
myThreadList.setSelectedIndex(index);
}
private static class CopyToClipboardAction extends AnAction {
private final List<ThreadState> myThreadDump;
private final Project myProject;
public CopyToClipboardAction(List<ThreadState> threadDump, Project project) {
super("Copy to Clipboard", "Copy whole thread dump to clipboard", IconLoader.getIcon("/general/copy.png"));
myThreadDump = threadDump;
myProject = project;
}
public void actionPerformed(AnActionEvent e) {
final StringBuilder buf = new StringBuilder();
for (ThreadState state : myThreadDump) {
buf.append(state.getStackTrace()).append("\n\n");
}
CopyPasteManager.getInstance().setContents(new StringSelection(buf.toString()));
SwingUtilities.invokeLater(new Runnable() {
public void run() {
if (!myProject.isDisposed()) {
ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.RUN, MessageType.INFO, "Full thread dump was successfully copied to clipboard");
}
}
});
}
}
}
@@ -6,9 +6,6 @@ package com.intellij.unscramble;
import com.intellij.execution.ui.ConsoleView;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileChooser.FileChooser;
@@ -17,21 +14,17 @@ import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.help.HelpManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.*;
import com.intellij.ui.GuiUtils;
import com.intellij.ui.TextFieldWithHistory;
import com.intellij.util.ArrayUtil;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -44,14 +37,6 @@ public class UnscrambleDialog extends DialogWrapper{
@NonNls private static final String PROPERTY_LOG_FILE_LAST_URL = "UNSCRAMBLE_LOG_FILE_LAST_URL";
@NonNls private static final String PROPERTY_UNSCRAMBLER_NAME_USED = "UNSCRAMBLER_NAME_USED";
private static final Icon PAUSE_ICON = IconLoader.getIcon("/debugger/threadStates/paused.png");
private static final Icon LOCKED_ICON = IconLoader.getIcon("/debugger/threadStates/locked.png");
private static final Icon RUNNING_ICON = IconLoader.getIcon("/debugger/threadStates/running.png");
private static final Icon SOCKET_ICON = IconLoader.getIcon("/debugger/threadStates/socket.png");
private static final Icon IDLE_ICON = IconLoader.getIcon("/debugger/threadStates/idle.png");
private static final Icon EDT_BUSY_ICON = IconLoader.getIcon("/debugger/threadStates/edtBusy.png");
private static final Icon IO_ICON = IconLoader.getIcon("/debugger/threadStates/io.png");
private final Project myProject;
private JPanel myEditorPanel;
private JPanel myLogFileChooserPanel;
@@ -335,111 +320,11 @@ public class UnscrambleDialog extends DialogWrapper{
public static ConsoleView addConsole(final Project project, final List<ThreadState> threadDump) {
return AnalyzeStacktraceUtil.addConsole(project, threadDump.size() > 1 ? new AnalyzeStacktraceUtil.ConsoleFactory() {
public JComponent createConsoleComponent(ConsoleView consoleView, DefaultActionGroup toolbarActions) {
return new ThreadDumpPanel(consoleView, toolbarActions, threadDump);
return new ThreadDumpPanel(project, consoleView, toolbarActions, threadDump);
}
} : null, IdeBundle.message("unscramble.unscrambled.stacktrace.tab"));
}
private static class ThreadDumpPanel extends JPanel {
public ThreadDumpPanel(final ConsoleView consoleView, final ActionGroup toolbarActions, final List<ThreadState> threadDump) {
super(new BorderLayout());
final JList threadList = new JList(threadDump.toArray(new ThreadState[threadDump.size()]));
threadList.setCellRenderer(new ThreadListCellRenderer());
threadList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
threadList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(final ListSelectionEvent e) {
int index = threadList.getSelectedIndex();
if (index >= 0) {
ThreadState selection = threadDump.get(index);
AnalyzeStacktraceUtil.printStacktrace(consoleView, selection.getStackTrace());
}
else {
AnalyzeStacktraceUtil.printStacktrace(consoleView, "");
}
threadList.repaint();
}
});
ListToolTipHandler.install(threadList);
add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, toolbarActions,false).getComponent(), BorderLayout.WEST);
final Splitter splitter = new Splitter(false, 0.3f);
splitter.setFirstComponent(new JScrollPane(threadList));
splitter.setSecondComponent(consoleView.getComponent());
add(splitter, BorderLayout.CENTER);
}
private static Icon getThreadStateIcon(final ThreadState threadState) {
if (threadState.isSleeping()) {
return PAUSE_ICON;
}
if (threadState.isWaiting()) {
return LOCKED_ICON;
}
if (threadState.getOperation() == ThreadOperation.Socket) {
return SOCKET_ICON;
}
if (threadState.getOperation() == ThreadOperation.IO) {
return IO_ICON;
}
if (threadState.isEDT()) {
if ("idle".equals(threadState.getThreadStateDetail())) {
return IDLE_ICON;
}
return EDT_BUSY_ICON;
}
return RUNNING_ICON;
}
private static SimpleTextAttributes getAttributes(final ThreadState threadState) {
if (threadState.isSleeping()) {
return SimpleTextAttributes.GRAY_ATTRIBUTES;
}
if (threadState.isEmptyStackTrace() || ThreadDumpParser.isKnownJdkThread(threadState)) {
return new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, Color.GRAY.brighter());
}
if (threadState.isEDT()) {
return SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES;
}
return SimpleTextAttributes.REGULAR_ATTRIBUTES;
}
private static class ThreadListCellRenderer extends ColoredListCellRenderer {
protected void customizeCellRenderer(final JList list, final Object value, final int index, final boolean selected, final boolean hasFocus) {
ThreadState threadState = (ThreadState) value;
setIcon(getThreadStateIcon(threadState));
if (!selected) {
ThreadState selectedThread = (ThreadState)list.getSelectedValue();
if (threadState.isDeadlocked()) {
setBackground(LightColors.RED);
}
else if (selectedThread != null && threadState.isAwaitedBy(selectedThread)) {
setBackground(Color.YELLOW);
}
else {
setBackground(UIUtil.getListBackground());
}
}
SimpleTextAttributes attrs = getAttributes(threadState);
append(threadState.getName() + " (", attrs);
String detail = threadState.getThreadStateDetail();
if (detail == null) {
detail = threadState.getState();
}
if (detail.length() > 30) {
detail = detail.substring(0, 30) + "...";
}
append(detail, attrs);
append(")", attrs);
if (threadState.getExtraState() != null) {
append(" [" + threadState.getExtraState() + "]", attrs);
}
}
}
}
protected String getDimensionServiceKey(){
return "#com.intellij.unscramble.UnscrambleDialog";
}