IDEA-121775 XDebugger: merge Watches and Variables in one view - do not evaluate...
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / ui / XDebugSessionTab.java
1 /*
2  * Copyright 2000-2016 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 package com.intellij.xdebugger.impl.ui;
17
18 import com.intellij.debugger.ui.DebuggerContentInfo;
19 import com.intellij.execution.Executor;
20 import com.intellij.execution.executors.DefaultDebugExecutor;
21 import com.intellij.execution.runners.ExecutionEnvironment;
22 import com.intellij.execution.runners.RunContentBuilder;
23 import com.intellij.execution.ui.RunContentDescriptor;
24 import com.intellij.execution.ui.RunnerLayoutUi;
25 import com.intellij.execution.ui.actions.CloseAction;
26 import com.intellij.execution.ui.layout.PlaceInGrid;
27 import com.intellij.execution.ui.layout.impl.RunnerContentUi;
28 import com.intellij.execution.ui.layout.impl.ViewImpl;
29 import com.intellij.icons.AllIcons;
30 import com.intellij.ide.DataManager;
31 import com.intellij.ide.actions.ContextHelpAction;
32 import com.intellij.idea.ActionsBundle;
33 import com.intellij.openapi.actionSystem.*;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.util.Disposer;
36 import com.intellij.openapi.util.registry.Registry;
37 import com.intellij.psi.search.GlobalSearchScope;
38 import com.intellij.ui.AppUIUtil;
39 import com.intellij.ui.content.Content;
40 import com.intellij.ui.content.ContentManagerAdapter;
41 import com.intellij.ui.content.ContentManagerEvent;
42 import com.intellij.ui.content.tabs.PinToolwindowTabAction;
43 import com.intellij.util.SystemProperties;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.containers.hash.LinkedHashMap;
46 import com.intellij.xdebugger.XDebugSession;
47 import com.intellij.xdebugger.XDebuggerBundle;
48 import com.intellij.xdebugger.impl.XDebugSessionImpl;
49 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
50 import com.intellij.xdebugger.impl.frame.*;
51 import com.intellij.xdebugger.impl.ui.tree.actions.SortValuesToggleAction;
52 import com.intellij.xdebugger.ui.XDebugTabLayouter;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
56
57 import javax.swing.*;
58 import java.util.List;
59
60 public class XDebugSessionTab extends DebuggerSessionTabBase {
61   public static final DataKey<XDebugSessionTab> TAB_KEY = DataKey.create("XDebugSessionTab");
62
63   private XWatchesViewImpl myWatchesView;
64   private boolean myWatchesInVariables = Registry.is("debugger.watches.in.variables");
65   private final LinkedHashMap<String, XDebugView> myViews = new LinkedHashMap<>();
66
67   @Nullable
68   private XDebugSessionImpl mySession;
69   private XDebugSessionData mySessionData;
70
71   private final Runnable myRebuildWatchesRunnable = new Runnable() {
72     @Override
73     public void run() {
74       if (myWatchesView != null && myWatchesView.rebuildNeeded()) {
75         myWatchesView.processSessionEvent(XDebugView.SessionEvent.SETTINGS_CHANGED);
76       }
77     }
78   };
79
80   @NotNull
81   public static XDebugSessionTab create(@NotNull XDebugSessionImpl session,
82                                         @Nullable Icon icon,
83                                         @Nullable ExecutionEnvironment environment,
84                                         @Nullable RunContentDescriptor contentToReuse) {
85     if (contentToReuse != null && SystemProperties.getBooleanProperty("xdebugger.reuse.session.tab", false)) {
86       JComponent component = contentToReuse.getComponent();
87       if (component != null) {
88         XDebugSessionTab oldTab = TAB_KEY.getData(DataManager.getInstance().getDataContext(component));
89         if (oldTab != null) {
90           oldTab.setSession(session, environment, icon);
91           oldTab.attachToSession(session);
92           return oldTab;
93         }
94       }
95     }
96     return new XDebugSessionTab(session, icon, environment);
97   }
98
99   @NotNull
100   public RunnerLayoutUi getUi() {
101     return myUi;
102   }
103
104   private XDebugSessionTab(@NotNull XDebugSessionImpl session,
105                            @Nullable Icon icon,
106                            @Nullable ExecutionEnvironment environment) {
107     super(session.getProject(), "Debug", session.getSessionName(), GlobalSearchScope.allScope(session.getProject()));
108
109     setSession(session, environment, icon);
110
111     myUi.addContent(createFramesContent(), 0, PlaceInGrid.left, false);
112     addVariablesAndWatches(session);
113
114     attachToSession(session);
115
116     DefaultActionGroup focus = new DefaultActionGroup();
117     focus.add(ActionManager.getInstance().getAction(XDebuggerActions.FOCUS_ON_BREAKPOINT));
118     myUi.getOptions().setAdditionalFocusActions(focus);
119
120     myUi.addListener(new ContentManagerAdapter() {
121       @Override
122       public void selectionChanged(ContentManagerEvent event) {
123         Content content = event.getContent();
124         if (mySession != null && content.isSelected() && getWatchesContentId().equals(ViewImpl.ID.get(content))) {
125           myRebuildWatchesRunnable.run();
126         }
127       }
128     }, myRunContentDescriptor);
129
130     rebuildViews();
131   }
132
133   private void addVariablesAndWatches(@NotNull XDebugSessionImpl session) {
134     myUi.addContent(createVariablesContent(session), 0, PlaceInGrid.center, false);
135     if (!myWatchesInVariables) {
136       myUi.addContent(createWatchesContent(session), 0, PlaceInGrid.right, false);
137     }
138   }
139
140   private void setSession(@NotNull XDebugSessionImpl session, @Nullable ExecutionEnvironment environment, @Nullable Icon icon) {
141     myEnvironment = environment;
142     mySession = session;
143     mySessionData = session.getSessionData();
144     myConsole = session.getConsoleView();
145
146     AnAction[] restartActions;
147     List<AnAction> restartActionsList = session.getRestartActions();
148     if (ContainerUtil.isEmpty(restartActionsList)) {
149       restartActions = AnAction.EMPTY_ARRAY;
150     }
151     else {
152       restartActions = restartActionsList.toArray(new AnAction[restartActionsList.size()]);
153     }
154
155     myRunContentDescriptor = new RunContentDescriptor(myConsole, session.getDebugProcess().getProcessHandler(),
156                                                       myUi.getComponent(), session.getSessionName(), icon, myRebuildWatchesRunnable, restartActions);
157     Disposer.register(myRunContentDescriptor, this);
158     Disposer.register(myProject, myRunContentDescriptor);
159   }
160
161   @Nullable
162   @Override
163   public Object getData(@NonNls String dataId) {
164     if (XWatchesView.DATA_KEY.is(dataId)) {
165       return myWatchesView;
166     }
167     else if (TAB_KEY.is(dataId)) {
168       return this;
169     }
170     else if (XDebugSessionData.DATA_KEY.is(dataId)) {
171       return mySessionData;
172     }
173
174     if (mySession != null) {
175       if (XDebugSession.DATA_KEY.is(dataId)) {
176         return mySession;
177       }
178       else if (LangDataKeys.CONSOLE_VIEW.is(dataId)) {
179         return mySession.getConsoleView();
180       }
181     }
182
183     return super.getData(dataId);
184   }
185
186   private Content createVariablesContent(@NotNull XDebugSessionImpl session) {
187     XVariablesView variablesView;
188     if (myWatchesInVariables) {
189       variablesView = myWatchesView = new XWatchesViewImpl(session, myWatchesInVariables);
190     }
191     else {
192       variablesView = new XVariablesView(session);
193     }
194     registerView(DebuggerContentInfo.VARIABLES_CONTENT, variablesView);
195     Content result = myUi.createContent(DebuggerContentInfo.VARIABLES_CONTENT, variablesView.getPanel(),
196                                         XDebuggerBundle.message("debugger.session.tab.variables.title"),
197                                         AllIcons.Debugger.Value, null);
198     result.setCloseable(false);
199
200     ActionGroup group = getCustomizedActionGroup(XDebuggerActions.VARIABLES_TREE_TOOLBAR_GROUP);
201     result.setActions(group, ActionPlaces.DEBUGGER_TOOLBAR, variablesView.getTree());
202     return result;
203   }
204
205   private Content createWatchesContent(@NotNull XDebugSessionImpl session) {
206     myWatchesView = new XWatchesViewImpl(session, myWatchesInVariables);
207     registerView(DebuggerContentInfo.WATCHES_CONTENT, myWatchesView);
208     Content watchesContent = myUi.createContent(DebuggerContentInfo.WATCHES_CONTENT, myWatchesView.getPanel(),
209                                                 XDebuggerBundle.message("debugger.session.tab.watches.title"), AllIcons.Debugger.Watches, null);
210     watchesContent.setCloseable(false);
211     return watchesContent;
212   }
213
214   @NotNull
215   private Content createFramesContent() {
216     XFramesView framesView = new XFramesView(myProject);
217     registerView(DebuggerContentInfo.FRAME_CONTENT, framesView);
218     Content framesContent = myUi.createContent(DebuggerContentInfo.FRAME_CONTENT, framesView.getMainPanel(),
219                                                XDebuggerBundle.message("debugger.session.tab.frames.title"), AllIcons.Debugger.Frame, null);
220     framesContent.setCloseable(false);
221     return framesContent;
222   }
223
224   public void rebuildViews() {
225     AppUIUtil.invokeLaterIfProjectAlive(myProject, () -> {
226       for (XDebugView view : myViews.values()) {
227         view.processSessionEvent(XDebugView.SessionEvent.SETTINGS_CHANGED);
228       }
229     });
230   }
231
232   public XWatchesView getWatchesView() {
233     return myWatchesView;
234   }
235
236   private void attachToSession(@NotNull XDebugSessionImpl session) {
237     for (XDebugView view : myViews.values()) {
238       attachViewToSession(session, view);
239     }
240
241     XDebugTabLayouter layouter = session.getDebugProcess().createTabLayouter();
242     Content consoleContent = layouter.registerConsoleContent(myUi, myConsole);
243     attachNotificationTo(consoleContent);
244
245     layouter.registerAdditionalContent(myUi);
246     RunContentBuilder.addAdditionalConsoleEditorActions(myConsole, consoleContent);
247
248     if (ApplicationManager.getApplication().isUnitTestMode()) {
249       return;
250     }
251
252     DefaultActionGroup leftToolbar = new DefaultActionGroup();
253     final Executor debugExecutor = DefaultDebugExecutor.getDebugExecutorInstance();
254     if (myEnvironment != null) {
255       leftToolbar.add(ActionManager.getInstance().getAction(IdeActions.ACTION_RERUN));
256       List<AnAction> additionalRestartActions = session.getRestartActions();
257       if (!additionalRestartActions.isEmpty()) {
258         leftToolbar.addAll(additionalRestartActions);
259         leftToolbar.addSeparator();
260       }
261       leftToolbar.addAll(session.getExtraActions());
262     }
263     leftToolbar.addAll(getCustomizedActionGroup(XDebuggerActions.TOOL_WINDOW_LEFT_TOOLBAR_GROUP));
264
265     for (AnAction action : session.getExtraStopActions()) {
266       leftToolbar.add(action, new Constraints(Anchor.AFTER, IdeActions.ACTION_STOP_PROGRAM));
267     }
268
269     //group.addSeparator();
270     //addAction(group, DebuggerActions.EXPORT_THREADS);
271     leftToolbar.addSeparator();
272
273     leftToolbar.add(myUi.getOptions().getLayoutActions());
274     final AnAction[] commonSettings = myUi.getOptions().getSettingsActionsList();
275     DefaultActionGroup settings = new DefaultActionGroup(ActionsBundle.message("group.XDebugger.settings.text"), true);
276     settings.getTemplatePresentation().setIcon(myUi.getOptions().getSettingsActions().getTemplatePresentation().getIcon());
277     settings.addAll(commonSettings);
278     leftToolbar.add(settings);
279
280     leftToolbar.addSeparator();
281
282     leftToolbar.add(PinToolwindowTabAction.getPinAction());
283     leftToolbar.add(new CloseAction(myEnvironment != null ? myEnvironment.getExecutor() : debugExecutor, myRunContentDescriptor, myProject));
284     leftToolbar.add(new ContextHelpAction(debugExecutor.getHelpId()));
285
286     DefaultActionGroup topToolbar = new DefaultActionGroup();
287     topToolbar.addAll(getCustomizedActionGroup(XDebuggerActions.TOOL_WINDOW_TOP_TOOLBAR_GROUP));
288
289     session.getDebugProcess().registerAdditionalActions(leftToolbar, topToolbar, settings);
290     myUi.getOptions().setLeftToolbar(leftToolbar, ActionPlaces.DEBUGGER_TOOLBAR);
291     myUi.getOptions().setTopToolbar(topToolbar, ActionPlaces.DEBUGGER_TOOLBAR);
292
293     if (myEnvironment != null) {
294       initLogConsoles(myEnvironment.getRunProfile(), myRunContentDescriptor, myConsole);
295     }
296   }
297
298   private static void attachViewToSession(@NotNull XDebugSessionImpl session, @Nullable XDebugView view) {
299     if (view != null) {
300       session.addSessionListener(new XDebugViewSessionListener(view), view);
301     }
302   }
303
304   public void detachFromSession() {
305     assert mySession != null;
306     mySession = null;
307   }
308
309   @Nullable
310   public RunContentDescriptor getRunContentDescriptor() {
311     return myRunContentDescriptor;
312   }
313
314   public boolean isWatchesInVariables() {
315     return myWatchesInVariables;
316   }
317
318   public void setWatchesInVariables(boolean watchesInVariables) {
319     if (myWatchesInVariables != watchesInVariables) {
320       myWatchesInVariables = watchesInVariables;
321       Registry.get("debugger.watches.in.variables").setValue(watchesInVariables);
322       if (mySession != null) {
323         removeContent(DebuggerContentInfo.VARIABLES_CONTENT);
324         removeContent(DebuggerContentInfo.WATCHES_CONTENT);
325         addVariablesAndWatches(mySession);
326         attachViewToSession(mySession, myViews.get(DebuggerContentInfo.VARIABLES_CONTENT));
327         attachViewToSession(mySession, myViews.get(DebuggerContentInfo.WATCHES_CONTENT));
328         rebuildViews();
329       }
330     }
331   }
332
333   public static void showWatchesView(@NotNull XDebugSessionImpl session) {
334     XDebugSessionTab tab = session.getSessionTab();
335     if (tab != null) {
336       tab.toFront(false, null);
337       // restore watches tab if minimized
338       JComponent component = tab.getUi().getComponent();
339       if (component instanceof DataProvider) {
340         RunnerContentUi ui = RunnerContentUi.KEY.getData(((DataProvider)component));
341         if (ui != null) {
342           ui.restoreContent(tab.getWatchesContentId());
343         }
344       }
345     }
346   }
347
348   @NotNull
349   private String getWatchesContentId() {
350     return myWatchesInVariables ? DebuggerContentInfo.VARIABLES_CONTENT : DebuggerContentInfo.WATCHES_CONTENT;
351   }
352
353   private void registerView(String contentId, @NotNull XDebugView view) {
354     myViews.put(contentId, view);
355     Disposer.register(myRunContentDescriptor, view);
356   }
357
358   private void removeContent(String contentId) {
359     myUi.removeContent(myUi.findContent(contentId), true);
360     XDebugView view = myViews.remove(contentId);
361     if (view != null) {
362       Disposer.dispose(view);
363     }
364   }
365
366   private static class ToggleSortValuesAction extends SortValuesToggleAction {
367     private final boolean myShowIcon;
368
369     private ToggleSortValuesAction(boolean showIcon) {
370       copyFrom(ActionManager.getInstance().getAction(XDebuggerActions.TOGGLE_SORT_VALUES));
371       myShowIcon = showIcon;
372     }
373
374     @Override
375     public void update(@NotNull AnActionEvent e) {
376       super.update(e);
377       if (!myShowIcon) {
378         e.getPresentation().setIcon(null);
379       }
380     }
381   }
382 }