/* * Copyright 2000-2016 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.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.*; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.frame.presentation.XValuePresentation; import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.XDebuggerManagerImpl; 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 com.intellij.xdebugger.settings.XDebuggerSettingsManager; import com.intellij.xdebugger.ui.DebuggerColors; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.*; import java.util.List; /** * @author Konstantin Bulenkov */ public class XDebuggerEditorLinePainter extends EditorLinePainter { public static final Key<Map<Variable, VariableValue>> CACHE = Key.create("debug.inline.variables.cache"); // we want to limit number of line extensions to avoid very slow painting // the constant is rather random (feel free to adjust it upon getting a new information) private static final int LINE_EXTENSIONS_MAX_COUNT = 200; @Override public Collection<LineExtensionInfo> getLineExtensions(@NotNull Project project, @NotNull VirtualFile file, int lineNumber) { if (!XDebuggerSettingsManager.getInstance().getDataViewSettings().isShowValuesInline()) { return null; } XVariablesView.InlineVariablesInfo data = project.getUserData(XVariablesView.DEBUG_VARIABLES); final Document doc = FileDocumentManager.getInstance().getDocument(file); if (data == null || doc == null) { return null; } Map<Variable, VariableValue> oldValues = project.getUserData(CACHE); if (oldValues == null) { oldValues = new HashMap<>(); project.putUserData(CACHE, oldValues); } List<XValueNodeImpl> values = data.get(file, lineNumber, doc.getModificationStamp()); if (values != null && !values.isEmpty()) { XDebugSession session = XDebugView.getSession(values.iterator().next().getTree()); final int bpLine = getCurrentBreakPointLineInFile(session, file); boolean isTopFrame = session instanceof XDebugSessionImpl && ((XDebugSessionImpl)session).isTopFrameSelected(); final TextAttributes attributes = bpLine == lineNumber && isTopFrame && ((XDebuggerManagerImpl)XDebuggerManager.getInstance(project)).isFullLineHighlighter() ? getTopFrameSelectedAttributes() : getNormalAttributes(); ArrayList<VariableText> 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 && !value.getTree().isUnderRemoteDebug()) { ((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 ignored) { continue; } final String name = value.getName(); if (StringUtil.isEmpty(text.toString())) { continue; } final VariableText res = new VariableText(); result.add(res); res.add(new LineExtensionInfo(" " + name + ": ", attributes)); Variable var = new Variable(name, lineNumber); VariableValue variableValue = oldValues.computeIfAbsent(var, k -> new VariableValue(text.toString(), null, value.hashCode())); 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()) { res.add(new LineExtensionInfo(s, attributes)); } } else { variableValue.produceChangedParts(res.infos); } } final List<LineExtensionInfo> infos = new ArrayList<>(); for (VariableText text : result) { infos.addAll(text.infos); } return infos.size() > LINE_EXTENSIONS_MAX_COUNT ? infos.subList(0, LINE_EXTENSIONS_MAX_COUNT) : infos; } return null; } private static int getCurrentBreakPointLineInFile(@Nullable XDebugSession session, VirtualFile file) { try { if (session != null) { final XSourcePosition position = session.getCurrentPosition(); if (position != null && position.getFile().equals(file)) { return position.getLine(); } } } catch (Exception ignore){} return -1; } private static boolean isDarkEditor() { Color bg = EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground(); return ColorUtil.isDark(bg); } public static TextAttributes getNormalAttributes() { TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(DebuggerColors.INLINED_VALUES); if (attributes == null || attributes.getForegroundColor() == null) { return new TextAttributes(new JBColor(() -> isDarkEditor() ? new Color(0x3d8065) : Gray._135), null, null, null, Font.ITALIC); } return attributes; } public static TextAttributes getChangedAttributes() { TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(DebuggerColors.INLINED_VALUES_MODIFIED); if (attributes == null || attributes.getForegroundColor() == null) { return new TextAttributes(new JBColor(() -> isDarkEditor() ? new Color(0xa1830a) : new Color(0xca8021)), null, null, null, Font.ITALIC); } return attributes; } private static TextAttributes getTopFrameSelectedAttributes() { TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(DebuggerColors.INLINED_VALUES_EXECUTION_LINE); if (attributes == null || attributes.getForegroundColor() == null) { //noinspection UseJBColor return new TextAttributes(isDarkEditor() ? new Color(255, 235, 9) : new Color(0, 255, 86), null, null, null, Font.ITALIC); } return attributes; } static class Variable { private final int lineNumber; private final 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<LineExtensionInfo> result) { if (isArray(actual) && isArray(old)) { List<String> actualParts = getArrayParts(actual); List<String> oldParts = getArrayParts(old); result.add(new LineExtensionInfo("{", getNormalAttributes())); 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), getNormalAttributes())); } else { result.add(new LineExtensionInfo(actualParts.get(i), getChangedAttributes())); } if (i != actualParts.size() - 1) { result.add(new LineExtensionInfo(", ", getNormalAttributes())); } } result.add(new LineExtensionInfo("}", getNormalAttributes())); return; } result.add(new LineExtensionInfo(actual, getChangedAttributes())); } private static boolean isArray(String s) { return s != null && s.startsWith("{") && s.endsWith("}"); } private static List<String> getArrayParts(String array) { return StringUtil.split(array.substring(1, array.length() - 1), ", "); } } private static class VariableText { final List<LineExtensionInfo> infos = new ArrayList<>(); int length = 0; void add(LineExtensionInfo info) { infos.add(info); length += info.getText().length(); } } }