|
|
@@ -5,14 +5,16 @@ import 'package:flutter_math_fork/flutter_math.dart';
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
import 'curve_plot.dart';
|
|
|
+import 'expr_editor.dart';
|
|
|
+import 'expr_node.dart';
|
|
|
import 'symja.dart';
|
|
|
import 'symja_functions.dart';
|
|
|
|
|
|
/// 科学计算器(默认主界面)。
|
|
|
///
|
|
|
-/// 表达式用**字符串**表示,按键在光标处插入 —— 这是 Q28b 定的第一阶段。
|
|
|
-/// 结构化节点输入(节点树 / 槽 / 选区)是后续独立的升级,但下游接口不变:
|
|
|
-/// 求值、历史、双界面看到的始终是一个表达式字符串。
|
|
|
+/// 输入用**结构化节点树**(见 lib/expr_node.dart),不是字符串:
|
|
|
+/// 按键把节点插进当前光标槽,分数竖排、指数上标、多参数函数各有自己的槽。
|
|
|
+/// 纯文本输入留给控制台。
|
|
|
class CalculatorPage extends StatefulWidget {
|
|
|
const CalculatorPage({super.key});
|
|
|
|
|
|
@@ -21,13 +23,9 @@ class CalculatorPage extends StatefulWidget {
|
|
|
}
|
|
|
|
|
|
class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
- final _ctrl = TextEditingController();
|
|
|
- final _focus = FocusNode();
|
|
|
+ final _doc = ExprDoc();
|
|
|
|
|
|
- /// 边输入边算的结果(不算历史、不动 `%`)。
|
|
|
SymjaResult? _preview;
|
|
|
-
|
|
|
- /// 按 = 提交后的结果。
|
|
|
SymjaResult? _committed;
|
|
|
String? _error;
|
|
|
|
|
|
@@ -37,66 +35,26 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
@override
|
|
|
void dispose() {
|
|
|
_debounce?.cancel();
|
|
|
- _ctrl.dispose();
|
|
|
- _focus.dispose();
|
|
|
super.dispose();
|
|
|
}
|
|
|
|
|
|
- // ------------------------------------------------------------------ 表达式编辑
|
|
|
-
|
|
|
- /// 在光标处插入 [text],并把光标往回退 [back] 位(用于 `Integrate[□, x]` 这类模板)。
|
|
|
- void _insert(String text, {int back = 0}) {
|
|
|
- final v = _ctrl.value;
|
|
|
- final sel = v.selection;
|
|
|
- final start = sel.isValid ? sel.start : v.text.length;
|
|
|
- final end = sel.isValid ? sel.end : v.text.length;
|
|
|
-
|
|
|
- final newText = v.text.replaceRange(start, end, text);
|
|
|
- _ctrl.value = TextEditingValue(
|
|
|
- text: newText,
|
|
|
- selection:
|
|
|
- TextSelection.collapsed(offset: start + text.length - back),
|
|
|
- );
|
|
|
- _onChanged();
|
|
|
- }
|
|
|
-
|
|
|
- void _backspace() {
|
|
|
- final v = _ctrl.value;
|
|
|
- final sel = v.selection;
|
|
|
- final start = sel.isValid ? sel.start : v.text.length;
|
|
|
- final end = sel.isValid ? sel.end : v.text.length;
|
|
|
- if (start == end && start == 0) return;
|
|
|
- final from = start == end ? start - 1 : start;
|
|
|
- final newText = v.text.replaceRange(from, end, '');
|
|
|
- _ctrl.value = TextEditingValue(
|
|
|
- text: newText,
|
|
|
- selection: TextSelection.collapsed(offset: from),
|
|
|
- );
|
|
|
- _onChanged();
|
|
|
- }
|
|
|
-
|
|
|
- void _clear() {
|
|
|
- _ctrl.clear();
|
|
|
- setState(() {
|
|
|
- _preview = null;
|
|
|
- _committed = null;
|
|
|
- _error = null;
|
|
|
- });
|
|
|
- }
|
|
|
+ // ------------------------------------------------------------------ 求值
|
|
|
|
|
|
- void _onChanged() {
|
|
|
+ /// 编辑器每次变化都走这里:清掉上次的提交态,防抖后重算预览。
|
|
|
+ void _onDocChanged() {
|
|
|
setState(() {
|
|
|
_committed = null;
|
|
|
_error = null;
|
|
|
});
|
|
|
_debounce?.cancel();
|
|
|
- // 防抖:按键连打时不要每个字符都过一次通道
|
|
|
_debounce = Timer(const Duration(milliseconds: 500), _runPreview);
|
|
|
}
|
|
|
|
|
|
+ String get _source => _doc.toSymja();
|
|
|
+
|
|
|
Future<void> _runPreview() async {
|
|
|
- final expr = _ctrl.text.trim();
|
|
|
- if (expr.isEmpty) {
|
|
|
+ final expr = _source;
|
|
|
+ if (expr.isEmpty || expr == 'Null') {
|
|
|
if (mounted) setState(() => _preview = null);
|
|
|
return;
|
|
|
}
|
|
|
@@ -107,11 +65,9 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
setState(() => _preview = r);
|
|
|
}
|
|
|
|
|
|
- // ------------------------------------------------------------------ 提交
|
|
|
-
|
|
|
Future<void> _commit() async {
|
|
|
- final expr = _ctrl.text.trim();
|
|
|
- if (expr.isEmpty) return;
|
|
|
+ final expr = _source;
|
|
|
+ if (expr.isEmpty || expr == 'Null') return;
|
|
|
_debounce?.cancel();
|
|
|
final session = context.read<SymjaSession>();
|
|
|
final id = ++_seq; // 让在途的预览作废
|
|
|
@@ -134,10 +90,9 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
await session.refreshVariables();
|
|
|
}
|
|
|
|
|
|
- /// 把当前表达式在 x ∈ [-6.3, 6.3] 上采样并画出来。
|
|
|
Future<void> _plotCurrent() async {
|
|
|
- final expr = _ctrl.text.trim();
|
|
|
- if (expr.isEmpty) return;
|
|
|
+ final expr = _source;
|
|
|
+ if (expr.isEmpty || expr == 'Null') return;
|
|
|
final session = context.read<SymjaSession>();
|
|
|
final messenger = ScaffoldMessenger.of(context);
|
|
|
try {
|
|
|
@@ -174,16 +129,64 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // ------------------------------------------------------------------ 按键
|
|
|
+
|
|
|
+ void _press(_Key k) {
|
|
|
+ switch (k.act) {
|
|
|
+ case _Act.char:
|
|
|
+ if (k.mergeDigit) {
|
|
|
+ // 数字键并入左边那个数字字面量,`42` 才是一个数而不是两个节点
|
|
|
+ _doc.insertChar(k.payload);
|
|
|
+ } else {
|
|
|
+ _doc.insert(LitNode(k.payload));
|
|
|
+ }
|
|
|
+ case _Act.fn:
|
|
|
+ _doc.makeFunction(k.payload);
|
|
|
+ case _Act.fnPrefixed:
|
|
|
+ // 像 Integrate 这种,第一个参数是要填的,后面预置好
|
|
|
+ _doc.makeFunction(k.payload, prefill: k.prefill);
|
|
|
+ // prefill 之后光标停在第一个槽(空的),正是要填的位置
|
|
|
+ case _Act.sqrt:
|
|
|
+ _doc.makeSqrt();
|
|
|
+ case _Act.group:
|
|
|
+ _doc.makeGroup();
|
|
|
+ case _Act.power:
|
|
|
+ _doc.makePower();
|
|
|
+ case _Act.frac:
|
|
|
+ _doc.makeFraction();
|
|
|
+ case _Act.postfix:
|
|
|
+ // 阶乘是后缀:把光标左边那个节点收进来当操作数
|
|
|
+ final operand = _doc.takeLeft();
|
|
|
+ _doc.insert(PostfixNode(
|
|
|
+ operand: SeqNode(operand == null ? null : [operand]),
|
|
|
+ ));
|
|
|
+ case _Act.comma:
|
|
|
+ _doc.nextArgument();
|
|
|
+ case _Act.back:
|
|
|
+ _doc.backspace();
|
|
|
+ case _Act.clear:
|
|
|
+ _doc.clear();
|
|
|
+ case _Act.commit:
|
|
|
+ _commit();
|
|
|
+ return; // 提交不改文档,不用刷新
|
|
|
+ }
|
|
|
+ _onDocChanged();
|
|
|
+ }
|
|
|
+
|
|
|
Future<void> _showPalette() async {
|
|
|
- // 面板返回「要插入什么」+「光标从末尾回退几位」:
|
|
|
- // 函数是 `Sin[]` 退 1(光标在括号里),常量是 `Pi` 退 0。
|
|
|
- final picked = await showModalBottomSheet<({String text, int back})>(
|
|
|
+ final picked = await showModalBottomSheet<({String value, bool isFn})>(
|
|
|
context: context,
|
|
|
isScrollControlled: true,
|
|
|
showDragHandle: true,
|
|
|
builder: (_) => const _FunctionPalette(),
|
|
|
);
|
|
|
- if (picked != null) _insert(picked.text, back: picked.back);
|
|
|
+ if (picked == null) return;
|
|
|
+ if (picked.isFn) {
|
|
|
+ _doc.makeFunction(picked.value);
|
|
|
+ } else {
|
|
|
+ _doc.insert(LitNode(picked.value));
|
|
|
+ }
|
|
|
+ _onDocChanged();
|
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------ UI
|
|
|
@@ -207,7 +210,10 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
IconButton(
|
|
|
tooltip: '清空',
|
|
|
icon: const Icon(Icons.refresh),
|
|
|
- onPressed: _clear,
|
|
|
+ onPressed: () {
|
|
|
+ _doc.clear();
|
|
|
+ _onDocChanged();
|
|
|
+ },
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
@@ -229,29 +235,27 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
|
|
|
return Container(
|
|
|
width: double.infinity,
|
|
|
- padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
|
|
+ constraints: const BoxConstraints(minHeight: 132),
|
|
|
+ padding: const EdgeInsets.fromLTRB(12, 10, 12, 10),
|
|
|
color: scheme.surfaceContainerLow,
|
|
|
child: Column(
|
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
children: [
|
|
|
- TextField(
|
|
|
- controller: _ctrl,
|
|
|
- focusNode: _focus,
|
|
|
- maxLines: 3,
|
|
|
- minLines: 1,
|
|
|
+ ExprEditor(doc: _doc, onChanged: _onDocChanged),
|
|
|
+ const SizedBox(height: 6),
|
|
|
+ // 序列化结果摆在明面上:用户能看到"到底会被求值成什么"
|
|
|
+ Text(
|
|
|
+ _source,
|
|
|
textAlign: TextAlign.right,
|
|
|
- style: const TextStyle(fontFamily: 'monospace', fontSize: 18),
|
|
|
- decoration: const InputDecoration(
|
|
|
- isDense: true,
|
|
|
- border: InputBorder.none,
|
|
|
- hintText: '按下面的键,或点这里用键盘输入',
|
|
|
+ style: TextStyle(
|
|
|
+ fontFamily: 'monospace',
|
|
|
+ fontSize: 11,
|
|
|
+ color: scheme.outline,
|
|
|
),
|
|
|
- onChanged: (_) => _onChanged(),
|
|
|
- onSubmitted: (_) => _commit(),
|
|
|
),
|
|
|
- const SizedBox(height: 8),
|
|
|
+ const SizedBox(height: 6),
|
|
|
SizedBox(
|
|
|
- height: 56,
|
|
|
+ height: 52,
|
|
|
child: Align(
|
|
|
alignment: Alignment.centerRight,
|
|
|
child: SingleChildScrollView(
|
|
|
@@ -279,12 +283,7 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
),
|
|
|
);
|
|
|
}
|
|
|
- if (shown == null) {
|
|
|
- return Text(
|
|
|
- _ctrl.text.trim().isEmpty ? '' : '…',
|
|
|
- style: TextStyle(fontSize: 22, color: scheme.outline),
|
|
|
- );
|
|
|
- }
|
|
|
+ if (shown == null) return const SizedBox.shrink();
|
|
|
|
|
|
final plain = SelectableText(
|
|
|
shown.text,
|
|
|
@@ -318,65 +317,62 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
Widget _keypad(BuildContext context) {
|
|
|
return LayoutBuilder(
|
|
|
builder: (context, box) {
|
|
|
- const rows = <List<_Key>>[
|
|
|
+ final rows = <List<_Key>>[
|
|
|
[
|
|
|
- _Key('∫', insert: 'Integrate[', after: ', x]'),
|
|
|
- _Key('Σ', insert: 'Sum[', after: ', {n, 1, Infinity}]'),
|
|
|
- _Key('d/dx', insert: 'D[', after: ', x]'),
|
|
|
- _Key('lim', insert: 'Limit[', after: ', x->0]'),
|
|
|
- _Key('solve', insert: 'Solve[', after: ', x]'),
|
|
|
+ _Key('∫', _Act.fnPrefixed, payload: 'Integrate', prefill: ['', 'x']),
|
|
|
+ _Key('Σ', _Act.fnPrefixed,
|
|
|
+ payload: 'Sum', prefill: ['', '{n, 1, Infinity}']),
|
|
|
+ _Key('d/dx', _Act.fnPrefixed, payload: 'D', prefill: ['', 'x']),
|
|
|
+ _Key('lim', _Act.fnPrefixed, payload: 'Limit', prefill: ['', 'x->0']),
|
|
|
+ _Key('solve', _Act.fnPrefixed, payload: 'Solve', prefill: ['', 'x']),
|
|
|
],
|
|
|
[
|
|
|
- _Key('sin', insert: 'Sin[', after: ']'),
|
|
|
- _Key('cos', insert: 'Cos[', after: ']'),
|
|
|
- _Key('tan', insert: 'Tan[', after: ']'),
|
|
|
- _Key('ln', insert: 'Log[', after: ']'),
|
|
|
- _Key('√', insert: 'Sqrt[', after: ']'),
|
|
|
+ _Key('sin', _Act.fn, payload: 'Sin'),
|
|
|
+ _Key('cos', _Act.fn, payload: 'Cos'),
|
|
|
+ _Key('tan', _Act.fn, payload: 'Tan'),
|
|
|
+ _Key('ln', _Act.fn, payload: 'Log'),
|
|
|
+ _Key('√', _Act.sqrt),
|
|
|
],
|
|
|
[
|
|
|
- _Key('7', insert: '7'),
|
|
|
- _Key('8', insert: '8'),
|
|
|
- _Key('9', insert: '9'),
|
|
|
- _Key('(', insert: '('),
|
|
|
- _Key(')', insert: ')'),
|
|
|
+ _Key('7', _Act.char, payload: '7', mergeDigit: true),
|
|
|
+ _Key('8', _Act.char, payload: '8', mergeDigit: true),
|
|
|
+ _Key('9', _Act.char, payload: '9', mergeDigit: true),
|
|
|
+ _Key('(', _Act.group),
|
|
|
+ _Key('/', _Act.frac),
|
|
|
],
|
|
|
[
|
|
|
- _Key('4', insert: '4'),
|
|
|
- _Key('5', insert: '5'),
|
|
|
- _Key('6', insert: '6'),
|
|
|
- _Key('×', insert: '*'),
|
|
|
- _Key('÷', insert: '/'),
|
|
|
+ _Key('4', _Act.char, payload: '4', mergeDigit: true),
|
|
|
+ _Key('5', _Act.char, payload: '5', mergeDigit: true),
|
|
|
+ _Key('6', _Act.char, payload: '6', mergeDigit: true),
|
|
|
+ _Key('×', _Act.char, payload: '*'),
|
|
|
+ _Key('!', _Act.postfix),
|
|
|
],
|
|
|
[
|
|
|
- _Key('1', insert: '1'),
|
|
|
- _Key('2', insert: '2'),
|
|
|
- _Key('3', insert: '3'),
|
|
|
- _Key('+', insert: '+'),
|
|
|
- _Key('−', insert: '-'),
|
|
|
+ _Key('1', _Act.char, payload: '1', mergeDigit: true),
|
|
|
+ _Key('2', _Act.char, payload: '2', mergeDigit: true),
|
|
|
+ _Key('3', _Act.char, payload: '3', mergeDigit: true),
|
|
|
+ _Key('+', _Act.char, payload: '+'),
|
|
|
+ _Key('−', _Act.char, payload: '-'),
|
|
|
],
|
|
|
[
|
|
|
- _Key('0', insert: '0'),
|
|
|
- _Key('.', insert: '.'),
|
|
|
- _Key('^', insert: '^'),
|
|
|
- // 变量键:没有它们就没法只用按钮写出 Integrate[x^2, x]。
|
|
|
- // π / e 挪进了函数面板(那里的常量是可以点的)。
|
|
|
- _Key('x', insert: 'x'),
|
|
|
- _Key('n', insert: 'n'),
|
|
|
+ _Key('0', _Act.char, payload: '0', mergeDigit: true),
|
|
|
+ _Key('.', _Act.char, payload: '.', mergeDigit: true),
|
|
|
+ _Key('^', _Act.power),
|
|
|
+ _Key('x', _Act.char, payload: 'x'),
|
|
|
+ _Key('n', _Act.char, payload: 'n'),
|
|
|
],
|
|
|
[
|
|
|
- _Key('⌫', action: _KeyAction.backspace, tint: true),
|
|
|
- _Key('CLR', action: _KeyAction.clear, tint: true),
|
|
|
- _Key(',', insert: ', '),
|
|
|
- _Key('ANS', insert: '%', tint: true),
|
|
|
- _Key('=', action: _KeyAction.commit, primary: true),
|
|
|
+ _Key('⌫', _Act.back, tint: true),
|
|
|
+ _Key('CLR', _Act.clear, tint: true),
|
|
|
+ _Key(',', _Act.comma),
|
|
|
+ _Key('ANS', _Act.char, payload: '%', tint: true),
|
|
|
+ _Key('=', _Act.commit, primary: true),
|
|
|
],
|
|
|
];
|
|
|
|
|
|
- // 7 行 + 6 个 6px 间隙,再扣掉 Padding 的上下各 6px ——
|
|
|
- // box.maxHeight 是 padding **之前**的高度,漏掉这一项会溢出 12px。
|
|
|
- const gaps = 6 * 6.0;
|
|
|
- const padding = 12.0;
|
|
|
- final h = (box.maxHeight - padding - gaps) / 7;
|
|
|
+ // 7 行 + 6 个间隙,再扣掉 Padding 上下各 6 ——
|
|
|
+ // box.maxHeight 是 padding **之前**的高度,漏掉会溢出。
|
|
|
+ final h = (box.maxHeight - 12 - 6 * 6) / 7;
|
|
|
return Padding(
|
|
|
padding: const EdgeInsets.all(6),
|
|
|
child: Column(
|
|
|
@@ -412,25 +408,14 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
final fg = k.primary ? scheme.onPrimary : scheme.onSurface;
|
|
|
|
|
|
return Material(
|
|
|
+ // 加 key 是为了让测试能精确定位按键 —— 编辑器里渲染出的字面量(比如 'x')
|
|
|
+ // 和键盘上同名的按键都符合 find.text,会撞车。
|
|
|
+ key: ValueKey('key-${k.label}'),
|
|
|
color: bg,
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
child: InkWell(
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
- onTap: () {
|
|
|
- switch (k.action) {
|
|
|
- case _KeyAction.insert:
|
|
|
- // 模板形如 Insert[□, x]:先插前半段,再插后半段,光标夹在中间
|
|
|
- _insert(k.insert + (k.after ?? ''), back: (k.after ?? '').length);
|
|
|
- case _KeyAction.backspace:
|
|
|
- _backspace();
|
|
|
- case _KeyAction.clear:
|
|
|
- _clear();
|
|
|
- case _KeyAction.commit:
|
|
|
- _commit();
|
|
|
- case null:
|
|
|
- _insert(k.insert);
|
|
|
- }
|
|
|
- },
|
|
|
+ onTap: () => _press(k),
|
|
|
child: Center(
|
|
|
child: Text(
|
|
|
k.label,
|
|
|
@@ -446,33 +431,48 @@ class _CalculatorPageState extends State<CalculatorPage> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-enum _KeyAction { insert, backspace, clear, commit }
|
|
|
+enum _Act {
|
|
|
+ char,
|
|
|
+ fn,
|
|
|
+ fnPrefixed,
|
|
|
+ sqrt,
|
|
|
+ group,
|
|
|
+ power,
|
|
|
+ frac,
|
|
|
+ postfix,
|
|
|
+ comma,
|
|
|
+ back,
|
|
|
+ clear,
|
|
|
+ commit,
|
|
|
+}
|
|
|
|
|
|
@immutable
|
|
|
class _Key {
|
|
|
final String label;
|
|
|
+ final _Act act;
|
|
|
+ final String payload;
|
|
|
|
|
|
- /// 插入的文本(模板时是前半段)。
|
|
|
- final String insert;
|
|
|
+ /// fnPrefixed 用:第一个参数留空给用户填,其余的预置。
|
|
|
+ final List<String> prefill;
|
|
|
|
|
|
- /// 模板的后半段;光标会停在 [insert] 与 [after] 之间。
|
|
|
- final String? after;
|
|
|
+ /// 数字键:并入左边那个数字字面量,而不是各建一个节点。
|
|
|
+ final bool mergeDigit;
|
|
|
|
|
|
- final _KeyAction? action;
|
|
|
final bool tint;
|
|
|
final bool primary;
|
|
|
|
|
|
const _Key(
|
|
|
- this.label, {
|
|
|
- this.insert = '',
|
|
|
- this.after,
|
|
|
- this.action = _KeyAction.insert,
|
|
|
+ this.label,
|
|
|
+ this.act, {
|
|
|
+ this.payload = '',
|
|
|
+ this.prefill = const [],
|
|
|
+ this.mergeDigit = false,
|
|
|
this.tint = false,
|
|
|
this.primary = false,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-/// 函数面板:检索 Symja 的两百多个函数,选中后插入 `Name[]`。
|
|
|
+/// 函数面板:检索 Symja 的两百多个函数名,选中后插入一个函数节点。
|
|
|
class _FunctionPalette extends StatefulWidget {
|
|
|
const _FunctionPalette();
|
|
|
|
|
|
@@ -525,8 +525,7 @@ class _FunctionPaletteState extends State<_FunctionPalette> {
|
|
|
runSpacing: 4,
|
|
|
crossAxisAlignment: WrapCrossAlignment.center,
|
|
|
children: [
|
|
|
- Text('常量',
|
|
|
- style: Theme.of(context).textTheme.bodySmall),
|
|
|
+ Text('常量', style: Theme.of(context).textTheme.bodySmall),
|
|
|
for (final c in kSymjaConstants)
|
|
|
ActionChip(
|
|
|
label: Text(
|
|
|
@@ -537,7 +536,7 @@ class _FunctionPaletteState extends State<_FunctionPalette> {
|
|
|
visualDensity: VisualDensity.compact,
|
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
|
onPressed: () =>
|
|
|
- Navigator.pop(context, (text: c, back: 0)),
|
|
|
+ Navigator.pop(context, (value: c, isFn: false)),
|
|
|
),
|
|
|
],
|
|
|
),
|
|
|
@@ -556,7 +555,7 @@ class _FunctionPaletteState extends State<_FunctionPalette> {
|
|
|
style: const TextStyle(fontFamily: 'monospace'),
|
|
|
),
|
|
|
onTap: () => Navigator.pop(
|
|
|
- context, (text: '${fns[i]}[]', back: 1)),
|
|
|
+ context, (value: fns[i], isFn: true)),
|
|
|
),
|
|
|
),
|
|
|
),
|