8eb082c7f6d8e6ee131b85d862a0d90515fe59cc
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / evaluate / XDebuggerEvaluationDialog.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.evaluate;
17
18 import com.intellij.openapi.actionSystem.*;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.editor.Editor;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.DialogWrapper;
23 import com.intellij.openapi.util.Condition;
24 import com.intellij.openapi.util.text.StringUtil;
25 import com.intellij.openapi.wm.IdeFocusManager;
26 import com.intellij.openapi.wm.WindowManager;
27 import com.intellij.util.ui.JBUI;
28 import com.intellij.util.ui.components.BorderLayoutPanel;
29 import com.intellij.xdebugger.*;
30 import com.intellij.xdebugger.evaluation.EvaluationMode;
31 import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider;
32 import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
33 import com.intellij.xdebugger.impl.XDebugSessionImpl;
34 import com.intellij.xdebugger.impl.XDebuggerUtilImpl;
35 import com.intellij.xdebugger.impl.actions.XDebuggerActions;
36 import com.intellij.xdebugger.impl.breakpoints.XExpressionImpl;
37 import com.intellij.xdebugger.impl.settings.XDebuggerSettingManagerImpl;
38 import com.intellij.xdebugger.impl.ui.XDebugSessionTab;
39 import com.intellij.xdebugger.impl.ui.XDebuggerEditorBase;
40 import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
41 import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel;
42 import com.intellij.xdebugger.impl.ui.tree.nodes.EvaluatingExpressionRootNode;
43 import org.jetbrains.annotations.NonNls;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
46
47 import javax.swing.*;
48 import javax.swing.tree.TreeNode;
49 import java.awt.*;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.InputEvent;
52 import java.awt.event.KeyEvent;
53
54 /**
55  * @author nik
56  */
57 public class XDebuggerEvaluationDialog extends DialogWrapper {
58   public static final DataKey<XDebuggerEvaluationDialog> KEY = DataKey.create("DEBUGGER_EVALUATION_DIALOG");
59
60   private final JPanel myMainPanel;
61   private final JPanel myResultPanel;
62   private final XDebuggerTreePanel myTreePanel;
63   private EvaluationInputComponent myInputComponent;
64   private final XDebugSession mySession;
65   private final XDebuggerEditorsProvider myEditorsProvider;
66   private EvaluationMode myMode;
67   private XSourcePosition mySourcePosition;
68   private final SwitchModeAction mySwitchModeAction;
69   private final boolean myIsCodeFragmentEvaluationSupported;
70
71   public XDebuggerEvaluationDialog(@NotNull XDebugSession session,
72                                    @NotNull XDebuggerEditorsProvider editorsProvider,
73                                    @NotNull XDebuggerEvaluator evaluator,
74                                    @NotNull XExpression text,
75                                    @Nullable XSourcePosition sourcePosition) {
76     super(WindowManager.getInstance().getFrame(session.getProject()), true);
77     mySession = session;
78     myEditorsProvider = editorsProvider;
79     mySourcePosition = sourcePosition;
80     setModal(false);
81     setOKButtonText(XDebuggerBundle.message("xdebugger.button.evaluate"));
82     setCancelButtonText(XDebuggerBundle.message("xdebugger.evaluate.dialog.close"));
83
84     mySession.addSessionListener(new XDebugSessionListener() {
85       @Override
86       public void sessionStopped() {
87         ApplicationManager.getApplication().invokeLater(() -> close(CANCEL_EXIT_CODE));
88       }
89
90       @Override
91       public void stackFrameChanged() {
92         updateSourcePosition();
93       }
94
95       @Override
96       public void sessionPaused() {
97         updateSourcePosition();
98       }
99     }, myDisposable);
100
101     myTreePanel = new XDebuggerTreePanel(session.getProject(), editorsProvider, myDisposable, sourcePosition, XDebuggerActions.EVALUATE_DIALOG_TREE_POPUP_GROUP,
102                                          ((XDebugSessionImpl)session).getValueMarkers());
103     myResultPanel = JBUI.Panels.simplePanel()
104       .addToTop(new JLabel(XDebuggerBundle.message("xdebugger.evaluate.label.result")))
105       .addToCenter(myTreePanel.getMainPanel());
106     myMainPanel = new EvaluationMainPanel();
107
108     mySwitchModeAction = new SwitchModeAction();
109
110     new AnAction(){
111       @Override
112       public void actionPerformed(AnActionEvent e) {
113         doOKAction();
114         addToWatches();
115       }
116     }.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK)), getRootPane(), myDisposable);
117
118     new AnAction() {
119       @Override
120       public void actionPerformed(AnActionEvent e) {
121         IdeFocusManager.getInstance(mySession.getProject()).requestFocus(myTreePanel.getTree(), true);
122       }
123     }.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_DOWN_MASK)), getRootPane(),
124                                 myDisposable);
125
126     Condition<TreeNode> rootFilter = node -> node.getParent() instanceof EvaluatingExpressionRootNode;
127     myTreePanel.getTree().expandNodesOnLoad(rootFilter);
128     myTreePanel.getTree().selectNodeOnLoad(rootFilter);
129
130     EvaluationMode mode = XDebuggerSettingManagerImpl.getInstanceImpl().getGeneralSettings().getEvaluationDialogMode();
131     myIsCodeFragmentEvaluationSupported = evaluator.isCodeFragmentEvaluationSupported();
132     if (mode == EvaluationMode.CODE_FRAGMENT && !myIsCodeFragmentEvaluationSupported) {
133       mode = EvaluationMode.EXPRESSION;
134     }
135     if (mode == EvaluationMode.EXPRESSION && text.getMode() == EvaluationMode.CODE_FRAGMENT && myIsCodeFragmentEvaluationSupported) {
136       mode = EvaluationMode.CODE_FRAGMENT;
137     }
138     switchToMode(mode, text);
139     init();
140   }
141
142   @Override
143   protected void dispose() {
144     super.dispose();
145     myMainPanel.removeAll();
146   }
147
148   private void updateSourcePosition() {
149     ApplicationManager.getApplication().invokeLater(() -> {
150       mySourcePosition = mySession.getCurrentPosition();
151       getInputEditor().setSourcePosition(mySourcePosition);
152     });
153   }
154
155   @Override
156   protected void doOKAction() {
157     evaluate();
158   }
159
160   @Override
161   protected void createDefaultActions() {
162     super.createDefaultActions();
163     myOKAction = new OkAction(){
164       @Override
165       public void actionPerformed(ActionEvent e) {
166         super.actionPerformed(e);
167         if ((e.getModifiers() & (InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK)) == (InputEvent.SHIFT_MASK | InputEvent.CTRL_MASK)) {
168           addToWatches();
169         }
170       }
171     };
172   }
173
174   private void addToWatches() {
175     if (myMode == EvaluationMode.EXPRESSION) {
176       XExpression expression = getInputEditor().getExpression();
177       if (!XDebuggerUtilImpl.isEmptyExpression(expression)) {
178         XDebugSessionTab tab = ((XDebugSessionImpl)mySession).getSessionTab();
179         if (tab != null) {
180           tab.getWatchesView().addWatchExpression(expression, -1, true);
181           getInputEditor().requestFocusInEditor();
182         }
183       }
184     }
185   }
186
187   @NotNull
188   @Override
189   protected Action[] createActions() {
190     if (myIsCodeFragmentEvaluationSupported) {
191       return new Action[]{getOKAction(), mySwitchModeAction, getCancelAction()};
192     }
193     return super.createActions();
194   }
195
196   @Override
197   protected String getHelpId() {
198     return "debugging.debugMenu.evaluate";
199   }
200
201   @Override
202   protected JButton createJButtonForAction(Action action) {
203     final JButton button = super.createJButtonForAction(action);
204     if (action == mySwitchModeAction) {
205       int width1 = new JButton(getSwitchButtonText(EvaluationMode.EXPRESSION)).getPreferredSize().width;
206       int width2 = new JButton(getSwitchButtonText(EvaluationMode.CODE_FRAGMENT)).getPreferredSize().width;
207       final Dimension size = new Dimension(Math.max(width1, width2), button.getPreferredSize().height);
208       button.setMinimumSize(size);
209       button.setPreferredSize(size);
210     }
211     return button;
212   }
213
214   public XExpression getExpression() {
215     return getInputEditor().getExpression();
216   }
217
218   private static String getSwitchButtonText(EvaluationMode mode) {
219     return mode != EvaluationMode.EXPRESSION
220            ? XDebuggerBundle.message("button.text.expression.mode")
221            : XDebuggerBundle.message("button.text.code.fragment.mode");
222   }
223
224   private void switchToMode(EvaluationMode mode, XExpression text) {
225     if (myMode == mode) return;
226
227     myMode = mode;
228
229     if (mode == EvaluationMode.EXPRESSION) {
230       text = new XExpressionImpl(StringUtil.convertLineSeparators(text.getExpression(), " "), text.getLanguage(), text.getCustomInfo());
231     }
232
233     myInputComponent = createInputComponent(mode, text);
234     myMainPanel.removeAll();
235     myInputComponent.addComponent(myMainPanel, myResultPanel);
236
237     setTitle(myInputComponent.getTitle());
238     mySwitchModeAction.putValue(Action.NAME, getSwitchButtonText(mode));
239     getInputEditor().requestFocusInEditor();
240   }
241
242   private XDebuggerEditorBase getInputEditor() {
243     return myInputComponent.getInputEditor();
244   }
245
246   private EvaluationInputComponent createInputComponent(EvaluationMode mode, XExpression text) {
247     final Project project = mySession.getProject();
248     text = XExpressionImpl.changeMode(text, mode);
249     if (mode == EvaluationMode.EXPRESSION) {
250       return new ExpressionInputComponent(project, myEditorsProvider, mySourcePosition, text, myDisposable);
251     }
252     else {
253       return new CodeFragmentInputComponent(project, myEditorsProvider, mySourcePosition, text,
254                                             getDimensionServiceKey() + ".splitter", myDisposable);
255     }
256   }
257
258   private void evaluate() {
259     final XDebuggerEditorBase inputEditor = getInputEditor();
260     int offset = -1;
261
262     //try to save caret position
263     Editor editor = inputEditor.getEditor();
264     if (editor != null) {
265       offset = editor.getCaretModel().getOffset();
266     }
267
268     final XDebuggerTree tree = myTreePanel.getTree();
269     tree.markNodesObsolete();
270     tree.setRoot(new EvaluatingExpressionRootNode(this, tree), false);
271
272     myResultPanel.invalidate();
273
274     //editor is already changed
275     editor = inputEditor.getEditor();
276     //selectAll puts focus back
277     inputEditor.selectAll();
278
279     //try to restore caret position and clear selection
280     if (offset >= 0 && editor != null) {
281       offset = Math.min(editor.getDocument().getTextLength(), offset);
282       editor.getCaretModel().moveToOffset(offset);
283       editor.getSelectionModel().setSelection(offset, offset);
284     }
285   }
286
287   @Override
288   public void doCancelAction() {
289     getInputEditor().saveTextInHistory();
290     super.doCancelAction();
291   }
292
293   @Override
294   protected String getDimensionServiceKey() {
295     return "#xdebugger.evaluate";
296   }
297
298   @Override
299   protected JComponent createCenterPanel() {
300     return myMainPanel;
301   }
302
303   public void startEvaluation(@NotNull XDebuggerEvaluator.XEvaluationCallback evaluationCallback) {
304     final XDebuggerEditorBase inputEditor = getInputEditor();
305     inputEditor.saveTextInHistory();
306     XExpression expression = inputEditor.getExpression();
307
308     XDebuggerEvaluator evaluator = mySession.getDebugProcess().getEvaluator();
309     if (evaluator == null) {
310       evaluationCallback.errorOccurred(XDebuggerBundle.message("xdebugger.evaluate.stack.frame.has.not.evaluator"));
311     }
312     else {
313       evaluator.evaluate(expression, evaluationCallback, null);
314     }
315   }
316
317   public void evaluationDone() {
318     mySession.rebuildViews();
319   }
320
321   @Override
322   public JComponent getPreferredFocusedComponent() {
323     return getInputEditor().getPreferredFocusedComponent();
324   }
325
326   private class SwitchModeAction extends AbstractAction {
327     @Override
328     public void actionPerformed(ActionEvent e) {
329       XExpression text = getInputEditor().getExpression();
330       EvaluationMode newMode = (myMode == EvaluationMode.EXPRESSION) ? EvaluationMode.CODE_FRAGMENT : EvaluationMode.EXPRESSION;
331       // remember only on user selection
332       XDebuggerSettingManagerImpl.getInstanceImpl().getGeneralSettings().setEvaluationDialogMode(newMode);
333       switchToMode(newMode, text);
334     }
335   }
336
337   private class EvaluationMainPanel extends BorderLayoutPanel implements DataProvider {
338     @Nullable
339     @Override
340     public Object getData(@NonNls String dataId) {
341       if (KEY.is(dataId)) {
342         return XDebuggerEvaluationDialog.this;
343       }
344       return null;
345     }
346   }
347 }