Skip to content

Commit 4d26146

Browse files
Support copy variable to clipboard
Signed-off-by: Jinbo Wang <jinbwan@microsoft.com>
1 parent 73a189f commit 4d26146

File tree

9 files changed

+184
-36
lines changed

9 files changed

+184
-36
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/EvaluateRequestHandler.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,13 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
120120
detailsString = VariableDetailUtils.formatDetailsValue(value, stackFrameReference.getThread(), variableFormatter, options, engine);
121121
}
122122

123-
response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString,
124-
referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options),
125-
Math.max(indexedVariables, 0));
123+
if ("clipboard".equals(evalArguments.context) && detailsString != null) {
124+
response.body = new Responses.EvaluateResponseBody(detailsString, -1, "String", 0);
125+
} else {
126+
response.body = new Responses.EvaluateResponseBody((detailsString == null) ? valueString : valueString + " " + detailsString,
127+
referenceId, variableFormatter.typeToString(value == null ? null : value.type(), options),
128+
Math.max(indexedVariables, 0));
129+
}
126130
return response;
127131
}
128132
// for primitive value

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/InitializeRequestHandler.java

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public CompletableFuture<Messages.Response> handle(Requests.Command command, Req
6262
caps.exceptionBreakpointFilters = exceptionFilters;
6363
caps.supportsExceptionInfoRequest = true;
6464
caps.supportsDataBreakpoints = true;
65+
caps.supportsClipboardContext = true;
6566
response.body = caps;
6667
return CompletableFuture.completedFuture(response);
6768
}

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/handler/VariablesRequestHandler.java

