761e9e35a83e92641e99c0544ee2bc33c8ca1982
[idea/community.git] / platform / lang-impl / src / com / intellij / unscramble / AnalyzeStacktraceUtil.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.unscramble;
18
19 import com.intellij.execution.ExecutionManager;
20 import com.intellij.execution.Executor;
21 import com.intellij.execution.executors.DefaultRunExecutor;
22 import com.intellij.execution.filters.Filter;
23 import com.intellij.execution.filters.TextConsoleBuilder;
24 import com.intellij.execution.filters.TextConsoleBuilderFactory;
25 import com.intellij.execution.impl.ConsoleViewImpl;
26 import com.intellij.execution.ui.*;
27 import com.intellij.execution.ui.actions.CloseAction;
28 import com.intellij.ide.DataManager;
29 import com.intellij.openapi.Disposable;
30 import com.intellij.openapi.actionSystem.*;
31 import com.intellij.openapi.actionSystem.ex.ActionUtil;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.command.CommandProcessor;
34 import com.intellij.openapi.editor.Document;
35 import com.intellij.openapi.editor.Editor;
36 import com.intellij.openapi.editor.EditorFactory;
37 import com.intellij.openapi.editor.EditorSettings;
38 import com.intellij.openapi.extensions.ExtensionPointName;
39 import com.intellij.openapi.extensions.Extensions;
40 import com.intellij.openapi.ide.CopyPasteManager;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.util.Disposer;
43 import com.intellij.openapi.util.text.StringUtil;
44 import com.intellij.openapi.wm.IdeFocusManager;
45 import com.intellij.util.ui.JBUI;
46 import com.intellij.util.ui.update.UiNotifyConnector;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
49
50 import javax.swing.*;
51 import java.awt.*;
52 import java.awt.datatransfer.DataFlavor;
53 import java.awt.event.ActionEvent;
54 import java.awt.event.ActionListener;
55
56 /**
57  * @author yole
58  */
59 public class AnalyzeStacktraceUtil {
60   public static final ExtensionPointName<Filter> EP_NAME = ExtensionPointName.create("com.intellij.analyzeStacktraceFilter");
61
62   private AnalyzeStacktraceUtil() {
63   }
64
65   public static void printStacktrace(@NotNull ConsoleView consoleView, @NotNull String unscrambledTrace) {
66     ApplicationManager.getApplication().assertIsDispatchThread();
67     String text = unscrambledTrace + "\n";
68     String consoleText = ((ConsoleViewImpl)consoleView).getText();
69     if (!text.equals(consoleText)) {
70       consoleView.clear();
71       consoleView.print(text, ConsoleViewContentType.ERROR_OUTPUT);
72       consoleView.scrollTo(0);
73     }
74   }
75
76   @Nullable
77   public static String getTextInClipboard() {
78     return CopyPasteManager.getInstance().getContents(DataFlavor.stringFlavor);
79   }
80
81   public interface ConsoleFactory {
82     JComponent createConsoleComponent(ConsoleView consoleView, DefaultActionGroup toolbarActions);
83   }
84
85   public static void addConsole(Project project, @Nullable ConsoleFactory consoleFactory, final String tabTitle, String text) {
86     addConsole(project, consoleFactory, tabTitle, text, null);
87   }
88
89   public static RunContentDescriptor addConsole(Project project,
90                                                 @Nullable ConsoleFactory consoleFactory,
91                                                 final String tabTitle,
92                                                 String text,
93                                                 @Nullable Icon icon) {
94     final TextConsoleBuilder builder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
95     builder.filters(Extensions.getExtensions(EP_NAME, project));
96     final ConsoleView consoleView = builder.getConsole();
97
98     final DefaultActionGroup toolbarActions = new DefaultActionGroup();
99     JComponent consoleComponent = consoleFactory != null
100                                   ? consoleFactory.createConsoleComponent(consoleView, toolbarActions)
101                                   : new MyConsolePanel(consoleView, toolbarActions);
102     final RunContentDescriptor descriptor =
103       new RunContentDescriptor(consoleView, null, consoleComponent, tabTitle, icon) {
104       @Override
105       public boolean isContentReuseProhibited() {
106         return true;
107       }
108     };
109     registerReplaceAction(project, descriptor, consoleView, text);
110
111     final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
112     for (AnAction action: consoleView.createConsoleActions()) {
113       toolbarActions.add(action);
114     }
115     final ConsoleViewImpl console = (ConsoleViewImpl)consoleView;
116     console.getEditor().getSettings().setCaretRowShown(true);
117     toolbarActions.add(new AnnotateStackTraceAction(console.getEditor(), console.getHyperlinks()));
118     toolbarActions.add(new CloseAction(executor, descriptor, project));
119     ExecutionManager.getInstance(project).getContentManager().showRunContent(executor, descriptor);
120     consoleView.allowHeavyFilters();
121     if (consoleFactory == null) {
122       printStacktrace(consoleView, text);
123     }
124     return descriptor;
125   }
126
127   /**
128    * Regular "Replace" editor action is unavailable for console editors (see com.intellij.openapi.editor.actions.IncrementalFindAction.Handler#isEnabled).
129    * However, it can be convenient to be able to replace user-specific paths with local paths
130    * if stacktrace links involves absolute paths (e.g. Node.js stack-traces contain absolute paths).
131    *
132    * @param consoleView
133    */
134   private static void registerReplaceAction(@NotNull Project project,
135                                             @NotNull RunContentDescriptor descriptor,
136                                             @NotNull ConsoleView consoleView,
137                                             @NotNull String stacktrace) {
138     AnAction replaceAction = ActionManager.getInstance().getAction("Replace");
139     if (replaceAction == null) {
140       return;
141     }
142     ActionUtil.registerForEveryKeyboardShortcut(consoleView.getComponent(), new ActionListener() {
143       @Override
144       public void actionPerformed(ActionEvent e) {
145         AnalyzeStacktraceDialog dialog = new AnalyzeStacktraceDialog(project) {
146           @Override
147           protected JComponent createCenterPanel() {
148             JComponent result = super.createCenterPanel();
149             myEditorPanel.setText(stacktrace);
150             UiNotifyConnector.doWhenFirstShown(myEditorPanel, new Runnable() {
151               @Override
152               public void run() {
153                 performReplaceAction(project, replaceAction, myEditorPanel);
154               }
155             });
156             return result;
157           }
158
159           @Override
160           protected void doOKAction() {
161             super.doOKAction();
162             RunContentManager contentManager = ExecutionManager.getInstance(project).getContentManager();
163             contentManager.removeRunContent(DefaultRunExecutor.getRunExecutorInstance(), descriptor);
164           }
165         };
166         dialog.show();
167       }
168     }, replaceAction.getShortcutSet());
169   }
170
171   private static void performReplaceAction(@NotNull Project project, @NotNull AnAction replaceAction,
172                                            @NotNull JComponent contextComponent) {
173     IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() {
174       @Override
175       public void run() {
176         DataContext context = DataManager.getInstance().getDataContext(contextComponent);
177         AnActionEvent actionEvent = AnActionEvent.createFromAnAction(replaceAction, null, ActionPlaces.UNKNOWN, context);
178         replaceAction.update(actionEvent);
179         if (actionEvent.getPresentation().isEnabledAndVisible()) {
180           ActionUtil.performActionDumbAware(replaceAction, actionEvent);
181         }
182       }
183     });
184     //ApplicationManager.getApplication().invokeLater(new Runnable() {
185     //  @Override
186     //  public void run() {
187     //  }
188     //}, ModalityState.stateForComponent(contextComponent));
189   }
190
191   private static final class MyConsolePanel extends JPanel {
192     public MyConsolePanel(ExecutionConsole consoleView, ActionGroup toolbarActions) {
193       super(new BorderLayout());
194       JPanel toolbarPanel = new JPanel(new BorderLayout());
195       toolbarPanel.add(ActionManager.getInstance()
196                          .createActionToolbar(ActionPlaces.ANALYZE_STACKTRACE_PANEL_TOOLBAR, toolbarActions, false)
197                          .getComponent());
198       add(toolbarPanel, BorderLayout.WEST);
199       add(consoleView.getComponent(), BorderLayout.CENTER);
200     }
201   }
202
203   public static StacktraceEditorPanel createEditorPanel(Project project, @NotNull Disposable parentDisposable) {
204     EditorFactory editorFactory = EditorFactory.getInstance();
205     Document document = editorFactory.createDocument("");
206     Editor editor = editorFactory.createEditor(document, project);
207     EditorSettings settings = editor.getSettings();
208     settings.setFoldingOutlineShown(false);
209     settings.setLineMarkerAreaShown(false);
210     settings.setIndentGuidesShown(false);
211     settings.setLineNumbersShown(false);
212     settings.setRightMarginShown(false);
213
214     StacktraceEditorPanel editorPanel = new StacktraceEditorPanel(project, editor);
215     editorPanel.setPreferredSize(JBUI.size(600, 400));
216     Disposer.register(parentDisposable, editorPanel);
217     return editorPanel;
218   }
219
220   public static final class StacktraceEditorPanel extends JPanel implements DataProvider, Disposable {
221     private final Project myProject;
222     private final Editor myEditor;
223
224     public StacktraceEditorPanel(Project project, Editor editor) {
225       super(new BorderLayout());
226       myProject = project;
227       myEditor = editor;
228       add(myEditor.getComponent());
229     }
230
231     @Override
232     public Object getData(String dataId) {
233       if (CommonDataKeys.EDITOR.is(dataId)) {
234         return myEditor;
235       }
236       return null;
237     }
238
239     public Editor getEditor() {
240       return myEditor;
241     }
242
243     public final void setText(@NotNull final String text) {
244       Runnable runnable = new Runnable() {
245         @Override
246         public void run() {
247           ApplicationManager.getApplication().runWriteAction(new Runnable() {
248             @Override
249             public void run() {
250               final Document document = myEditor.getDocument();
251               document.replaceString(0, document.getTextLength(), StringUtil.convertLineSeparators(text));
252             }
253           });
254         }
255       };
256       CommandProcessor.getInstance().executeCommand(myProject, runnable, "", this);
257     }
258
259     public void pasteTextFromClipboard() {
260       String text = getTextInClipboard();
261       if (text != null) {
262         setText(text);
263       }
264
265     }
266
267     @Override
268     public void dispose() {
269       EditorFactory.getInstance().releaseEditor(myEditor);
270     }
271
272     public String getText() {
273       return myEditor.getDocument().getText();
274     }
275
276     public JComponent getEditorComponent() {
277       return myEditor.getContentComponent();
278     }
279   }
280 }