PY-48235 Add quoting policy for PyXCopyValueAction

IJ-CR-110671

GitOrigin-RevId: 768a88b91943de9dcab5c5023073c82c5f8c93d7
This commit is contained in:
Egor.Eliseev
2023-07-11 13:51:10 +02:00
committed by intellij-monorepo-bot
parent 34d15a1d21
commit c368c2f4f1
13 changed files with 236 additions and 74 deletions

View File

@@ -743,6 +743,13 @@ debugger.variables.loading.on.demand.description=Load variable values on demand
debugger.variables.view.loading.timed.out=Loading timed out
debugger.variables.view.switch.to.loading.on.demand=Switch to loading on demand
debugger.variables.view.warning.message=The values of several variables couldn't be loaded
debugger.variables.view.quoting.policy=Variables Quoting Policy
debugger.variables.view.quoting.single.text=Single quotes
debugger.variables.view.quoting.single.description=Copy variable with single quotes
debugger.variables.view.quoting.double.text=Double quotes
debugger.variables.view.quoting.double.description=Copy variable with double quotes
debugger.variables.view.quoting.without.text=Without quotes
debugger.variables.view.quoting.without.description=Copy variable without quotes
debugger.exception.breakpoint.select.exception.class=Select Exception Class
debugger.watch.show.return.values=Show Return Values
debugger.data.view.connected.to.python.console=Connected to python console

View File