+35-10
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
9898
VariableProxy containerNode = (VariableProxy) container;
9999
List<Variable> childrenList = new ArrayList<>();
100100
IStackFrameManager stackFrameManager = context.getStackFrameManager();
101+
String containerEvaluateName = containerNode.getEvaluateName();
102+
boolean isUnboundedTypeContainer = containerNode.isUnboundedType();
101103
if (containerNode.getProxiedVariable() instanceof StackFrameReference) {
102104
StackFrameReference stackFrameReference = (StackFrameReference) containerNode.getProxiedVariable();
103105
StackFrame frame = stackFrameManager.getStackFrame(stackFrameReference);
@@ -111,7 +113,7 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
111113
JdiMethodResult result = context.getStepResultManager().getMethodResult(threadId);
112114
if (result != null) {
113115
String returnIcon = (AdapterUtils.isWin || AdapterUtils.isMac) ? "⎯►" : "->";
114-
childrenList.add(new Variable(returnIcon + result.method.name() + "()", result.value));
116+
childrenList.add(new Variable(returnIcon + result.method.name() + "()", result.value, null));
115117
}
116118
childrenList.addAll(VariableUtils.listLocalVariables(frame));
117119
Variable thisVariable = VariableUtils.getThisVariable(frame);
@@ -132,11 +134,17 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
132134
ObjectReference containerObj = (ObjectReference) containerNode.getProxiedVariable();
133135
if (DebugSettings.getCurrent().showLogicalStructure && evaluationEngine != null) {
134136
JavaLogicalStructure logicalStructure = JavaLogicalStructureManager.getLogicalStructure(containerObj);
137+
if (isUnboundedTypeContainer && logicalStructure != null && containerEvaluateName != null) {
138+
containerEvaluateName = "((" + logicalStructure.getFullyQualifiedName() + ")" + containerEvaluateName + ")";
139+
isUnboundedTypeContainer = false;
140+
}
135141
while (logicalStructure != null) {
136142
LogicalStructureExpression valueExpression = logicalStructure.getValueExpression();
137143
LogicalVariable[] logicalVariables = logicalStructure.getVariables();
138144
try {
139145
if (valueExpression != null) {
146+
containerEvaluateName = containerEvaluateName == null ? null : containerEvaluateName + "." + valueExpression.evaluateName;
147+
isUnboundedTypeContainer = valueExpression.returnUnboundedType;
140148
Value value = logicalStructure.getValue(containerObj, containerNode.getThread(), evaluationEngine);
141149
if (value instanceof ObjectReference) {
142150
containerObj = (ObjectReference) value;
@@ -149,7 +157,9 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
149157
for (LogicalVariable logicalVariable : logicalVariables) {
150158
String name = logicalVariable.getName();
151159
Value value = logicalVariable.getValue(containerObj, containerNode.getThread(), evaluationEngine);
152-
childrenList.add(new Variable(name, value));
160+
Variable variable = new Variable(name, value, logicalVariable.getEvaluateName());
161+
variable.setUnboundedType(logicalVariable.returnUnboundedType());
162+
childrenList.add(variable);
153163
}
154164
}
155165
} catch (IllegalArgumentException | CancellationException | InterruptedException | ExecutionException e) {
@@ -236,17 +246,32 @@ public CompletableFuture<Response> handle(Command command, Arguments arguments,
236246
}
237247
}
238248

239-
int referenceId = 0;
240-
241-
boolean containerIsArray = containerNode.getProxiedVariable() instanceof ArrayReference;
242-
String evaluateName;
249+
String evaluateName = null;
250+
if (javaVariable.evaluateName == null || (containerEvaluateName == null && containerNode.getProxiedVariable() instanceof ObjectReference)) {
251+
// Disable evaluate on the method return value.
252+
evaluateName = null;
253+
} else if (isUnboundedTypeContainer && !containerNode.isIndexedVariable()) {
254+
// The type name returned by JDI is the binary name, which uses '$' as the separator of
255+
// inner class e.g. Foo$Bar. But the evaluation expression only accepts using '.' as the class
256+
// name separator.
257+
String typeName = ((ObjectReference) containerNode.getProxiedVariable()).referenceType().name();
258+
// TODO: This replacement will possibly change the $ in the class name itself.
259+
typeName = typeName.replaceAll("\\$", ".");
260+
evaluateName = VariableUtils.getEvaluateName(javaVariable.evaluateName, "((" + typeName + ")" + containerEvaluateName + ")", false);
261+
} else {
262+
if (containerEvaluateName != null && containerEvaluateName.contains("%s")) {
263+
evaluateName = String.format(containerEvaluateName, javaVariable.evaluateName);
264+
} else {
265+
evaluateName = VariableUtils.getEvaluateName(javaVariable.evaluateName, containerEvaluateName, containerNode.isIndexedVariable());
266+
}
267+
}
243268

269+
int referenceId = 0;
244270
if (indexedVariables > 0 || (indexedVariables < 0 && VariableUtils.hasChildren(value, showStaticVariables))) {
245-
VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, javaVariable.name);
271+
VariableProxy varProxy = new VariableProxy(containerNode.getThread(), containerNode.getScope(), value, containerNode, evaluateName);
246272
referenceId = context.getRecyclableIdPool().addObject(containerNode.getThreadId(), varProxy);
247-
evaluateName = varProxy.getEvaluateName();
248-
} else {
249-
evaluateName = VariableUtils.getEvaluateName(javaVariable.name, containerNode.getEvaluateName(), containerIsArray);
273+
varProxy.setIndexedVariable(indexedVariables >= 0);
274+
varProxy.setUnboundedType(javaVariable.isUnboundedType());
250275
}
251276

252277
Types.Variable typedVariables = new Types.Variable(name, variableFormatter.valueToString(value, options),

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructure.java

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2019 Microsoft Corporation and others.
2+
* Copyright (c) 2019-2020 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -26,7 +26,10 @@
2626
import com.sun.jdi.Value;
2727

2828
public class JavaLogicalStructure {
29+
// The binary type name. For inner type, the binary name uses '$' as the separator, e.g. java.util.Map$Entry.
2930
private final String type;
31+
// The fully qualified name, which uses '.' as the separator, e.g. java.util.Map.Entry.
32+
private final String fullyQualifiedName;
3033
private final LogicalStructureExpression valueExpression;
3134
private final LogicalStructureExpression sizeExpression;
3235
private final LogicalVariable[] variables;
@@ -36,8 +39,17 @@ public class JavaLogicalStructure {
3639
*/
3740
public JavaLogicalStructure(String type, LogicalStructureExpression valueExpression, LogicalStructureExpression sizeExpression,
3841
LogicalVariable[] variables) {
42+
this(type, type, valueExpression, sizeExpression, variables);
43+
}
44+
45+
/**
46+
* Constructor.
47+
*/
48+
public JavaLogicalStructure(String type, String fullyQualifiedName, LogicalStructureExpression valueExpression, LogicalStructureExpression sizeExpression,
49+
LogicalVariable[] variables) {
3950
this.valueExpression = valueExpression;
4051
this.type = type;
52+
this.fullyQualifiedName = fullyQualifiedName;
4153
this.sizeExpression = sizeExpression;
4254
this.variables = variables;
4355
}
@@ -46,6 +58,10 @@ public String getType() {
4658
return type;
4759
}
4860

61+
public String getFullyQualifiedName() {
62+
return fullyQualifiedName;
63+
}
64+
4965
public LogicalStructureExpression getValueExpression() {
5066
return valueExpression;
5167
}
@@ -152,18 +168,50 @@ public Value getValue(ObjectReference thisObject, ThreadReference thread, IEvalu
152168
throws CancellationException, IllegalArgumentException, InterruptedException, ExecutionException {
153169
return JavaLogicalStructure.getValue(thisObject, valueExpression, thread, evaluationEngine);
154170
}
171+
172+
public String getEvaluateName() {
173+
if (valueExpression == null || valueExpression.evaluateName == null) {
174+
return name;
175+
}
176+
177+
return valueExpression.evaluateName;
178+
}
179+
180+
public boolean returnUnboundedType() {
181+
return valueExpression != null && valueExpression.returnUnboundedType;
182+
}
155183
}
156184

157185
public static class LogicalStructureExpression {
158186
public LogicalStructureExpressionType type;
159187
public String[] value;
188+
public String evaluateName;
189+
public boolean returnUnboundedType = false;
160190

161191
/**
162192
* Constructor.
163193
*/
164194
public LogicalStructureExpression(LogicalStructureExpressionType type, String[] value) {
195+
this(type, value, null);
196+
}
197+
198+
/**
199+
* Constructor.
200+
*/
201+
public LogicalStructureExpression(LogicalStructureExpressionType type, String[] value, String evaluateName) {
202+
this.type = type;
203+
this.value = value;
204+
this.evaluateName = evaluateName;
205+
}
206+
207+
/**
208+
* Constructor.
209+
*/
210+
public LogicalStructureExpression(LogicalStructureExpressionType type, String[] value, String evaluateName, boolean returnUnboundedType) {
165211
this.type = type;
166212
this.value = value;
213+
this.evaluateName = evaluateName;
214+
this.returnUnboundedType = returnUnboundedType;
167215
}
168216
}
169217

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/JavaLogicalStructureManager.java

+24-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2019 Microsoft Corporation and others.
2+
* Copyright (c) 2019-2020 Microsoft Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License v1.0
55
* which accompanies this distribution, and is available at
@@ -30,18 +30,30 @@ public class JavaLogicalStructureManager {
3030

3131
static {
3232
supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Map",
33-
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"entrySet", "()Ljava/util/Set;"}),
34-
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}),
35-
new LogicalVariable[0]));
36-
supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Map$Entry", null, null, new LogicalVariable[] {
37-
new LogicalVariable("key", new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"getKey", "()Ljava/lang/Object;"})),
38-
new LogicalVariable("value",
39-
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"getValue", "()Ljava/lang/Object;"}))
40-
}));
33+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"entrySet", "()Ljava/util/Set;"}, "entrySet()"),
34+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}),
35+
new LogicalVariable[0]
36+
));
37+
supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Map$Entry", "java.util.Map.Entry", null, null,
38+
new LogicalVariable[] {
39+
new LogicalVariable("key",
40+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"getKey", "()Ljava/lang/Object;"}, "getKey()", true)
41+
),
42+
new LogicalVariable("value",
43+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD,
44+
new String[] {"getValue", "()Ljava/lang/Object;"}, "getValue()", true)
45+
)}
46+
));
47+
supportedLogicalStructures.add(new JavaLogicalStructure("java.util.List",
48+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"toArray", "()[Ljava/lang/Object;"}, "get(%s)", true),
49+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}),
50+
new LogicalVariable[0]
51+
));
4152
supportedLogicalStructures.add(new JavaLogicalStructure("java.util.Collection",
42-
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"toArray", "()[Ljava/lang/Object;"}),
43-
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}),
44-
new LogicalVariable[0]));
53+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"toArray", "()[Ljava/lang/Object;"}, "toArray()", true),
54+
new LogicalStructureExpression(LogicalStructureExpressionType.METHOD, new String[] {"size", "()I"}),
55+
new LogicalVariable[0]
56+
));
4557
}
4658

