Skip to content

Commit 1ff357f

Browse files
DanTupCommit Queue
authored and
Commit Queue
committed
[dds/dap] Tidy up variables formatting and allow "format specifiers" in evaluation expressions
This adds some new classes to support different formatting of values in expression evaluation/watch/variables panes. Some existing code that passed a flag around to suppress quotes has been converted to this use, and in addition, we support some C#-inspired "format specifiers" that allow you to influence the formatting of returned values by adding a suffix to a watch/evaluation expression: https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/e514eeby(v=vs.100)?redirectedfrom=MSDN So if you add "foo" and "foo,nq" to your watch window, the latter will format the string without quotes. If you add "a" and "a,h" then the latter will format a in hex. Format specifiers are carried down, so you can add ",h" to a class or list variable, and each value down the tree will be formatted that way. Partly fixes Dart-Code/Dart-Code#3940. Change-Id: I78001bd046ee2586bd828752f9ea08da01e7180c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/279961 Commit-Queue: Ben Konyi <[email protected]> Reviewed-by: Ben Konyi <[email protected]>
1 parent a64b34d commit 1ff357f

File tree

6 files changed

+298
-62
lines changed

6 files changed

+298
-62
lines changed

pkg/dds/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# 2.7.3
22
- [DAP] Added support for displaying records in responses to `variablesRequest`.
33
- A new exception `ExistingDartDevelopmentServiceException` (extending `DartDevelopmentServiceException`) is thrown when trying to connect DDS to a VM Service that already has a DDS instance. This new exception contains a `ddsUri` field that is populated with the URI of the existing DDS instance if provided by the target VM Service.
4+
- [DAP] Added support for `,d` (decimal), `,h` (hex) and `,nq` (no quotes) format specifiers to be used as suffixes to evaluation requests.
45

56
# 2.7.2
67
- Update DDS protocol version to 1.4.

pkg/dds/lib/src/dap/adapters/dart.dart

+44-23
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import '../protocol_converter.dart';
2424
import '../protocol_generated.dart';
2525
import '../protocol_stream.dart';
2626
import '../utils.dart';
27+
import '../variables.dart';
2728
import 'mixins.dart';
2829

2930
/// The mime type to send with source responses to the client.
@@ -915,18 +916,24 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
915916
throw UnimplementedError('Global evaluation not currently supported');
916917
}
917918

919+
// Parse the expression for trailing format specifiers.
920+
final expressionData = EvaluationExpression.parse(
921+
args.expression
922+
.trim()
923+
// Remove any trailing semicolon as the VM only evaluates expressions
924+
// but a user may have highlighted a whole line/statement to send for
925+
// evaluation.
926+
.replaceFirst(_trailingSemicolonPattern, ''),
927+
);
928+
final expression = expressionData.expression;
929+
final format = expressionData.format;
930+
931+
final exceptionReference = thread.exceptionReference;
918932
// The value in the constant `frameExceptionExpression` is used as a special
919933
// expression that evaluates to the exception on the current thread. This
920934
// allows us to construct evaluateNames that evaluate to the fields down the
921935
// tree to support some of the debugger functionality (for example
922936
// "Copy Value", which re-evaluates).
923-
final expression = args.expression
924-
.trim()
925-
// Remove any trailing semicolon as the VM only evaluates expressions
926-
// but a user may have highlighted a whole line/statement to send for
927-
// evaluation.
928-
.replaceFirst(_trailingSemicolonPattern, '');
929-
final exceptionReference = thread.exceptionReference;
930937
final isExceptionExpression = expression == threadExceptionExpression ||
931938
expression.startsWith('$threadExceptionExpression.');
932939

@@ -974,10 +981,12 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
974981
thread,
975982
result,
976983
allowCallingToString: evaluateToStringInDebugViews,
984+
format: format,
977985
);
978986

979-
final variablesReference =
980-
_converter.isSimpleKind(result.kind) ? 0 : thread.storeData(result);
987+
final variablesReference = _converter.isSimpleKind(result.kind)
988+
? 0
989+
: thread.storeData(VariableData(result, format));
981990

