5217334c7346d608c589d9d968ebe411cce0890a
[idea/community.git] / platform / xdebugger-impl / src / com / intellij / xdebugger / impl / evaluate / quick / XValueHint.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 package com.intellij.xdebugger.impl.evaluate.quick;
17
18 import com.intellij.codeInsight.hint.HintManager;
19 import com.intellij.codeInsight.hint.HintUtil;
20 import com.intellij.execution.console.LanguageConsoleView;
21 import com.intellij.execution.impl.ConsoleViewImpl;
22 import com.intellij.execution.ui.ConsoleView;
23 import com.intellij.openapi.Disposable;
24 import com.intellij.openapi.actionSystem.ActionManager;
25 import com.intellij.openapi.actionSystem.AnActionEvent;
26 import com.intellij.openapi.actionSystem.ShortcutSet;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.editor.Editor;
30 import com.intellij.openapi.fileEditor.FileDocumentManager;
31 import com.intellij.openapi.keymap.KeymapUtil;
32 import com.intellij.openapi.project.DumbAwareAction;
33 import com.intellij.openapi.project.Project;
34 import com.intellij.openapi.util.Disposer;
35 import com.intellij.openapi.util.Key;
36 import com.intellij.openapi.util.Pair;
37 import com.intellij.openapi.util.text.StringUtil;
38 import com.intellij.openapi.vcs.changes.issueLinks.LinkMouseListenerBase;
39 import com.intellij.openapi.vfs.VirtualFile;
40 import com.intellij.ui.SimpleColoredComponent;
41 import com.intellij.ui.SimpleColoredText;
42 import com.intellij.ui.SimpleTextAttributes;
43 import com.intellij.util.Consumer;
44 import com.intellij.xdebugger.XDebugSession;
45 import com.intellij.xdebugger.XDebuggerUtil;
46 import com.intellij.xdebugger.XSourcePosition;
47 import com.intellij.xdebugger.evaluation.ExpressionInfo;
48 import com.intellij.xdebugger.evaluation.XDebuggerEvaluator;
49 import com.intellij.xdebugger.frame.XDebuggerTreeNodeHyperlink;
50 import com.intellij.xdebugger.frame.XFullValueEvaluator;
51 import com.intellij.xdebugger.frame.XValue;
52 import com.intellij.xdebugger.frame.XValuePlace;
53 import com.intellij.xdebugger.frame.presentation.XValuePresentation;
54 import com.intellij.xdebugger.impl.XDebugSessionImpl;
55 import com.intellij.xdebugger.impl.actions.handlers.XDebuggerEvaluateActionHandler;
56 import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint;
57 import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType;
58 import com.intellij.xdebugger.impl.frame.XValueMarkers;
59 import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
60 import com.intellij.xdebugger.impl.ui.XDebuggerUIConstants;
61 import com.intellij.xdebugger.impl.ui.tree.nodes.XEvaluationCallbackBase;
62 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
63 import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodePresentationConfigurator;
64 import org.jetbrains.annotations.NotNull;
65 import org.jetbrains.annotations.Nullable;
66
67 import javax.swing.*;
68 import java.awt.*;
69 import java.awt.event.MouseEvent;
70
71 /**
72  * @author nik
73  */
74 public class XValueHint extends AbstractValueHint {
75   private static final Logger LOG = Logger.getInstance(XValueHint.class);
76
77   private final XDebuggerEvaluator myEvaluator;
78   private final XDebugSession myDebugSession;
79   private final String myExpression;
80   private final String myValueName;
81   private final @Nullable XSourcePosition myExpressionPosition;
82   private final ExpressionInfo myExpressionInfo;
83   private Disposable myDisposable;
84
85   private static final Key<XValueHint> HINT_KEY = Key.create("allows only one value hint per editor");
86
87   public XValueHint(@NotNull Project project, @NotNull Editor editor, @NotNull Point point, @NotNull ValueHintType type,
88                     @NotNull ExpressionInfo expressionInfo, @NotNull XDebuggerEvaluator evaluator,
89                     @NotNull XDebugSession session) {
90     super(project, editor, point, type, expressionInfo.getTextRange());
91
92     myEvaluator = evaluator;
93     myDebugSession = session;
94     myExpression = XDebuggerEvaluateActionHandler.getExpressionText(expressionInfo, editor.getDocument());
95     myValueName = XDebuggerEvaluateActionHandler.getDisplayText(expressionInfo, editor.getDocument());
96     myExpressionInfo = expressionInfo;
97
98     VirtualFile file;
99     ConsoleView consoleView = ConsoleViewImpl.CONSOLE_VIEW_IN_EDITOR_VIEW.get(editor);
100     if (consoleView instanceof LanguageConsoleView) {
101       LanguageConsoleView console = ((LanguageConsoleView)consoleView);
102       file = console.getHistoryViewer() == editor ? console.getVirtualFile() : null;
103     }
104     else {
105       file = FileDocumentManager.getInstance().getFile(editor.getDocument());
106     }
107
108     myExpressionPosition = file != null ? XDebuggerUtil.getInstance().createPositionByOffset(file, expressionInfo.getTextRange().getStartOffset()) : null;
109   }
110
111   @Override
112   protected boolean canShowHint() {
113     return true;
114   }
115
116   @Override
117   protected boolean showHint(final JComponent component) {
118     boolean result = super.showHint(component);
119     if (result && getType() == ValueHintType.MOUSE_OVER_HINT) {
120       myDisposable = Disposer.newDisposable();
121       ShortcutSet shortcut = ActionManager.getInstance().getAction("ShowErrorDescription").getShortcutSet();
122       new DumbAwareAction() {
123         @Override
124         public void actionPerformed(@NotNull AnActionEvent e) {
125           hideHint();
126           final Point point = new Point(myPoint.x, myPoint.y + getEditor().getLineHeight());
127           new XValueHint(getProject(), getEditor(), point, ValueHintType.MOUSE_CLICK_HINT, myExpressionInfo, myEvaluator, myDebugSession).invokeHint();
128         }
129       }.registerCustomShortcutSet(shortcut, getEditor().getContentComponent(), myDisposable);
130     }
131     if (result) {
132       XValueHint prev = getEditor().getUserData(HINT_KEY);
133       if (prev != null) {
134         prev.hideHint();
135       }
136       getEditor().putUserData(HINT_KEY, this);
137     }
138     return result;
139   }
140
141   @Override
142   protected void onHintHidden() {
143     super.onHintHidden();
144     XValueHint prev = getEditor().getUserData(HINT_KEY);
145     if (prev == this) {
146       getEditor().putUserData(HINT_KEY, null);
147     }
148     if (myDisposable != null) {
149       Disposer.dispose(myDisposable);
150       myDisposable = null;
151     }
152   }
153
154   @Override
155   public void hideHint() {
156     super.hideHint();
157     if (myDisposable != null) {
158       Disposer.dispose(myDisposable);
159     }
160   }
161
162   @Override
163   protected void evaluateAndShowHint() {
164     myEvaluator.evaluate(myExpression, new XEvaluationCallbackBase() {
165       @Override
166       public void evaluated(@NotNull final XValue result) {
167         result.computePresentation(new XValueNodePresentationConfigurator.ConfigurableXValueNodeImpl() {
168           private XFullValueEvaluator myFullValueEvaluator;
169           private boolean myShown = false;
170
171           @Override
172           public void applyPresentation(@Nullable Icon icon,
173                                         @NotNull XValuePresentation valuePresenter,
174                                         boolean hasChildren) {
175             if (isHintHidden()) {
176               return;
177             }
178
179             SimpleColoredText text = new SimpleColoredText();
180             text.append(StringUtil.trimMiddle(myValueName, 200), XDebuggerUIConstants.VALUE_NAME_ATTRIBUTES);
181             XValueNodeImpl.buildText(valuePresenter, text);
182
183             if (!hasChildren) {
184               SimpleColoredComponent component = HintUtil.createInformationComponent();
185               text.appendToComponent(component);
186               if (myFullValueEvaluator != null) {
187                 component.append(myFullValueEvaluator.getLinkText(), XDebuggerTreeNodeHyperlink.TEXT_ATTRIBUTES,
188                                  new Consumer<MouseEvent>() {
189                                    @Override
190                                    public void consume(MouseEvent event) {
191                                      DebuggerUIUtil.showValuePopup(myFullValueEvaluator, event, getProject(), getEditor());
192                                    }
193                                  });
194                 LinkMouseListenerBase.installSingleTagOn(component);
195               }
196               showHint(component);
197             }
198             else if (getType() == ValueHintType.MOUSE_CLICK_HINT) {
199               if (!myShown) {
200                 showTree(result);
201               }
202             }
203             else {
204               if (getType() == ValueHintType.MOUSE_OVER_HINT) {
205                 text.insert(0, "(" + KeymapUtil.getFirstKeyboardShortcutText("ShowErrorDescription") + ") ",
206                             SimpleTextAttributes.GRAYED_ATTRIBUTES);
207               }
208
209               JComponent component = createExpandableHintComponent(text, new Runnable() {
210                 @Override
211                 public void run() {
212                   showTree(result);
213                 }
214               });
215               showHint(component);
216             }
217             myShown = true;
218           }
219
220           @Override
221           public void setFullValueEvaluator(@NotNull XFullValueEvaluator fullValueEvaluator) {
222             myFullValueEvaluator = fullValueEvaluator;
223           }
224
225           @Override
226           public boolean isObsolete() {
227             return isHintHidden();
228           }
229         }, XValuePlace.TOOLTIP);
230       }
231
232       @Override
233       public void errorOccurred(@NotNull final String errorMessage) {
234         ApplicationManager.getApplication().invokeLater(new Runnable() {
235           @Override
236           public void run() {
237             int start = 0, end = 0;
238             if (getCurrentRange() != null) {
239               start = getCurrentRange().getStartOffset();
240               end = getCurrentRange().getEndOffset();
241             }
242             HintManager.getInstance().showErrorHint(getEditor(), errorMessage, start,
243                                                     end, HintManager.ABOVE,
244                                                     HintManager.HIDE_BY_ESCAPE
245                                                     | HintManager.HIDE_BY_TEXT_CHANGE
246                                                     | HintManager.HIDE_BY_SCROLLING,
247                                                     0);
248           }
249         });
250         LOG.debug("Cannot evaluate '" + myExpression + "':" + errorMessage);
251       }
252     }, myExpressionPosition);
253   }
254
255   private void showTree(@NotNull XValue value) {
256     XValueMarkers<?,?> valueMarkers = ((XDebugSessionImpl)myDebugSession).getValueMarkers();
257     XDebuggerTreeCreator creator = new XDebuggerTreeCreator(myDebugSession.getProject(), myDebugSession.getDebugProcess().getEditorsProvider(),
258                                                             myDebugSession.getCurrentPosition(), valueMarkers);
259     showTreePopup(creator, Pair.create(value, myValueName));
260   }
261 }