4759
/**

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/variables/Variable.java

+35
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313

1414
import org.apache.commons.lang3.StringUtils;
1515

16+
import java.util.Objects;
17+
1618
import com.sun.jdi.Field;
1719
import com.sun.jdi.LocalVariable;
1820
import com.sun.jdi.Type;
@@ -61,18 +63,39 @@ public class Variable {
6163
*/
6264
public int argumentIndex;
6365

66+
/**
67+
* The variable evaluate name for the container context. Defaults to the variable name.
68+
*/
69+
public String evaluateName;
70+
71+
/**
72+
* Indicates whether this variable's type is determined at runtime.
73+
*/
74+
private boolean isUnboundedType = false;
75+
6476
/**
6577
* The constructor of <code>JavaVariable</code>.
6678
* @param name the name of this variable.
6779
* @param value the JDI value
6880
*/
6981
public Variable(String name, Value value) {
82+
this(name, value, name);
83+
}
84+
85+
/**
86+
* The constructor of <code>JavaVariable</code>.
87+
* @param name the name of this variable.
88+
* @param value the JDI value
89+
* @param evaluateName the variable evaluate name for the container context if any
90+
*/
91+
public Variable(String name, Value value, String evaluateName) {
7092
if (StringUtils.isBlank(name)) {
7193
throw new IllegalArgumentException("Name is required for a java variable.");
7294
}
7395
this.name = name;
7496
this.value = value;
7597
this.argumentIndex = -1;
98+
this.evaluateName = evaluateName;
7699
}
77100

78101
/**
@@ -86,4 +109,16 @@ public Type getDeclaringType() {
86109
}
87110
return null;
88111
}
112+
113+
public void setUnboundedType(boolean isUnboundedType) {
114+
this.isUnboundedType = isUnboundedType;
115+
}
116+
117+
public boolean isUnboundedType() {
118+
if (isUnboundedType) {
119+
return true;
120+
}
121+
122+
return field != null && Objects.equals(field.signature(), "Ljava/lang/Object;");
123+
}
89124
}

0 commit comments

Comments
 (0)