blob: 3544949b6c70ec67a2b98eaffe1e363574bd328b [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:vm_service/vm_service.dart';
import '../debugger/debugger_state.dart';
import '../ui/custom.dart';
import '../ui/elements.dart';
typedef VariableDescriber = Future<String> Function(BoundVariable variable);
class VariablesView implements CoreElementView {
VariablesView(
DebuggerState debuggerState, VariableDescriber variableDescriber) {
_items = SelectableTree<BoundVariable>()
..flex()
..clazz('menu-item-bottom-border')
..clazz('debugger-items-list');
_items.setChildProvider(new VariablesChildProvider(debuggerState));
_items.setRenderer((BoundVariable variable) {
final String name = variable.name;
final dynamic value = variable.value;
String valueStr;
if (value is InstanceRef) {
if (value.valueAsString == null) {
valueStr = value.classRef.name;
} else {
valueStr = value.valueAsString;
if (value.valueAsStringIsTruncated) {
valueStr += '...';
}
if (value.kind == InstanceKind.kString) {
valueStr = "'$valueStr'";
}
}
if (value.kind == InstanceKind.kList) {
valueStr = '[${value.length}] $valueStr';
} else if (value.kind == InstanceKind.kMap) {
valueStr = '{ ${value.length} } $valueStr';
} else if (value.kind != null && value.kind.endsWith('List')) {
// Uint8List, Uint16List, ...
valueStr = '[${value.length}] $valueStr';
}
} else if (value is Sentinel) {
valueStr = value.valueAsString;
} else if (value is TypeArgumentsRef) {
valueStr = value.name;
} else {
valueStr = value.toString();
}
final CoreElement element = li(c: 'list-item')
..add([
span(text: name),
span(text: ' $valueStr', c: 'subtle'),
]);
StreamSubscription sub;
sub = element.element.onMouseOver.listen((e) {
// TODO(devoncarew): Call toString() only after a short dwell.
sub.cancel();
variableDescriber(variable).then((String desc) {
element.tooltip = desc;
});
});
return element;
});
}
SelectableTree<BoundVariable> _items;
List<BoundVariable> get items => _items.items;
@override
CoreElement get element => _items;
void showVariables(Frame frame) {
// AsyncCausal frames don't have local vars.
_items.setItems(frame.vars ?? <BoundVariable>[]);
}
void clearVariables() {
_items.setItems(<BoundVariable>[]);
}
}
class VariablesChildProvider extends ChildProvider<BoundVariable> {
VariablesChildProvider(this.debuggerState);
final DebuggerState debuggerState;
@override
bool hasChildren(BoundVariable item) {
final dynamic value = item.value;
return value is InstanceRef && value.valueAsString == null;
}
@override
Future<List<BoundVariable>> getChildren(BoundVariable item) async {
final dynamic value = item.value;
if (value is! InstanceRef) {
return [];
}
final InstanceRef instanceRef = value;
final dynamic result = await debuggerState.getInstance(instanceRef);
if (result is! Instance) {
return [];
}
// TODO: how to test?
final Instance instance = result;
if (instance.associations != null) {
return instance.associations.map((MapAssociation assoc) {
// For string keys, quote the key value.
String keyString = assoc.key.valueAsString;
if (assoc.key is InstanceRef &&
assoc.key.kind == InstanceKind.kString) {
keyString = "'$keyString'";
}
return new BoundVariable()
..name = '[$keyString]'
..value = assoc.value;
}).toList();
} else if (instance.elements != null) {
final List<BoundVariable> result = [];
int index = 0;
for (dynamic value in instance.elements) {
result.add(new BoundVariable()
..name = '[$index]'
..value = value);
index++;
}
return result;
} else if (instance.fields != null) {
return instance.fields.map((BoundField field) {
return new BoundVariable()
..name = field.decl.name
..value = field.value;
}).toList();
} else {
return [];
}
}
}