blob: b4e638ffc9f0e5047197e04df71efa2efa6d6073 [file] [log] [blame]
// Copyright 2020 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 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:vm_service/vm_service.dart';
import '../notifications.dart';
import 'debugger_controller.dart';
// TODO(devoncarew): We'll want some kind of code completion w/ eval.
// TODO(devoncarew): We should insert eval result objects into the console as
// expandable objects.
class ExpressionEvalField extends StatefulWidget {
const ExpressionEvalField({
this.controller,
});
final DebuggerController controller;
@override
_ExpressionEvalFieldState createState() => _ExpressionEvalFieldState();
}
class _ExpressionEvalFieldState extends State<ExpressionEvalField> {
TextEditingController textController;
FocusNode textFocus;
int historyPosition = -1;
@override
void initState() {
super.initState();
textController = TextEditingController();
textFocus = FocusNode();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Container(
decoration: BoxDecoration(
border: Border(
top: BorderSide(color: theme.focusColor),
),
),
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
const Text('>'),
const SizedBox(width: 8.0),
Expanded(
child: Focus(
onKey: (_, RawKeyEvent event) {
if (event.isKeyPressed(LogicalKeyboardKey.arrowUp)) {
_historyNavUp();
return KeyEventResult.handled;
} else if (event.isKeyPressed(LogicalKeyboardKey.arrowDown)) {
_historyNavDown();
return KeyEventResult.handled;
}
return KeyEventResult.ignored;
},
child: TextField(
onSubmitted: (value) => _handleExpressionEval(context, value),
focusNode: textFocus,
decoration: null,
controller: textController,
),
),
),
],
),
);
}
void _handleExpressionEval(
BuildContext context,
String expressionText,
) async {
textFocus.requestFocus();
// Don't try to eval if we're not paused.
if (!widget.controller.isPaused.value) {
Notifications.of(context)
.push('Application must be paused to support expression evaluation.');
return;
}
expressionText = expressionText.trim();
widget.controller.appendStdio('> $expressionText\n');
setState(() {
historyPosition = -1;
widget.controller.evalHistory.pushEvalHistory(expressionText);
});
textController.clear();
try {
// Response is either a ErrorRef, InstanceRef, or Sentinel.
final response =
await widget.controller.evalAtCurrentFrame(expressionText);
// Display the response to the user.
if (response is InstanceRef) {
_emitRefToConsole(response);
} else {
var value = response.toString();
if (response is ErrorRef) {
value = response.message;
} else if (response is Sentinel) {
value = response.valueAsString;
}
_emitToConsole(value);
}
} catch (e) {
// Display the error to the user.
_emitToConsole('$e');
}
}
void _emitToConsole(String text) {
widget.controller.appendStdio(' ${text.replaceAll('\n', '\n ')}\n');
}
void _emitRefToConsole(InstanceRef ref) {
widget.controller.appendInstanceRef(ref);
}
@override
void dispose() {
textFocus.dispose();
textController.dispose();
super.dispose();
}
void _historyNavUp() {
final evalHistory = widget.controller.evalHistory;
if (!evalHistory.canNavigateUp) {
return;
}
setState(() {
evalHistory.navigateUp();
final text = evalHistory.currentText;
textController.value = TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
);
});
}
void _historyNavDown() {
final evalHistory = widget.controller.evalHistory;
if (!evalHistory.canNavigateDown) {
return;
}
setState(() {
evalHistory.navigateDown();
final text = evalHistory.currentText ?? '';
textController.value = TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
);
});
}
}