@@ -6,10 +6,12 @@ import com.intellij.xdebugger.impl.ui.tree.actions.XFetchValueActionBase
import com.intellij.xdebugger.impl.ui.tree.nodes.HeadlessValueEvaluationCallback
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
class PyCopyValueEvaluationCallback(node: XValueNodeImpl, private val valueCollector: XFetchValueActionBase.ValueCollector) : HeadlessValueEvaluationCallback(node) {
class PyCopyValueEvaluationCallback(node: XValueNodeImpl,
private val valueCollector: XFetchValueActionBase.ValueCollector,
private val currentPolicy: QuotingPolicy) : HeadlessValueEvaluationCallback(node) {
private val valueIndex = valueCollector.acquire()
override fun evaluationComplete(value: String, project: Project) {
valueCollector.evaluationComplete(valueIndex, value)
valueCollector.evaluationComplete(valueIndex, getQuotingString(currentPolicy, value))
}
}

View File

@@ -0,0 +1,29 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.debugger
interface AbstractPolicy
fun interface PolicyListener {
fun valuesPolicyUpdated()
}
enum class ValuesPolicy : AbstractPolicy {
SYNC,
ASYNC,
ON_DEMAND
}
enum class QuotingPolicy : AbstractPolicy {
SINGLE,
DOUBLE,
NONE
}
fun getQuotingString(policy: QuotingPolicy, value: String): String =
when (policy) {
QuotingPolicy.SINGLE -> value
QuotingPolicy.DOUBLE -> value.replace("'", "\"")
QuotingPolicy.NONE -> value.replace("'", "")
}

View File

@@ -65,10 +65,6 @@ public class PyDebugValue extends XNamedValue {
private @NotNull PyDebugValueDescriptor myDescriptor = new PyDebugValueDescriptor();
public enum ValuesPolicy {
SYNC, ASYNC, ON_DEMAND
}
private static final Map<String, ValuesPolicy> POLICY_DEFAULT_VALUES = ImmutableMap.of("__pydevd_value_async", ValuesPolicy.ASYNC,
"__pydevd_value_on_demand",
ValuesPolicy.ON_DEMAND);

View File

@@ -6,6 +6,7 @@ import com.intellij.xdebugger.frame.XValueNode;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerException;
import com.jetbrains.python.debugger.PyFrameAccessor;
import com.jetbrains.python.debugger.ValuesPolicy;
import com.jetbrains.python.debugger.pydev.PyDebugCallback;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

View File

@@ -71,9 +71,7 @@ import com.jetbrains.python.PythonPluginDisposable;
import com.jetbrains.python.console.actions.ShowCommandQueueAction;
import com.jetbrains.python.console.actions.ShowVarsAction;
import com.jetbrains.python.console.pydev.ConsoleCommunicationListener;
import com.jetbrains.python.debugger.PyDebugRunner;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyVariableViewSettings;
import com.jetbrains.python.debugger.*;
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
import com.jetbrains.python.remote.PyRemotePathMapper;
import com.jetbrains.python.remote.PyRemoteSdkAdditionalDataBase;
@@ -228,6 +226,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
settings.getTemplatePresentation().setIcon(AllIcons.General.GearPlain);
settings.add(new PyVariableViewSettings.SimplifiedView(null));
settings.add(new PyVariableViewSettings.VariablesPolicyGroup());
settings.add(new PyVariableViewSettings.QuotingPolicyGroup());
toolbarActions.add(settings);
// New console
toolbarActions.add(new NewConsoleAction());
@@ -1388,7 +1387,7 @@ public class PydevConsoleRunnerImpl implements PydevConsoleRunner {
myEnvironmentVariables = envs;
myEnvironmentVariables.putAll(consoleSettings.getEnvs());
PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
if (debuggerSettings.getValuesPolicy() != PyDebugValue.ValuesPolicy.SYNC) {
if (debuggerSettings.getValuesPolicy() != ValuesPolicy.SYNC) {
myEnvironmentVariables.put(PyDebugValue.POLICY_ENV_VARS.get(debuggerSettings.getValuesPolicy()), "True");
}
if (PyConsoleOutputCustomizer.Companion.getInstance().isInlineOutputSupported()) {

View File

@@ -517,6 +517,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
settings.add(new WatchReturnValuesAction(this));
settings.add(new PyVariableViewSettings.SimplifiedView(this));
settings.add(new PyVariableViewSettings.VariablesPolicyGroup());
settings.add(new PyVariableViewSettings.QuotingPolicyGroup());
}
private static final class WatchReturnValuesAction extends ToggleAction {

View File

@@ -643,7 +643,7 @@ public class PyDebugRunner implements ProgramRunner<RunnerSettings> {
if (debuggerSettings.isLibrariesFilterEnabled()) {
environmentController.putFixedValue(PYDEVD_FILTER_LIBRARIES, "True");
}
if (debuggerSettings.getValuesPolicy() != PyDebugValue.ValuesPolicy.SYNC) {
if (debuggerSettings.getValuesPolicy() != ValuesPolicy.SYNC) {
environmentController.putFixedValue(PyDebugValue.POLICY_ENV_VARS.get(debuggerSettings.getValuesPolicy()), "True");
}

View File

@@ -3,6 +3,7 @@ package com.jetbrains.python.debugger;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.util.NlsActions;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.xdebugger.frame.XCompositeNode;
import com.intellij.xdebugger.frame.XDebuggerTreeNodeHyperlink;
@@ -57,7 +58,7 @@ public final class PyVariableViewSettings {
public static void showWarningMessage(@Nullable final XCompositeNode node) {
if (node == null) return;
final PyDebuggerSettings debuggerSettings = PyDebuggerSettings.getInstance();
if (debuggerSettings.getValuesPolicy() == PyDebugValue.ValuesPolicy.ON_DEMAND) return;
if (debuggerSettings.getValuesPolicy() == ValuesPolicy.ON_DEMAND) return;
node.setMessage(
PyBundle.message("debugger.variables.view.warning.message"),
@@ -68,7 +69,7 @@ public final class PyVariableViewSettings {
@Override
public void onClick(MouseEvent event) {
debuggerSettings.setValuesPolicy(PyDebugValue.ValuesPolicy.ON_DEMAND);
debuggerSettings.setValuesPolicy(ValuesPolicy.ON_DEMAND);
linkClicked = true;
}
@@ -85,64 +86,100 @@ public final class PyVariableViewSettings {
});
}
public static class VariablesPolicyGroup extends DefaultActionGroup {
@NotNull private final List<PolicyAction> myValuesPolicyActions = new ArrayList<>();
private final List<ValuesPolicyListener> myValuesPolicyListeners = new ArrayList<>();
public static class VariablePolicyAction extends AbstractPolicyAction<ValuesPolicy, VariablesPolicyGroup> {
public VariablesPolicyGroup() {
super(PyBundle.message("debugger.variables.loading.policy"), true);
myValuesPolicyActions
.add(new PolicyAction(PyBundle.message("debugger.variables.loading.synchronously.text"),
PyBundle.message("debugger.variables.loading.synchronously.description"), PyDebugValue.ValuesPolicy.SYNC, this));
myValuesPolicyActions
.add(new PolicyAction(PyBundle.message("debugger.variables.loading.asynchronously.text"),
PyBundle.message("debugger.variables.loading.asynchronously.description"), PyDebugValue.ValuesPolicy.ASYNC, this));
myValuesPolicyActions
.add(new PolicyAction(PyBundle.message("debugger.variables.loading.on.demand.text"),
PyBundle.message("debugger.variables.loading.on.demand.description"), PyDebugValue.ValuesPolicy.ON_DEMAND, this));
for (AnAction action : myValuesPolicyActions) {
add(action);
}
public VariablePolicyAction(@Nls @NotNull String text,
@Nls @NotNull String description,
@NotNull ValuesPolicy policy,
@NotNull VariablesPolicyGroup actionGroup) {
super(text, description, policy, actionGroup);
}
public void updatePolicyActions() {
final PyDebugValue.ValuesPolicy currentValuesPolicy = PyDebuggerSettings.getInstance().getValuesPolicy();
for (PolicyAction action : myValuesPolicyActions) {
action.setEnabled(currentValuesPolicy == action.getPolicy());
}
}
private void notifyValuesPolicyUpdated() {
for (ValuesPolicyListener listener : myValuesPolicyListeners) {
listener.valuesPolicyUpdated();
}
}
public void addValuesPolicyListener(@NotNull ValuesPolicyListener listener) {
myValuesPolicyListeners.add(listener);
}
public interface ValuesPolicyListener {
void valuesPolicyUpdated();
@Override
protected void changeDebuggerSettings() {
PyDebuggerSettings.getInstance().setValuesPolicy(getPolicy());
}
}
public static class PolicyAction extends ToggleAction {
@NotNull private final PyDebugValue.ValuesPolicy myPolicy;
@NotNull private final VariablesPolicyGroup myActionGroup;
public static class VariablesPolicyGroup extends AbstractPolicyGroup<ValuesPolicy, VariablePolicyAction> {
public VariablesPolicyGroup() {
super(PyBundle.message("debugger.variables.loading.policy"));
addPolicyActions(new VariablePolicyAction(PyBundle.message("debugger.variables.loading.synchronously.text"),
PyBundle.message("debugger.variables.loading.synchronously.description"),
ValuesPolicy.SYNC, this),
new VariablePolicyAction(PyBundle.message("debugger.variables.loading.asynchronously.text"),
PyBundle.message("debugger.variables.loading.asynchronously.description"),
ValuesPolicy.ASYNC, this),
new VariablePolicyAction(PyBundle.message("debugger.variables.loading.on.demand.text"),
PyBundle.message("debugger.variables.loading.on.demand.description"),
ValuesPolicy.ON_DEMAND, this));
}
@Override
protected ValuesPolicy getDebuggerPolicy() {
return PyDebuggerSettings.getInstance().getValuesPolicy();
}
}
public static class QuotingPolicyAction extends AbstractPolicyAction<QuotingPolicy, QuotingPolicyGroup> {
public QuotingPolicyAction(@Nls @NotNull String text,
@Nls @NotNull String description,
@NotNull QuotingPolicy policy,
@NotNull QuotingPolicyGroup actionGroup) {
super(text, description, policy, actionGroup);
}
@Override
protected void changeDebuggerSettings() {
PyDebuggerSettings.getInstance().setQuotingPolicy(getPolicy());
}
}
public static class QuotingPolicyGroup extends AbstractPolicyGroup<QuotingPolicy, QuotingPolicyAction> {
public QuotingPolicyGroup() {
super(PyBundle.message("debugger.variables.view.quoting.policy"));
addPolicyActions(new QuotingPolicyAction(PyBundle.message("debugger.variables.view.quoting.single.text"),
PyBundle.message("debugger.variables.view.quoting.single.description"),
QuotingPolicy.SINGLE, this),
new QuotingPolicyAction(PyBundle.message("debugger.variables.view.quoting.double.text"),
PyBundle.message("debugger.variables.view.quoting.double.description"),
QuotingPolicy.DOUBLE, this),
new QuotingPolicyAction(PyBundle.message("debugger.variables.view.quoting.without.text"),
PyBundle.message("debugger.variables.view.quoting.without.description"),
QuotingPolicy.NONE, this));
}
@Override
protected QuotingPolicy getDebuggerPolicy() {
return PyDebuggerSettings.getInstance().getQuotingPolicy();
}
}
private static abstract class AbstractPolicyAction<Policy extends AbstractPolicy, PolicyGroup extends AbstractPolicyGroup<Policy, ? extends ToggleAction>>
extends ToggleAction {
@NotNull private final Policy myPolicy;
@NotNull private final PolicyGroup myActionGroup;
private volatile boolean isEnabled;
public PolicyAction(@NotNull @Nls String text,
@NotNull @Nls String description,
@NotNull PyDebugValue.ValuesPolicy policy,
@NotNull VariablesPolicyGroup actionGroup) {
private AbstractPolicyAction(@NotNull @Nls String text,
@NotNull @Nls String description,
@NotNull Policy policy,
@NotNull PolicyGroup actionGroup) {
super(text, description, null);
myPolicy = policy;
myActionGroup = actionGroup;
isEnabled = PyDebuggerSettings.getInstance().getValuesPolicy() == policy;
}
public @NotNull PolicyGroup getActionGroup() {
return myActionGroup;
}
public @NotNull Policy getPolicy() {
return myPolicy;
}
@Override
public void update(@NotNull final AnActionEvent e) {
super.update(e);
@@ -156,11 +193,6 @@ public final class PyVariableViewSettings {
return ActionUpdateThread.BGT;
}
@NotNull
public PyDebugValue.ValuesPolicy getPolicy() {
return myPolicy;
}
public void setEnabled(boolean enabled) {
isEnabled = enabled;
}
@@ -174,10 +206,48 @@ public final class PyVariableViewSettings {
public void setSelected(@NotNull AnActionEvent e, boolean hide) {
isEnabled = hide;
if (hide) {
PyDebuggerSettings.getInstance().setValuesPolicy(myPolicy);
myActionGroup.notifyValuesPolicyUpdated();
changeDebuggerSettings();
myActionGroup.notifyPolicyUpdated();
}
myActionGroup.updatePolicyActions();
}
protected abstract void changeDebuggerSettings();
}
private static abstract class AbstractPolicyGroup<Policy extends AbstractPolicy, PolicyAction extends AbstractPolicyAction<Policy, ? extends DefaultActionGroup>>
extends DefaultActionGroup {
@NotNull private final List<PolicyAction> myPolicyActions = new ArrayList<>();
private final List<PolicyListener> myPolicyListeners = new ArrayList<>();
private AbstractPolicyGroup(@NlsActions.ActionText String name) {
super(name, true);
}
public void addPolicyActions(PolicyAction... actions) {
myPolicyActions.addAll(List.of(actions));
for (AnAction action : myPolicyActions) {
add(action);
}
}
public void notifyPolicyUpdated() {
for (PolicyListener listener : myPolicyListeners) {
listener.valuesPolicyUpdated();
}
}
public void addValuesPolicyListener(@NotNull PolicyListener listener) {
myPolicyListeners.add(listener);
}
protected void updatePolicyActions() {
final AbstractPolicy currentPolicy = getDebuggerPolicy();
for (PolicyAction action : myPolicyActions) {
action.setEnabled(currentPolicy == action.getPolicy());
}
}
protected abstract AbstractPolicy getDebuggerPolicy();
}
}

View File

@@ -6,6 +6,8 @@ import com.intellij.xdebugger.impl.ui.tree.actions.XCopyValueAction
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
import com.jetbrains.python.PythonFileType
import com.jetbrains.python.debugger.PyCopyValueEvaluationCallback
import com.jetbrains.python.debugger.getQuotingString
import com.jetbrains.python.debugger.settings.PyDebuggerSettings
class PyXCopyValueAction : XCopyValueAction() {
@@ -21,11 +23,12 @@ class PyXCopyValueAction : XCopyValueAction() {
}
else {
val fullValueEvaluator = valueNode.fullValueEvaluator
val quotingPolicy = PyDebuggerSettings.getInstance().quotingPolicy
if (fullValueEvaluator != null) {
PyCopyValueEvaluationCallback(valueNode, valueCollector).startFetchingValue(fullValueEvaluator)
PyCopyValueEvaluationCallback(valueNode, valueCollector, quotingPolicy).startFetchingValue(fullValueEvaluator)
}
else {
valueCollector.add((DebuggerUIUtil.getNodeRawValue(valueNode) ?: ""))
valueCollector.add(getQuotingString(quotingPolicy, DebuggerUIUtil.getNodeRawValue(valueNode) ?: ""))
}
}
}

View File

@@ -8,7 +8,8 @@ import com.intellij.util.SmartList;
import com.intellij.util.xmlb.XmlSerializerUtil;
import com.intellij.xdebugger.settings.DebuggerSettingsCategory;
import com.intellij.xdebugger.settings.XDebuggerSettings;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.QuotingPolicy;
import com.jetbrains.python.debugger.ValuesPolicy;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
@@ -24,7 +25,8 @@ public final class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettin
public static final String FILTERS_DIVIDER = ";";
private boolean myWatchReturnValues = false;
private boolean mySimplifiedView = true;
private volatile PyDebugValue.ValuesPolicy myValuesPolicy = PyDebugValue.ValuesPolicy.ASYNC;
private volatile ValuesPolicy myValuesPolicy = ValuesPolicy.ASYNC;
private volatile QuotingPolicy myQuotingPolicy = QuotingPolicy.SINGLE;
private boolean myAlwaysDoSmartStepIntoEnabled = true;
public PyDebuggerSettings() {
@@ -48,14 +50,22 @@ public final class PyDebuggerSettings extends XDebuggerSettings<PyDebuggerSettin
mySimplifiedView = simplifiedView;
}
public PyDebugValue.ValuesPolicy getValuesPolicy() {
public ValuesPolicy getValuesPolicy() {
return myValuesPolicy;
}
public void setValuesPolicy(PyDebugValue.ValuesPolicy valuesPolicy) {
public void setValuesPolicy(ValuesPolicy valuesPolicy) {
myValuesPolicy = valuesPolicy;
}
public QuotingPolicy getQuotingPolicy() {
return myQuotingPolicy;
}
public void setQuotingPolicy(QuotingPolicy copyQuotingPolicy) {
myQuotingPolicy = copyQuotingPolicy;
}
public static PyDebuggerSettings getInstance() {
return getInstance(PyDebuggerSettings.class);
}

View File

@@ -0,0 +1,11 @@
class Car:
def __init__(self):
self.color = "green"
self.size = 23
car = Car()
some_str = "abc"
some_lst = ["a", "b", "c"]
some_dict = {"a" : 1, "b" : 2, "c" : 3}
print()

View File

@@ -13,10 +13,7 @@ import com.intellij.xdebugger.breakpoints.SuspendPolicy;
import com.intellij.xdebugger.frame.XValueChildrenList;
import com.jetbrains.env.PyEnvTestCase;
import com.jetbrains.python.console.pydev.PydevCompletionVariant;
import com.jetbrains.python.debugger.PyDebugValue;
import com.jetbrains.python.debugger.PyDebuggerException;
import com.jetbrains.python.debugger.PyExceptionBreakpointProperties;
import com.jetbrains.python.debugger.PyExceptionBreakpointType;
import com.jetbrains.python.debugger.*;
import com.jetbrains.python.debugger.pydev.ProcessDebugger;
import com.jetbrains.python.debugger.settings.PyDebuggerSettings;
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor;
@@ -31,6 +28,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import static com.jetbrains.python.debugger.PyDebugUtilsKt.getQuotingString;
import static org.junit.Assert.*;
public class PythonDebuggerTest extends PyEnvTestCase {
@@ -1708,6 +1706,41 @@ public class PythonDebuggerTest extends PyEnvTestCase {
});
}
@Test
public void testQuotingInCopyAction() {
runPythonTest(new PyDebuggerTask("/debug", "test_quoting_value.py") {
private void testQuotingValue(String value) throws PyDebuggerException {
var variable = myDebugProcess.evaluate(value, false, false).getValue();
for (var policy : QuotingPolicy.values()) {
String result = getQuotingString(policy, variable);
switch (policy) {
case DOUBLE -> assertFalse(result.contains("'"));
case SINGLE -> assertFalse(result.contains("\""));
case NONE -> {
assertFalse(result.contains("'"));
assertFalse(result.contains("\""));
}
}
}
}
@Override
public void before() {
toggleBreakpoint(getFilePath(getScriptName()), 10);
}
@Override
public void testing() throws Exception {
waitForPause();
testQuotingValue("car");
testQuotingValue("some_str");
testQuotingValue("some_lst");
testQuotingValue("some_dict");
resume();
}
});
}
private static class PyDebuggerTaskTagAware extends PyDebuggerTask {
private PyDebuggerTaskTagAware(@Nullable String relativeTestDataPath, String scriptName) {