blob: b9a58844585a2dff3b3c8ca1d33f2b3cc343f3be [file] [log] [blame]
/*
* 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.editor.colors.EditorColorsManager;
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.*;
import com.intellij.util.NotNullProducer;
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<Map<Variable, VariableValue>> CACHE = Key.create("debug.inline.variables.cache");
@Override
public Collection<LineExtensionInfo> getLineExtensions(@NotNull Project project, @NotNull VirtualFile file, int lineNumber) {
if (!Registry.is("ide.debugger.inline")) {
return null;
}
final Map<Pair<VirtualFile, Integer>, Set<XValueNodeImpl>> map = project.getUserData(XVariablesView.DEBUG_VARIABLES);
final Map<VirtualFile, Long> timestamps = project.getUserData(XVariablesView.DEBUG_VARIABLES_TIMESTAMPS);
final Document doc = FileDocumentManager.getInstance().getDocument(file);
if (map == null || timestamps == null || doc == null) {
return null;
}
Map<Variable, VariableValue> oldValues = project.getUserData(CACHE);
if (oldValues == null) {
oldValues = new HashMap<Variable, VariableValue>();
project.putUserData(CACHE, oldValues);
}
final Long timestamp = timestamps.get(file);
if (timestamp == null || timestamp < doc.getModificationStamp()) {
return null;
}
Set<XValueNodeImpl> values = map.get(Pair.create(file, lineNumber));
if (values != null && !values.isEmpty()) {
final int bpLine = getCurrentBreakPointLine(values);
ArrayList<LineExtensionInfo> result = new ArrayList<LineExtensionInfo>();
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<XValueNodeImpl> 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;
}
private static boolean isDarkEditor() {
Color bg = EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground();
return ColorUtil.isDark(bg);
}
public static JBColor getForeground() {
return new JBColor(new NotNullProducer<Color>() {
@SuppressWarnings("UseJBColor")
@NotNull
@Override
public Color produce() {
return isDarkEditor() ? Registry.getColor("ide.debugger.inline.dark.fg.color", new Color(0x3d8065))
: Registry.getColor("ide.debugger.inline.fg.color", new Color(0x3d8065));
}
});
}
public static JBColor getChangedForeground() {
return new JBColor(new NotNullProducer<Color>() {
@SuppressWarnings("UseJBColor")
@NotNull
@Override
public Color produce() {
return isDarkEditor() ? Registry.getColor("ide.debugger.inline.dark.fg.modified.color", new Color(0xa1830a))
: Registry.getColor("ide.debugger.inline.fg.modified.color", new Color(0xca8021));
}
});
}
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<LineExtensionInfo> result) {
if (isArray(actual) && isArray(old)) {
List<String> actualParts = getArrayParts(actual);
List<String> 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<String> getArrayParts(String array) {
return StringUtil.split(array.substring(1, array.length() - 1), ", ");
}
}
}