982991
// Store the expression that gets this object as we may need it to
983992
// compute evaluateNames for child objects later.
@@ -1662,12 +1671,19 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
16621671
throw StateError('variablesReference is no longer valid');
16631672
}
16641673
final thread = storedData.thread;
1665-
final data = storedData.data;
1666-
final vmData = data is vm.Response ? data : null;
1674+
var data = storedData.data;
1675+
1676+
VariableFormat? format;
1677+
// Unwrap any variable we stored with formatting info.
1678+
if (data is VariableData) {
1679+
format = data.format;
1680+
data = data.data;
1681+
}
1682+
16671683
final variables = <Variable>[];
16681684

1669-
if (vmData is vm.Frame) {
1670-
final vars = vmData.vars;
1685+
if (data is vm.Frame) {
1686+
final vars = data.vars;
16711687
if (vars != null) {
16721688
Future<Variable> convert(int index, vm.BoundVariable variable) {
16731689
// Store the expression that gets this object as we may need it to
@@ -1680,6 +1696,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
16801696
allowCallingToString: evaluateToStringInDebugViews &&
16811697
index <= maxToStringsPerEvaluation,
16821698
evaluateName: variable.name,
1699+
format: format,
16831700
);
16841701
}
16851702

@@ -1701,28 +1718,31 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
17011718
thread,
17021719
key,
17031720
allowCallingToString: evaluateToStringInDebugViews,
1721+
format: format,
17041722
),
1705-
variablesReference:
1706-
_converter.isSimpleKind(key.kind) ? 0 : thread.storeData(key),
1723+
variablesReference: _converter.isSimpleKind(key.kind)
1724+
? 0
1725+
: thread.storeData(VariableData(key, format)),
17071726
),
17081727
Variable(
17091728
name: 'value',
17101729
value: await _converter.convertVmInstanceRefToDisplayString(
17111730
thread,
17121731
value,
17131732
allowCallingToString: evaluateToStringInDebugViews,
1733+
format: format,
17141734
),
17151735
variablesReference: _converter.isSimpleKind(value.kind)
17161736
? 0
1717-
: thread.storeData(value),
1737+
: thread.storeData(VariableData(value, format)),
17181738
evaluateName:
17191739
buildEvaluateName('', parentInstanceRefId: value.id)),
17201740
]);
17211741
}
1722-
} else if (vmData is vm.ObjRef) {
1742+
} else if (data is vm.ObjRef) {
17231743
final object = await _isolateManager.getObject(
17241744
storedData.thread.isolate,
1725-
vmData,
1745+
data,
17261746
offset: childStart,
17271747
count: childCount,
17281748
);
@@ -1737,10 +1757,11 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
17371757
variables.addAll(await _converter.convertVmInstanceToVariablesList(
17381758
thread,
17391759
object,
1740-
evaluateName: buildEvaluateName('', parentInstanceRefId: vmData.id),
1760+
evaluateName: buildEvaluateName('', parentInstanceRefId: data.id),
17411761
allowCallingToString: evaluateToStringInDebugViews,
17421762
startItem: childStart,
17431763
numItems: childCount,
1764+
format: format,
17441765
));
17451766
} else {
17461767
variables.add(Variable(
@@ -1976,9 +1997,9 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
19761997
}
19771998
}
19781999

1979-
/// Helper to convert to InstanceRef to a complete untruncated String,
1980-
/// handling [vm.InstanceKind.kNull] which is the type for the unused fields
1981-
/// of a log event.
2000+
/// Helper to convert to InstanceRef to a complete untruncated unquoted
2001+
/// String, handling [vm.InstanceKind.kNull] which is the type for the unused
2002+
/// fields of a log event.
19822003
Future<String?> getFullString(ThreadInfo thread, vm.InstanceRef? ref) async {
19832004
if (ref == null || ref.kind == vm.InstanceKind.kNull) {
19842005
return null;
@@ -1992,7 +2013,7 @@ abstract class DartDebugAdapter<TL extends LaunchRequestArguments,
19922013
// setting.
19932014
allowCallingToString: true,
19942015
allowTruncatedValue: false,
1995-
includeQuotesAroundString: false,
2016+
format: VariableFormat.noQuotes(),
19962017
)
19972018
// Fetching strings from the server may throw if they have been
19982019
// collected since (for example if a Hot Restart occurs while

pkg/dds/lib/src/dap/protocol_converter.dart

+58-39
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import 'package:collection/collection.dart';
1010
import 'package:path/path.dart' as path;
1111
import 'package:vm_service/vm_service.dart' as vm;
1212

13+
import '../../dap.dart';
1314
import 'adapters/dart.dart';
1415
import 'isolate_manager.dart';
1516
import 'protocol_generated.dart' as dap;
17+
import 'variables.dart';
1618

1719
/// A helper that handlers converting to/from DAP and VM Service types and to
1820
/// user-friendly display strings.
@@ -57,43 +59,42 @@ class ProtocolConverter {
5759
vm.InstanceRef ref, {
5860
required bool allowCallingToString,
5961
bool allowTruncatedValue = true,
60-
bool includeQuotesAroundString = true,
62+
VariableFormat? format,
6163
}) async {
6264
final isTruncated = ref.valueAsStringIsTruncated ?? false;
65+
final valueAsString = ref.valueAsString;
66+
final formatter = format ?? const VariableFormat();
6367
if (ref.kind == vm.InstanceKind.kString && isTruncated) {
6468
// Call toString() if allowed (and we don't already have a value),
6569
// otherwise (or if it returns null) fall back to the truncated value
6670
// with "…" suffix.
6771
var stringValue = allowCallingToString &&
68-
(ref.valueAsString == null || !allowTruncatedValue)
69-
? await _callToString(
70-
thread,
71-
ref,
72+
(valueAsString == null || !allowTruncatedValue)
73+
? await _callToString(thread, ref,
7274
// Quotes are handled below, so they can be wrapped around the
7375
// ellipsis.
74-
includeQuotesAroundString: false,
75-
)
76+
format: VariableFormat.noQuotes())
7677
: null;
77-
stringValue ??= '${ref.valueAsString}…';
78+
stringValue ??= '$valueAsString…';
7879

79-
return includeQuotesAroundString ? '"$stringValue"' : stringValue;
80+
return formatter.formatString(stringValue);
8081
} else if (ref.kind == vm.InstanceKind.kString) {
8182
// Untruncated strings.
82-
return includeQuotesAroundString
83-
? '"${ref.valueAsString}"'
84-
: ref.valueAsString.toString();
85-
} else if (ref.valueAsString != null) {
86-
return isTruncated
87-
? '${ref.valueAsString}…'
88-
: ref.valueAsString.toString();
83+
return formatter.formatString(valueAsString ?? "");
84+
} else if (valueAsString != null) {
85+
if (isTruncated) {
86+
return '$valueAsString…';
87+
} else if (ref.kind == vm.InstanceKind.kInt) {
88+
return formatter.formatInt(int.tryParse(valueAsString));
89+
} else {
90+
return valueAsString.toString();
91+
}
8992
} else if (ref.kind == 'PlainInstance') {
9093
var stringValue = ref.classRef?.name ?? '<unknown instance>';
9194
if (allowCallingToString) {
92-
final toStringValue = await _callToString(
93-
thread,
94-
ref,
95-
includeQuotesAroundString: false,
96-
);
95+
final toStringValue = await _callToString(thread, ref,
96+
// Suppress quotes because this is going inside a longer string.
97+
format: VariableFormat.noQuotes());
9798
// Include the toString() result only if it's not the default (which
9899
// duplicates the type name we're already showing).
99100
if (toStringValue != "Instance of '${ref.classRef?.name}'") {
@@ -125,6 +126,7 @@ class ProtocolConverter {
125126
required bool allowCallingToString,
126127
int? startItem = 0,
127128
int? numItems,
129+
VariableFormat? format,
128130
}) async {
129131
final elements = instance.elements;
130132
final associations = instance.associations;
@@ -139,6 +141,7 @@ class ProtocolConverter {
139141
name: null,
140142
evaluateName: evaluateName,
141143
allowCallingToString: allowCallingToString,
144+
format: format,
142145
)
143146
];
144147
} else if (elements != null) {
@@ -153,6 +156,7 @@ class ProtocolConverter {
153156
evaluateName, '[${start + index}]'),
154157
allowCallingToString:
155158
allowCallingToString && index <= maxToStringsPerEvaluation,
159+
format: format,
156160
),
157161
));
158162
} else if (associations != null) {
@@ -168,11 +172,18 @@ class ProtocolConverter {
168172
final callToString =
169173
allowCallingToString && index <= maxToStringsPerEvaluation;
170174

171-
final keyDisplay = await convertVmResponseToDisplayString(thread, key,
172-
allowCallingToString: callToString);
175+
final keyDisplay = await convertVmResponseToDisplayString(
176+
thread,
177+
key,
178+
allowCallingToString: callToString,
179+
format: format,
180+
);
173181
final valueDisplay = await convertVmResponseToDisplayString(
174-
thread, value,
175-
allowCallingToString: callToString);
182+
thread,
183+
value,
184+
allowCallingToString: callToString,
185+
format: format,
186+
);
176187

177188
// We only provide an evaluateName for the value, and only if the
178189
// key is a simple value.
@@ -186,7 +197,7 @@ class ProtocolConverter {
186197
return dap.Variable(
187198
name: '${start + index}',
188199
value: '$keyDisplay -> $valueDisplay',
189-
variablesReference: thread.storeData(mapEntry),
200+
variablesReference: thread.storeData(VariableData(mapEntry, format)),
190201
);
191202
}));
192203
} else if (_isList(instance) &&
@@ -216,13 +227,17 @@ class ProtocolConverter {
216227
} else {
217228
name ??= field.name;
218229
}
219-
return convertVmResponseToVariable(thread, field.value,
220-
name: name ?? '<unnamed field>',
221-
evaluateName: name != null
222-
? _adapter.combineEvaluateName(evaluateName, '.$name')
223-
: null,
224-
allowCallingToString:
225-
allowCallingToString && index <= maxToStringsPerEvaluation);
230+
return convertVmResponseToVariable(
231+
thread,
232+
field.value,
233+
name: name ?? '<unnamed field>',
234+
evaluateName: name != null
235+
? _adapter.combineEvaluateName(evaluateName, '.$name')
236+
: null,
237+
allowCallingToString:
238+
allowCallingToString && index <= maxToStringsPerEvaluation,
239+
format: format,
240+
);
226241
},
227242
));
228243

