IDEA-121775 XDebugger: merge Watches and Variables in one view - allow to switch...
[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.ViewImpl;
28 import com.intellij.icons.AllIcons;
29 import com.intellij.ide.DataManager;
30 import com.intellij.ide.actions.ContextHelpAction;
31 import com.intellij.idea.ActionsBundle;
32 import com.intellij.openapi.actionSystem.*;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.util.Disposer;
35 import com.intellij.openapi.util.registry.Registry;
36 import com.intellij.psi.search.GlobalSearchScope;
37 import com.intellij.ui.AppUIUtil;
38 import com.intellij.ui.content.Content;
39 import com.intellij.ui.content.ContentManagerAdapter;
40 import com.intellij.ui.content.ContentManagerEvent;
41 import com.intellij.ui.content.tabs.PinToolwindowTabAction;
42 import com.intellij.util.SystemProperties;
43 import com.intellij.util.containers.ContainerUtil;
44 import com.intellij.util.containers.hash.LinkedHashMap;
45 import com.intellij.xdebugger.XDebugSession;
46 import com.intellij.xdebugger.XDebuggerBundle;
47 import com.intellij.xdebugger.impl.XDebugSessionImpl;
48 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
49 import com.intellij.xdebugger.impl.frame.*;
50 import com.intellij.xdebugger.impl.ui.tree.actions.SortValuesToggleAction;
51 import com.intellij.xdebugger.ui.XDebugTabLayouter;
52 import org.jetbrains.annotations.NonNls;
53 import org.jetbrains.annotations.NotNull;
54 import org.jetbrains.annotations.Nullable;
55
56 import javax.swing.*;
57 import java.util.List;
58
59 public class XDebugSessionTab extends DebuggerSessionTabBase {
60   public static final DataKey<XDebugSessionTab> TAB_KEY = DataKey.create("XDebugSessionTab");
61
62   private XWatchesViewImpl myWatchesView;
63   private boolean myWatchesInVariables = Registry.is("debugger.watches.in.variables");
64   private final LinkedHashMap<String, XDebugView> myViews = new LinkedHashMap<>();
65
66   @Nullable
67   private XDebugSessionImpl mySession;
68   private XDebugSessionData mySessionData;
69
70   private final Runnable myRebuildWatchesRunnable = new Runnable() {
71     @Override
72     public void run() {
73       if (myWatchesView != null && myWatchesView.rebuildNeeded()) {
74         myWatchesView.processSessionEvent(XDebugView.SessionEvent.SETTINGS_CHANGED);
75       }
76     }
77   };
78
79   @NotNull
80   public static XDebugSessionTab create(@NotNull XDebugSessionImpl session,
81                                         @Nullable Icon icon,
82                                         @Nullable ExecutionEnvironment environment,
83                                         @Nullable RunContentDescriptor contentToReuse) {
84     if (contentToReuse != null && SystemProperties.getBooleanProperty("xdebugger.reuse.session.tab", false)) {
85       JComponent component = contentToReuse.getComponent();
86       if (component != null) {
87         XDebugSessionTab oldTab = TAB_KEY.getData(DataManager.getInstance().getDataContext(component));
88         if (oldTab != null) {
89           oldTab.setSession(session, environment, icon);
90           oldTab.attachToSession(session);
91           return oldTab;
92         }
93       }
94     }
95     return new XDebugSessionTab(session, icon, environment);
96   }
97
98   @NotNull
99   public RunnerLayoutUi getUi() {
100     return myUi;
101   }
102
103   private XDebugSessionTab(@NotNull XDebugSessionImpl session,
104                            @Nullable Icon icon,
105                            @Nullable ExecutionEnvironment environment) {
106     super(session.getProject(), "Debug", session.getSessionName(), GlobalSearchScope.allScope(session.getProject()));
107
108     setSession(session, environment, icon);
109
110     myUi.addContent(createFramesContent(), 0, PlaceInGrid.left, false);
111     addVariablesAndWatches(session);
112
113     attachToSession(session);
114
115     DefaultActionGroup focus = new DefaultActionGroup();
116     focus.add(ActionManager.getInstance().getAction(XDebuggerActions.FOCUS_ON_BREAKPOINT));
117     myUi.getOptions().setAdditionalFocusActions(focus);
118
119     myUi.addListener(new ContentManagerAdapter() {
120       @Override
121       public void selectionChanged(ContentManagerEvent event) {
122         Content content = event.getContent();
123         XDebugSessionImpl session = mySession;
124         if (session != null && content.isSelected() && DebuggerContentInfo.WATCHES_CONTENT.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         XDebugView variablesView = myViews.get(DebuggerContentInfo.VARIABLES_CONTENT);
327         if (variablesView != null) {
328           Disposer.register(myRunContentDescriptor, variablesView);
329         }
330         attachViewToSession(mySession, variablesView);
331         if (!myWatchesInVariables) {
332           XDebugView watchesView = myViews.get(DebuggerContentInfo.WATCHES_CONTENT);
333           if (watchesView != null) {
334             Disposer.register(myRunContentDescriptor, watchesView);
335           }
336           attachViewToSession(mySession, watchesView);
337         }
338         rebuildViews();
339       }
340     }
341   }
342
343   private void registerView(String contentId, @NotNull XDebugView view) {
344     myViews.put(contentId, view);
345     Disposer.register(myRunContentDescriptor, view);
346   }
347
348   private void removeContent(String contentId) {
349     myUi.removeContent(myUi.findContent(contentId), true);
350     XDebugView view = myViews.remove(contentId);
351     if (view != null) {
352       Disposer.dispose(view);
353     }
354   }
355
356   private static class ToggleSortValuesAction extends SortValuesToggleAction {
357     private final boolean myShowIcon;
358
359     private ToggleSortValuesAction(boolean showIcon) {
360       copyFrom(ActionManager.getInstance().getAction(XDebuggerActions.TOGGLE_SORT_VALUES));
361       myShowIcon = showIcon;
362     }
363
364     @Override
365     public void update(@NotNull AnActionEvent e) {
366       super.update(e);
367       if (!myShowIcon) {
368         e.getPresentation().setIcon(null);
369       }
370     }
371   }
372 }