/* * Copyright 2000-2014 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.xdebugger.impl.evaluate; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorLinePainter; import com.intellij.openapi.editor.LineExtensionInfo; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.ui.SimpleColoredText; import com.intellij.ui.SimpleTextAttributes; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.presentation.XValuePresentation; import com.intellij.xdebugger.impl.frame.XDebugView; import com.intellij.xdebugger.impl.frame.XVariablesView; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueTextRendererImpl; import org.jetbrains.annotations.NotNull; import java.awt.*; import java.util.*; import java.util.List; /** * @author Konstantin Bulenkov */ public class XDebuggerEditorLinePainter extends EditorLinePainter { public static final Key> CACHE = Key.create("debug.inline.variables.cache"); @Override public Collection getLineExtensions(@NotNull Project project, @NotNull VirtualFile file, int lineNumber) { if (!Registry.is("ide.debugger.inline")) { return null; } final Map, Set> map = project.getUserData(XVariablesView.DEBUG_VARIABLES); final Map timestamps = project.getUserData(XVariablesView.DEBUG_VARIABLES_TIMESTAMPS); final Document doc = FileDocumentManager.getInstance().getDocument(file); if (map == null || timestamps == null || doc == null) { return null; } Map oldValues = project.getUserData(CACHE); if (oldValues == null) { oldValues = new HashMap(); project.putUserData(CACHE, oldValues); } final Long timestamp = timestamps.get(file); if (timestamp == null || timestamp < doc.getModificationStamp()) { return null; } Set values = map.get(Pair.create(file, lineNumber)); if (values != null && !values.isEmpty()) { final int bpLine = getCurrentBreakPointLine(values); ArrayList result = new ArrayList(); for (XValueNodeImpl value : values) { SimpleColoredText text = new SimpleColoredText(); XValueTextRendererImpl renderer = new XValueTextRendererImpl(text); final XValuePresentation presentation = value.getValuePresentation(); if (presentation == null) continue; try { if (presentation instanceof XValueCompactPresentation) { ((XValueCompactPresentation)presentation).renderValue(renderer, value); } else { presentation.renderValue(renderer); } if (StringUtil.isEmpty(text.toString())) { final String type = value.getValuePresentation().getType(); if (!StringUtil.isEmpty(type)) { text.append(type, SimpleTextAttributes.REGULAR_ATTRIBUTES); } } } catch (Exception e) { continue; } final Color color = bpLine == lineNumber ? new JBColor(Gray._180, new Color(147, 217, 186)) : getForeground(); final String name = value.getName(); if (StringUtil.isEmpty(text.toString())) { continue; } result.add(new LineExtensionInfo(" " + name + ": ", color, null, null, Font.PLAIN)); Variable var = new Variable(name, lineNumber); VariableValue variableValue = oldValues.get(var); if (variableValue == null) { variableValue = new VariableValue(text.toString(), null, value.hashCode()); oldValues.put(var, variableValue); } if (variableValue.valueNodeHashCode != value.hashCode()) { variableValue.old = variableValue.actual; variableValue.actual = text.toString(); variableValue.valueNodeHashCode = value.hashCode(); } if (!variableValue.isChanged()) { for (String s : text.getTexts()) { result.add(new LineExtensionInfo(s, color, null, null, Font.PLAIN)); } } else { variableValue.produceChangedParts(result); } } return result; } return null; } private static int getCurrentBreakPointLine(Set values) { try { final XValueNodeImpl node = values.iterator().next(); final XDebugSession session = XDebugView.getSession(node.getTree()); if (session != null) { final XSourcePosition position = session.getCurrentPosition(); if (position != null) { return position.getLine(); } } } catch (Exception ignore){} return -1; } public static JBColor getForeground() { return new JBColor(new Color(61, 128, 101), new Color(61, 128, 101)); } public static JBColor getChangedForeground() { return new JBColor(new Color(202, 128, 33), new Color(161, 131, 10)); } static class Variable { private int lineNumber; private String name; public Variable(String name, int lineNumber) { this.lineNumber = lineNumber; this.name = name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Variable variable = (Variable)o; if (lineNumber != variable.lineNumber) return false; if (!name.equals(variable.name)) return false; return true; } @Override public int hashCode() { int result = lineNumber; result = 31 * result + name.hashCode(); return result; } } static class VariableValue { private String actual; private String old; private int valueNodeHashCode; public VariableValue(String actual, String old, int valueNodeHashCode) { this.actual = actual; this.old = old; this.valueNodeHashCode = valueNodeHashCode; } public boolean isChanged() { return old != null && !StringUtil.equals(actual, old); } public void produceChangedParts(List result) { if (isArray(actual) && isArray(old)) { List actualParts = getArrayParts(actual); List oldParts = getArrayParts(old); result.add(new LineExtensionInfo("{", getForeground(), null, null, Font.PLAIN)); for (int i = 0; i < actualParts.size(); i++) { if (i < oldParts.size() && StringUtil.equals(actualParts.get(i), oldParts.get(i))) { result.add(new LineExtensionInfo(actualParts.get(i), getForeground(), null, null, Font.PLAIN)); } else { result.add(new LineExtensionInfo(actualParts.get(i), getChangedForeground(), null, null, Font.BOLD)); } if (i != actualParts.size() - 1) { result.add(new LineExtensionInfo(", ", getForeground(), null, null, Font.PLAIN)); } } result.add(new LineExtensionInfo("}", getForeground(), null, null, Font.PLAIN)); return; } result.add(new LineExtensionInfo(actual, getChangedForeground(), null, null, Font.BOLD)); } private static boolean isArray(String s) { return s != null && s.startsWith("{") && s.endsWith("}"); } private static List getArrayParts(String array) { return StringUtil.split(array.substring(1, array.length() - 1), ", "); } } }