| /* |
| * Copyright 2000-2011 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.debugger.ui; |
| |
| import com.intellij.codeInsight.hint.HintUtil; |
| import com.intellij.debugger.DebuggerBundle; |
| import com.intellij.debugger.DebuggerInvocationUtil; |
| import com.intellij.debugger.DebuggerManagerEx; |
| import com.intellij.debugger.actions.DebuggerActions; |
| import com.intellij.debugger.engine.DebugProcessImpl; |
| import com.intellij.debugger.engine.JVMName; |
| import com.intellij.debugger.engine.JVMNameUtil; |
| import com.intellij.debugger.engine.evaluation.*; |
| import com.intellij.debugger.engine.evaluation.expression.EvaluatorBuilderImpl; |
| import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator; |
| import com.intellij.debugger.engine.events.DebuggerContextCommandImpl; |
| import com.intellij.debugger.impl.DebuggerContextImpl; |
| import com.intellij.debugger.impl.DebuggerSession; |
| import com.intellij.debugger.impl.DebuggerUtilsEx; |
| import com.intellij.debugger.impl.EditorTextProvider; |
| import com.intellij.debugger.ui.impl.DebuggerTreeRenderer; |
| import com.intellij.debugger.ui.impl.InspectDebuggerTree; |
| import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl; |
| import com.intellij.debugger.ui.impl.watch.WatchItemDescriptor; |
| import com.intellij.debugger.ui.tree.render.DescriptorLabelListener; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.ActionManager; |
| import com.intellij.openapi.actionSystem.AnAction; |
| import com.intellij.openapi.actionSystem.CustomShortcutSet; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.*; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.ui.SimpleColoredText; |
| import com.intellij.ui.SimpleTextAttributes; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.xdebugger.impl.evaluate.quick.common.AbstractValueHint; |
| import com.intellij.xdebugger.impl.evaluate.quick.common.ValueHintType; |
| import com.sun.jdi.Method; |
| import com.sun.jdi.PrimitiveValue; |
| import com.sun.jdi.Value; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.KeyEvent; |
| |
| /** |
| * @author lex |
| * @since Nov 24, 2003 |
| */ |
| public class ValueHint extends AbstractValueHint { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.ui.ValueHint"); |
| private PsiElement myCurrentExpression = null; |
| private Value myValueToShow = null; |
| |
| private ValueHint(Project project, Editor editor, Point point, ValueHintType type, final PsiElement selectedExpression, final TextRange textRange) { |
| super(project, editor, point, type, textRange); |
| myCurrentExpression = selectedExpression; |
| } |
| |
| public static ValueHint createValueHint(Project project, Editor editor, Point point, ValueHintType type) { |
| Trinity<PsiElement, TextRange, Value> trinity = getSelectedExpression(project, editor, point, type); |
| final ValueHint hint = new ValueHint(project, editor, point, type, trinity.getFirst(), trinity.getSecond()); |
| hint.myValueToShow = trinity.getThird(); |
| return hint; |
| } |
| |
| @Override |
| protected boolean canShowHint() { |
| return myCurrentExpression != null; |
| } |
| |
| |
| @Nullable |
| private ExpressionEvaluator getExpressionEvaluator(DebuggerContextImpl debuggerContext) throws EvaluateException { |
| if (myCurrentExpression instanceof PsiExpression) { |
| return EvaluatorBuilderImpl.getInstance().build(myCurrentExpression, debuggerContext.getSourcePosition()); |
| } |
| |
| TextWithImportsImpl textWithImports = new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, myCurrentExpression.getText()); |
| CodeFragmentFactory factory = DebuggerUtilsEx.findAppropriateCodeFragmentFactory(textWithImports, myCurrentExpression); |
| JavaCodeFragment codeFragment = factory.createCodeFragment(textWithImports, myCurrentExpression.getContext(), getProject()); |
| codeFragment.forceResolveScope(GlobalSearchScope.allScope(getProject())); |
| return factory.getEvaluatorBuilder().build(codeFragment, debuggerContext.getSourcePosition()); |
| } |
| |
| |
| @Override |
| protected void evaluateAndShowHint() { |
| final DebuggerContextImpl debuggerContext = DebuggerManagerEx.getInstanceEx(getProject()).getContext(); |
| |
| final DebuggerSession debuggerSession = debuggerContext.getDebuggerSession(); |
| if(debuggerSession == null || !debuggerSession.isPaused()) return; |
| |
| try { |
| |
| final ExpressionEvaluator evaluator = getExpressionEvaluator(debuggerContext); |
| if (evaluator == null) return; |
| |
| debuggerContext.getDebugProcess().getManagerThread().schedule(new DebuggerContextCommandImpl(debuggerContext) { |
| @Override |
| public Priority getPriority() { |
| return Priority.HIGH; |
| } |
| |
| @Override |
| public void threadAction() { |
| try { |
| final EvaluationContextImpl evaluationContext = debuggerContext.createEvaluationContext(); |
| |
| final String expressionText = ApplicationManager.getApplication().runReadAction(new Computable<String>() { |
| @Override |
| public String compute() { |
| return myCurrentExpression.getText(); |
| } |
| }); |
| final TextWithImports text = new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText); |
| final Value value = myValueToShow != null? myValueToShow : evaluator.evaluate(evaluationContext); |
| |
| final WatchItemDescriptor descriptor = new WatchItemDescriptor(getProject(), text, value); |
| if (!isActiveTooltipApplicable(value) || getType() == ValueHintType.MOUSE_OVER_HINT) { |
| if (getType() == ValueHintType.MOUSE_OVER_HINT) { |
| // force using default renderer for mouse over hint in order to not to call accidentally methods while rendering |
| // otherwise, if the hint is invoked explicitly, show it with the right "auto" renderer |
| descriptor.setRenderer(DebugProcessImpl.getDefaultRenderer(value)); |
| } |
| descriptor.updateRepresentation(evaluationContext, new DescriptorLabelListener() { |
| @Override |
| public void labelChanged() { |
| if(getCurrentRange() != null) { |
| if(getType() != ValueHintType.MOUSE_OVER_HINT || descriptor.isValueValid()) { |
| final SimpleColoredText simpleColoredText = DebuggerTreeRenderer.getDescriptorText(debuggerContext, descriptor, true); |
| if (isActiveTooltipApplicable(value)){ |
| simpleColoredText.append(" (" + DebuggerBundle.message("active.tooltip.suggestion") + ")", SimpleTextAttributes.GRAYED_ATTRIBUTES); |
| } |
| showHint(simpleColoredText, descriptor); |
| } |
| } |
| } |
| }); |
| } |
| else { |
| createAndShowTree(expressionText, descriptor); |
| } |
| } |
| catch (EvaluateException e) { |
| LOG.debug(e); |
| } |
| } |
| |
| }); |
| } |
| catch (EvaluateException e) { |
| LOG.debug(e); |
| } |
| } |
| |
| private void createAndShowTree(final String expressionText, final NodeDescriptorImpl descriptor) { |
| final DebuggerTreeCreatorImpl creator = new DebuggerTreeCreatorImpl(getProject()); |
| DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() { |
| @Override |
| public void run() { |
| showTreePopup(creator, Pair.create(descriptor, expressionText)); |
| } |
| }); |
| } |
| |
| private static boolean isActiveTooltipApplicable(final Value value) { |
| return value != null && !(value instanceof PrimitiveValue); |
| } |
| |
| private void showHint(final SimpleColoredText text, final WatchItemDescriptor descriptor) { |
| DebuggerInvocationUtil.invokeLater(getProject(), new Runnable() { |
| @Override |
| public void run() { |
| if(!isHintHidden()) { |
| JComponent component; |
| if (!isActiveTooltipApplicable(descriptor.getValue())) { |
| component = HintUtil.createInformationLabel(text); |
| } |
| else { |
| component = createExpandableHintComponent(text, new Runnable() { |
| @Override |
| public void run() { |
| final DebuggerContextImpl debuggerContext = DebuggerManagerEx.getInstanceEx(getProject()).getContext(); |
| final DebugProcessImpl debugProcess = debuggerContext.getDebugProcess(); |
| debugProcess.getManagerThread().schedule(new DebuggerContextCommandImpl(debuggerContext) { |
| @Override |
| public void threadAction() { |
| descriptor.setRenderer(debugProcess.getAutoRenderer(descriptor)); |
| final String expressionText = ApplicationManager.getApplication().runReadAction(new Computable<String>() { |
| @Override |
| public String compute() { |
| return myCurrentExpression.getText(); |
| } |
| }); |
| |
| createAndShowTree(expressionText, descriptor); |
| } |
| }); |
| } |
| }); |
| } |
| if (!showHint(component)) return; |
| if(getType() == ValueHintType.MOUSE_CLICK_HINT) { |
| HintUtil.createInformationLabel(text).requestFocusInWindow(); |
| } |
| } |
| } |
| }); |
| } |
| |
| public static InspectDebuggerTree createInspectTree(final NodeDescriptorImpl descriptor, Project project) { |
| final InspectDebuggerTree tree = new InspectDebuggerTree(project); |
| final AnAction setValueAction = ActionManager.getInstance().getAction(DebuggerActions.SET_VALUE); |
| setValueAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)), tree); |
| Disposer.register(tree, new Disposable() { |
| @Override |
| public void dispose() { |
| setValueAction.unregisterCustomShortcutSet(tree); |
| } |
| }); |
| tree.setInspectDescriptor(descriptor); |
| DebuggerContextImpl context = DebuggerManagerEx.getInstanceEx(project).getContext(); |
| tree.rebuild(context); |
| return tree; |
| } |
| |
| @Nullable |
| private static Pair<PsiElement, TextRange> findExpression(PsiElement element, boolean allowMethodCalls) { |
| final EditorTextProvider textProvider = EditorTextProvider.EP.forLanguage(element.getLanguage()); |
| if (textProvider != null) { |
| return textProvider.findExpression(element, allowMethodCalls); |
| } |
| return null; |
| } |
| |
| private static Trinity<PsiElement, TextRange, Value> getSelectedExpression(final Project project, final Editor editor, final Point point, final ValueHintType type) { |
| final Ref<PsiElement> selectedExpression = Ref.create(null); |
| final Ref<TextRange> currentRange = Ref.create(null); |
| final Ref<Value> preCalculatedValue = Ref.create(null); |
| |
| PsiDocumentManager.getInstance(project).commitAndRunReadAction(new Runnable() { |
| @Override |
| public void run() { |
| // Point -> offset |
| final int offset = calculateOffset(editor, point); |
| |
| |
| PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); |
| |
| if(psiFile == null || !psiFile.isValid()) { |
| return; |
| } |
| |
| int selectionStart = editor.getSelectionModel().getSelectionStart(); |
| int selectionEnd = editor.getSelectionModel().getSelectionEnd(); |
| |
| if((type == ValueHintType.MOUSE_CLICK_HINT || type == ValueHintType.MOUSE_ALT_OVER_HINT) && (selectionStart <= offset && offset <= selectionEnd)) { |
| PsiElement ctx = (selectionStart > 0) ? psiFile.findElementAt(selectionStart - 1) : psiFile.findElementAt(selectionStart); |
| try { |
| String text = editor.getSelectionModel().getSelectedText(); |
| if(text != null && ctx != null) { |
| final JVMElementFactory factory = JVMElementFactories.getFactory(ctx.getLanguage(), project); |
| if (factory == null) { |
| return; |
| } |
| selectedExpression.set(factory.createExpressionFromText(text, ctx)); |
| currentRange.set(new TextRange(editor.getSelectionModel().getSelectionStart(), editor.getSelectionModel().getSelectionEnd())); |
| } |
| } |
| catch (IncorrectOperationException ignored) { |
| } |
| } |
| |
| if(currentRange.get() == null) { |
| PsiElement elementAtCursor = psiFile.findElementAt(offset); |
| if (elementAtCursor == null) { |
| return; |
| } |
| Pair<PsiElement, TextRange> pair = findExpression(elementAtCursor, type == ValueHintType.MOUSE_CLICK_HINT || type == ValueHintType.MOUSE_ALT_OVER_HINT); |
| if (pair == null) { |
| if (type == ValueHintType.MOUSE_OVER_HINT) { |
| final DebuggerSession debuggerSession = DebuggerManagerEx.getInstanceEx(project).getContext().getDebuggerSession(); |
| if(debuggerSession != null && debuggerSession.isPaused()) { |
| final Pair<Method, Value> lastExecuted = debuggerSession.getProcess().getLastExecutedMethod(); |
| if (lastExecuted != null) { |
| final Method method = lastExecuted.getFirst(); |
| if (method != null) { |
| final Pair<PsiElement, TextRange> expressionPair = findExpression(elementAtCursor, true); |
| if (expressionPair != null && expressionPair.getFirst() instanceof PsiMethodCallExpression) { |
| final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expressionPair.getFirst(); |
| final PsiMethod psiMethod = methodCallExpression.resolveMethod(); |
| if (psiMethod != null) { |
| final JVMName jvmSignature = JVMNameUtil.getJVMSignature(psiMethod); |
| try { |
| if (method.name().equals(psiMethod.getName()) && method.signature().equals(jvmSignature.getName(debuggerSession.getProcess()))) { |
| pair = expressionPair; |
| preCalculatedValue.set(lastExecuted.getSecond()); |
| } |
| } |
| catch (EvaluateException ignored) { |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| if (pair == null) { |
| return; |
| } |
| selectedExpression.set(pair.getFirst()); |
| currentRange.set(pair.getSecond()); |
| } |
| } |
| }); |
| return Trinity.create(selectedExpression.get(), currentRange.get(), preCalculatedValue.get()); |
| } |
| } |