@@ -252,6 +267,7 @@ class ProtocolConverter {
252267
_adapter.combineEvaluateName(evaluateName, '.$getterName'),
253268
allowCallingToString:
254269
allowCallingToString && index <= maxToStringsPerEvaluation,
270+
format: format,
255271
);
256272
} catch (e) {
257273
return dap.Variable(
@@ -325,14 +341,14 @@ class ProtocolConverter {
325341
ThreadInfo thread,
326342
vm.Response response, {
327343
required bool allowCallingToString,
328-
bool includeQuotesAroundString = true,
344+
VariableFormat? format,
329345
}) async {
330346
if (response is vm.InstanceRef) {
331347
return convertVmInstanceRefToDisplayString(
332348
thread,
333349
response,
334350
allowCallingToString: allowCallingToString,
335-
includeQuotesAroundString: includeQuotesAroundString,
351+
format: format,
336352
);
337353
} else if (response is vm.Sentinel) {
338354
return '<sentinel>';
@@ -354,12 +370,14 @@ class ProtocolConverter {
354370
required String? name,
355371
required String? evaluateName,
356372
required bool allowCallingToString,
373+
VariableFormat? format,
357374
}) async {
358375
if (response is vm.InstanceRef) {
359376
// For non-simple variables, store them and produce a new reference that
360377
// can be used to access their fields/items/associations.
361-
final variablesReference =
362-
isSimpleKind(response.kind) ? 0 : thread.storeData(response);
378+
final variablesReference = isSimpleKind(response.kind)
379+
? 0
380+
: thread.storeData(VariableData(response, format));
363381

364382
return dap.Variable(
365383
name: name ?? response.kind.toString(),
@@ -368,6 +386,7 @@ class ProtocolConverter {
368386
thread,
369387
response,
370388
allowCallingToString: allowCallingToString,
389+
format: format,
371390
),
372391
indexedVariables: _isList(response) ? response.length : null,
373392
variablesReference: variablesReference,
@@ -545,7 +564,7 @@ class ProtocolConverter {
545564
Future<String?> _callToString(
546565
ThreadInfo thread,
547566
vm.InstanceRef ref, {
548-
bool includeQuotesAroundString = true,
567+
VariableFormat? format,
549568
}) async {
550569
final service = _adapter.vmService;
551570
if (service == null) {
@@ -571,7 +590,7 @@ class ProtocolConverter {
571590
thread,
572591
result,
573592
allowCallingToString: false, // Don't allow recursing.
574-
includeQuotesAroundString: includeQuotesAroundString,
593+
format: format,
575594
);
576595
}
577596

0 commit comments

Comments
 (0)