@@ -138,6 +138,20 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
138
138
/// have been called.
139
139
late final bool isAttach;
140
140
141
+ /// A list of evaluateNames for InstanceRef IDs.
142
+ ///
143
+ /// When providing variables for fields/getters or items in maps/arrays, we
144
+ /// need to provide an expression to the client that evaluates to that
145
+ /// variable so that functionality like "Add to Watch" or "Copy Value" can
146
+ /// work. For example, if a user expands a list named `myList` then the 1st
147
+ /// [Variable] returned should have an evaluateName of `myList[0]` . The `foo`
148
+ /// getter of that object would then have an evaluateName of `myList[0].foo` .
149
+ ///
150
+ /// Since those expressions aren't round-tripped as child variables are
151
+ /// requested we build them up as we send variables out, so we can append to
152
+ /// them when returning elements/map entries/fields/getters.
153
+ final _evaluateNamesForInstanceRefIds = < String , String > {};
154
+
141
155
/// A list of all possible project paths that should be considered the users
142
156
/// own code.
143
157
///
@@ -219,6 +233,26 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
219
233
// sendResponse();
220
234
}
221
235
236
+ /// Builds an evaluateName given a parent VM InstanceRef ID and a suffix.
237
+ ///
238
+ /// If [parentInstanceRefId] is `null` , or we have no evaluateName for it,
239
+ /// will return null.
240
+ String ? buildEvaluateName (
241
+ String suffix, {
242
+ required String ? parentInstanceRefId,
243
+ }) {
244
+ final parentEvaluateName =
245
+ _evaluateNamesForInstanceRefIds[parentInstanceRefId];
246
+ return combineEvaluateName (parentEvaluateName, suffix);
247
+ }
248
+
249
+ /// Builds an evaluateName given a prefix and a suffix.
250
+ ///
251
+ /// If [prefix] is null, will return be null.
252
+ String ? combineEvaluateName (String ? prefix, String suffix) {
253
+ return prefix != null ? '$prefix $suffix ' : null ;
254
+ }
255
+
222
256
/// configurationDone is called by the client when it has finished sending
223
257
/// any initial configuration (such as breakpoints and exception pause
224
258
/// settings).
@@ -489,11 +523,14 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
489
523
result,
490
524
allowCallingToString: evaluateToStringInDebugViews,
491
525
);
492
- // TODO(dantup): We may need to store `expression` with this data
493
- // to allow building nested evaluateNames.
526
+
494
527
final variablesReference =
495
528
_converter.isSimpleKind (result.kind) ? 0 : thread.storeData (result);
496
529
530
+ // Store the expression that gets this object as we may need it to
531
+ // compute evaluateNames for child objects later.
532
+ storeEvaluateName (result, expression);
533
+
497
534
sendResponse (EvaluateResponseBody (
498
535
result: resultString,
499
536
variablesReference: variablesReference,
@@ -663,7 +700,7 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
663
700
// For local variables, we can just reuse the frameId as variablesReference
664
701
// as variablesRequest handles stored data of type `Frame` directly.
665
702
scopes.add (Scope (
666
- name: 'Variables ' ,
703
+ name: 'Locals ' ,
667
704
presentationHint: 'locals' ,
668
705
variablesReference: args.frameId,
669
706
expensive: false ,
@@ -943,6 +980,14 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
943
980
sendResponse ();
944
981
}
945
982
983
+ /// Stores [evaluateName] as the expression that can be evaluated to get
984
+ /// [instanceRef] .
985
+ void storeEvaluateName (vm.InstanceRef instanceRef, String ? evaluateName) {
986
+ if (evaluateName != null ) {
987
+ _evaluateNamesForInstanceRefIds[instanceRef.id! ] = evaluateName;
988
+ }
989
+ }
990
+
946
991
/// Overridden by sub-classes to handle when the client sends a
947
992
/// `terminateRequest` (a request for a graceful shut down).
948
993
Future <void > terminateImpl ();
@@ -1032,19 +1077,55 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
1032
1077
final vars = vmData.vars;
1033
1078
if (vars != null ) {
1034
1079
Future <Variable > convert (int index, vm.BoundVariable variable) {
1080
+ // Store the expression that gets this object as we may need it to
1081
+ // compute evaluateNames for child objects later.
1082
+ storeEvaluateName (variable.value, variable.name);
1035
1083
return _converter.convertVmResponseToVariable (
1036
1084
thread,
1037
1085
variable.value,
1038
1086
name: variable.name,
1039
1087
allowCallingToString: evaluateToStringInDebugViews &&
1040
1088
index <= maxToStringsPerEvaluation,
1089
+ evaluateName: variable.name,
1041
1090
);
1042
1091
}
1043
1092
1044
1093
variables.addAll (await Future .wait (vars.mapIndexed (convert)));
1094
+
1095
+ // Sort the variables by name.
1096
+ variables.sortBy ((v) => v.name);
1097
+ }
1098
+ } else if (data is vm.MapAssociation ) {
1099
+ final key = data.key;
1100
+ final value = data.value;
1101
+ if (key is vm.InstanceRef && value is vm.InstanceRef ) {
1102
+ // For a MapAssociation, we create a dummy set of variables for "key" and
1103
+ // "value" so that each may be expanded if they are complex values.
1104
+ variables.addAll ([
1105
+ Variable (
1106
+ name: 'key' ,
1107
+ value: await _converter.convertVmInstanceRefToDisplayString (
1108
+ thread,
1109
+ key,
1110
+ allowCallingToString: evaluateToStringInDebugViews,
1111
+ ),
1112
+ variablesReference:
1113
+ _converter.isSimpleKind (key.kind) ? 0 : thread.storeData (key),
1114
+ ),
1115
+ Variable (
1116
+ name: 'value' ,
1117
+ value: await _converter.convertVmInstanceRefToDisplayString (
1118
+ thread,
1119
+ value,
1120
+ allowCallingToString: evaluateToStringInDebugViews,
1121
+ ),
1122
+ variablesReference: _converter.isSimpleKind (value.kind)
1123
+ ? 0
1124
+ : thread.storeData (value),
1125
+ evaluateName:
1126
+ buildEvaluateName ('' , parentInstanceRefId: value.id)),
1127
+ ]);
1045
1128
}
1046
- } else if (vmData is vm.MapAssociation ) {
1047
- // TODO(dantup): Maps
1048
1129
} else if (vmData is vm.ObjRef ) {
1049
1130
final object =
1050
1131
await _isolateManager.getObject (storedData.thread.isolate, vmData);
@@ -1056,13 +1137,10 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
1056
1137
variablesReference: 0 ,
1057
1138
));
1058
1139
} else if (object is vm.Instance ) {
1059
- // TODO(dantup): evaluateName
1060
- // should be built taking the parent into account, for ex. if
1061
- // args.variablesReference == thread.exceptionReference then we need to
1062
- // use some sythensized variable name like `frameExceptionExpression`.
1063
1140
variables.addAll (await _converter.convertVmInstanceToVariablesList (
1064
1141
thread,
1065
1142
object,
1143
+ evaluateName: buildEvaluateName ('' , parentInstanceRefId: vmData.id),
1066
1144
allowCallingToString: evaluateToStringInDebugViews,
1067
1145
startItem: childStart,
1068
1146
numItems: childCount,
@@ -1076,8 +1154,6 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
1076
1154
}
1077
1155
}
1078
1156
1079
- variables.sortBy ((v) => v.name);
1080
-
1081
1157
sendResponse (VariablesResponseBody (variables: variables));
1082
1158
}
1083
1159
@@ -1172,6 +1248,7 @@ abstract class DartDebugAdapter<T extends DartLaunchRequestArguments>
1172
1248
// string they logged regardless of the evaluateToStringInDebugViews
1173
1249
// setting.
1174
1250
allowCallingToString: true ,
1251
+ allowTruncatedValue: false ,
1175
1252
includeQuotesAroundString: false ,
1176
1253
);
1177
1254
}
0 commit comments