PY-8118 Add hex and binary integer renderers to Python debugger and console

GitOrigin-RevId: 8295632e227e7a68a37ceffcc34551d4cc606281
This commit is contained in:
Andrey Lisin
2019-10-15 13:40:27 +00:00
committed by intellij-monorepo-bot
parent d825cd0866
commit 64e1a276b7
13 changed files with 376 additions and 2 deletions
@@ -2,6 +2,7 @@ package com.jetbrains.python.debugger;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
@@ -9,6 +10,7 @@ import com.intellij.xdebugger.frame.*;
import com.intellij.xdebugger.frame.presentation.XRegularValuePresentation;
import com.jetbrains.python.debugger.pydev.PyDebugCallback;
import com.jetbrains.python.debugger.pydev.PyVariableLocator;
import com.jetbrains.python.debugger.render.PyNodeRenderer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -51,6 +53,8 @@ public class PyDebugValue extends XNamedValue {
private int myOffset;
private int myCollectionLength = -1;
private @NotNull PyDebugValueDescriptor myDescriptor = new PyDebugValueDescriptor();
public enum ValuesPolicy {
SYNC, ASYNC, ON_DEMAND
}
@@ -336,6 +340,7 @@ public class PyDebugValue extends XNamedValue {
if (value.length() >= MAX_VALUE) {
value = value.substring(0, MAX_VALUE);
}
value = applyRendererIfApplicable(value);
setElementPresentation(node, value);
}
@@ -456,6 +461,9 @@ public class PyDebugValue extends XNamedValue {
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
XValueChildrenList values = myFrameAccessor.loadVariable(this);
restoreChildrenRenderers(values);
if (!node.isObsolete()) {
updateLengthIfIsCollection(values);
@@ -655,4 +663,49 @@ public class PyDebugValue extends XNamedValue {
}
}
@NotNull
public PyDebugValueDescriptor getDescriptor() {
return myDescriptor;
}
public void setDescriptor(@NotNull PyDebugValueDescriptor descriptor) {
myDescriptor = descriptor;
}
private void restoreChildrenRenderers(XValueChildrenList values) {
PyDebugValueDescriptor descriptor = getDescriptor();
Map<String, PyDebugValueDescriptor> childrenDescriptors = descriptor.getChildrenDescriptors();
if (childrenDescriptors == null) {
childrenDescriptors = Maps.newHashMap();
descriptor.setChildrenDescriptors(childrenDescriptors);
}
if (values == null) return;
for (int i = 0; i < values.size(); i++) {
if (values.getValue(i) instanceof PyDebugValue) {
PyDebugValue value = (PyDebugValue) values.getValue(i);
descriptor = childrenDescriptors.getOrDefault(value.getName(), null);
if (descriptor == null) {
descriptor = new PyDebugValueDescriptor();
childrenDescriptors.put(value.getName(), descriptor);
}
value.setDescriptor(descriptor);
}
}
}
private String applyRendererIfApplicable(String value) {
final PyNodeRenderer renderer = getDescriptor().getRenderer();
final String type = getType();
if (renderer == null || type == null) return value;
if (renderer.isApplicable(type)) {
return renderer.render(value);
}
else {
getDescriptor().setRenderer(null);
}
return value;
}
}
@@ -0,0 +1,40 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger;
import com.jetbrains.python.debugger.render.PyNodeRenderer;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public class PyDebugValueDescriptor {
@Nullable private PyNodeRenderer myRenderer;
/**
* Because of the variables and recreated every time we make a step in the debugger,
* we have to save the variable children's descriptors to restore them when updating
* the debugger variable view. Note that even if there is no renderer associated
* with a {@link PyDebugValue} instance, it still maintains a value descriptor
* to preserve access to its children renderers. Otherwise, it will be impossible to
* reach them during the renderers' restoration process.
*/
@Nullable private Map<String, PyDebugValueDescriptor> myChildrenDescriptors;
@Nullable
public PyNodeRenderer getRenderer() {
return myRenderer;
}
public void setRenderer(@Nullable PyNodeRenderer renderer) {
myRenderer = renderer;
}
@Nullable
public Map<String, PyDebugValueDescriptor> getChildrenDescriptors() {
return myChildrenDescriptors;
}
public void setChildrenDescriptors(@Nullable Map<String, PyDebugValueDescriptor> childrenDescriptors) {
myChildrenDescriptors = childrenDescriptors;
}
}
@@ -0,0 +1,14 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.render;
import com.google.common.collect.ImmutableSet;
public abstract class AbstractIntegerRenderer implements PyNodeRenderer {
private static final ImmutableSet<String> mySupportedTypes = ImmutableSet.of("int");
@Override
public boolean isApplicable(String type) {
return type != null && mySupportedTypes.contains(type);
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.render;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.math.BigInteger;
public class BinaryRenderer extends AbstractIntegerRenderer {
private static final String myName = "Binary";
@Override
public @NonNls String getName() {
return myName;
}
@Override
public @NotNull String render(@NotNull String value) {
final String prefix = value.startsWith("-") ? "-0b" : "0b";
return prefix + new BigInteger(value).abs().toString(2);
}
}
@@ -0,0 +1,20 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.render;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
public class DecimalRenderer extends AbstractIntegerRenderer {
private static final String myName = "Decimal";
@Override
public @NonNls String getName() {
return myName;
}
@Override
public @NotNull String render(@NotNull String value) {
return value;
}
}
@@ -0,0 +1,23 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.render;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.math.BigInteger;
public class HexRenderer extends AbstractIntegerRenderer {
private static final String myName = "Hex";
@Override
public @NonNls String getName() {
return myName;
}
@Override
public @NotNull @NonNls String render(@NotNull String value) {
final String prefix = value.startsWith("-") ? "-0x" : "0x";
return prefix + new BigInteger(value).abs().toString(16).toUpperCase();
}
}
@@ -0,0 +1,12 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.render;
public interface PyNodeRenderer {
String getName();
String render(String value);
boolean isApplicable(String type);
}
@@ -0,0 +1,29 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.render;
import org.jetbrains.annotations.Contract;
import java.util.ArrayList;
import java.util.List;
public class PyNodeRendererManager {
private static final PyNodeRendererManager INSTANCE = new PyNodeRendererManager();
private final List<PyNodeRenderer> myAvailableRenderers = new ArrayList<>();
private PyNodeRendererManager() {
myAvailableRenderers.add(new BinaryRenderer());
myAvailableRenderers.add(new DecimalRenderer());
myAvailableRenderers.add(new HexRenderer());
}
@Contract(pure = true)
public static PyNodeRendererManager getInstance() {
return INSTANCE;
}
public List<PyNodeRenderer> getAvailableRenderers() {
return new ArrayList<>(myAvailableRenderers);
}
}
@@ -827,6 +827,10 @@
<add-to-group group-id="XDebugger.ValueGroup" anchor="after" relative-to-action="Debugger.AddToWatch"/>
</action>
<action id="PyDebugger.ViewAsGroup" class="com.jetbrains.python.debugger.actions.ViewAsGroup" text="View as">
<add-to-group group-id="XDebugger.ValueGroup" anchor="last" />
</action>
<action id="PyConvertModuleToPackage" class="com.jetbrains.python.refactoring.convertModulePackage.PyConvertModuleToPackageAction"
text="Convert to Python Package"
description="Create package with the same name and move content of the module to its __init__.py">
@@ -1,6 +1,7 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.console;
import com.google.common.collect.Maps;
import com.intellij.execution.console.LanguageConsoleImpl;
import com.intellij.execution.filters.OpenFileHyperlinkInfo;
import com.intellij.execution.impl.ConsoleViewUtil;
@@ -40,6 +41,7 @@ import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.console.completion.PythonConsoleAutopopupBlockingHandler;
import com.jetbrains.python.console.pydev.ConsoleCommunication;
import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
import com.jetbrains.python.debugger.PyDebugValueDescriptor;
import com.jetbrains.python.debugger.PyDebuggerEditorsProvider;
import com.jetbrains.python.debugger.PyStackFrame;
import com.jetbrains.python.debugger.PyStackFrameInfo;
@@ -54,6 +56,7 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.awt.*;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -77,6 +80,8 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable
private boolean isShowVars;
@Nullable private String mySdkHomePath;
private final Map<String, Map<String, PyDebugValueDescriptor>> myDescriptorsCache = Maps.newConcurrentMap();
/**
* @param testMode this console will be used to display test output and should support TC messages
*/
@@ -364,6 +369,7 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable
public void showVariables(PydevConsoleCommunication consoleCommunication) {
PyStackFrame stackFrame = new PyStackFrame(getProject(), consoleCommunication, new PyStackFrameInfo("", "", "", null), null);
stackFrame.restoreChildrenDescriptors(myDescriptorsCache);
final XStandaloneVariablesView view = new XStandaloneVariablesView(getProject(), new PyDebuggerEditorsProvider(), stackFrame);
consoleCommunication.addCommunicationListener(new ConsoleCommunicationListener() {
@Override
@@ -111,6 +111,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
private boolean isCythonWarningShown = false;
@Nullable private XCompositeNode myCurrentRootNode;
private final Map<String, Map<String, PyDebugValueDescriptor>> myDescriptorsCache = Maps.newConcurrentMap();
public PyDebugProcess(@NotNull XDebugSession session,
@NotNull ServerSocket serverSocket,
@NotNull ExecutionConsole executionConsole,
@@ -1066,8 +1068,10 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
}
public PyStackFrame createStackFrame(PyStackFrameInfo frameInfo) {
return new PyStackFrame(getSession().getProject(), this, frameInfo,
getPositionConverter().convertFromPython(frameInfo.getPosition(), frameInfo.getName()));
final PyStackFrame frame = new PyStackFrame(getSession().getProject(), this, frameInfo,
getPositionConverter().convertFromPython(frameInfo.getPosition(), frameInfo.getName()));
frame.restoreChildrenDescriptors(myDescriptorsCache);
return frame;
}
@Override
@@ -1,6 +1,7 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
@@ -48,6 +49,8 @@ public class PyStackFrame extends XStackFrame {
private final PyStackFrameInfo myFrameInfo;
private final XSourcePosition myPosition;
private @Nullable Map<String, PyDebugValueDescriptor> myChildrenDescriptors;
public PyStackFrame(@NotNull Project project,
@NotNull final PyFrameAccessor debugProcess,
@NotNull final PyStackFrameInfo frameInfo, XSourcePosition position) {
@@ -146,6 +149,9 @@ public class PyStackFrame extends XStackFrame {
String name = children.getName(i);
if (value instanceof PyDebugValue) {
PyDebugValue pyValue = (PyDebugValue)value;
restoreValueDescriptor(pyValue);
if (pyValue.isReturnedVal() && debuggerSettings.isWatchReturnValues()) {
returnedValues.put(name, value);
}
@@ -209,4 +215,26 @@ public class PyStackFrame extends XStackFrame {
protected XSourcePosition getPosition() {
return myPosition;
}
public void setChildrenDescriptors(@Nullable Map<String, PyDebugValueDescriptor> childrenDescriptors) {
myChildrenDescriptors = childrenDescriptors;
}
public void restoreChildrenDescriptors(@NotNull Map<String, Map<String, PyDebugValueDescriptor>> descriptorsCache) {
final String threadFrameId = getThreadFrameId();
final Map<String, PyDebugValueDescriptor> childrenDescriptors = descriptorsCache.getOrDefault(threadFrameId, Maps.newHashMap());
setChildrenDescriptors(childrenDescriptors);
descriptorsCache.put(threadFrameId, childrenDescriptors);
}
private void restoreValueDescriptor(PyDebugValue value) {
if (myChildrenDescriptors != null) {
PyDebugValueDescriptor descriptor = myChildrenDescriptors.getOrDefault(value.getName(), null);
if (descriptor == null) {
descriptor = new PyDebugValueDescriptor();
myChildrenDescriptors.put(value.getName(), descriptor);
}
value.setDescriptor(descriptor);
}
}
}
@@ -0,0 +1,118 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.debugger.actions;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.project.DumbAware;
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.render.DecimalRenderer;
import com.jetbrains.python.debugger.render.PyNodeRenderer;
import com.jetbrains.python.debugger.render.PyNodeRendererManager;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class ViewAsGroup extends ActionGroup implements DumbAware {
private static final AnAction[] myChildren;
static {
final List<AnAction> children = new ArrayList<>();
for (PyNodeRenderer renderer : PyNodeRendererManager.getInstance().getAvailableRenderers()) {
children.add(new RendererAction(renderer));
}
myChildren = children.toArray(EMPTY_ARRAY);
}
private static class RendererAction extends ToggleAction {
private final PyNodeRenderer myPyNodeRenderer;
private RendererAction(PyNodeRenderer pyNodeRenderer) {
super(pyNodeRenderer.getName());
myPyNodeRenderer = pyNodeRenderer;
}
@Override
public boolean isSelected(@NotNull AnActionEvent e) {
final List<PyDebugValue> values = getSelectedValues(e);
if (values.isEmpty()) return false;
if (values.size() == 1) {
PyNodeRenderer renderer = values.get(0).getDescriptor().getRenderer();
return renderer == null ? myPyNodeRenderer instanceof DecimalRenderer : renderer == myPyNodeRenderer;
}
boolean allRenderersAreTheSame = values.stream().map(value -> value.getDescriptor().getRenderer()).distinct().count() == 1;
if (allRenderersAreTheSame) {
if (values.get(0).getDescriptor().getRenderer() == myPyNodeRenderer
|| (values.get(0).getDescriptor().getRenderer() == null && myPyNodeRenderer instanceof DecimalRenderer)) return true;
}
return false;
}
@Override
public void setSelected(@NotNull AnActionEvent e, boolean state) {
if (!state) return;
final List<XValueNodeImpl> selectedNodes = XDebuggerTreeActionBase.getSelectedNodes(e.getDataContext());
for (XValueNodeImpl node : selectedNodes) {
PyDebugValue value = (PyDebugValue) node.getValueContainer();
value.getDescriptor().setRenderer(myPyNodeRenderer);
if (value.getValue() == null) continue;
String lbl = value.getDescriptor().getRenderer() != null ? value.getDescriptor().
getRenderer().render(value.getValue()) : value.getValue();
if (lbl != null) {
node.setPresentation(node.getIcon(), value.getType(), lbl, node.getChildCount() != 0);
}
}
}
public PyNodeRenderer getRenderer() {
return myPyNodeRenderer;
}
}
public ViewAsGroup() {
super(null, true);
}
@NotNull
@Override
public AnAction[] getChildren(@Nullable AnActionEvent e) {
if (e == null) return EMPTY_ARRAY;
final List<PyDebugValue> values = getSelectedValues(e);
if (values.isEmpty()) return EMPTY_ARRAY;
for (PyDebugValue value : values) {
for (AnAction child : myChildren) {
if (!((RendererAction)child).getRenderer().isApplicable(value.getType())) return EMPTY_ARRAY;
}
}
return myChildren;
}
@Override
public boolean hideIfNoVisibleChildren() {
return true;
}
@NotNull
public static List<PyDebugValue> getSelectedValues(AnActionEvent e) {
return StreamEx.of(XDebuggerTreeActionBase.getSelectedNodes(e.getDataContext()))
.map(XValueNodeImpl::getValueContainer)
.select(PyDebugValue.class)
.toList();
}
}