Code editor in formulaeditor
This commit is contained in:
parent
02fd849484
commit
e49f6c3079
6 changed files with 98 additions and 190 deletions
|
|
@ -9,6 +9,7 @@
|
|||
- `flutter pub get` --> `./flutterw pub get`
|
||||
- `flutter run -d linux` --> `./flutterw run -d linux`
|
||||
- See `./Makefile` for more examples.
|
||||
- If you are an agent, you may be also containerized. Try `distrobox-host-exec $(pwd)/flutterw`
|
||||
|
||||
|
||||
# MANDATORY WORKFLOW
|
||||
|
|
|
|||
26
Makefile
26
Makefile
|
|
@ -1,43 +1,45 @@
|
|||
|
||||
all: build-container clean-container build-builders build-linux-debug-container
|
||||
|
||||
DB=~/.local/share/com.example.d4rt_formulas/d4rt_formulas/formulas.sqlite
|
||||
DATABASEFILE=~/.local/share/com.example.d4rt_formulas/d4rt_formulas/formulas.sqlite
|
||||
|
||||
FLUTTERW := $(shell if [ "$$CONTAINER_ID" = "" ]; then echo "./flutterw"; else echo "distrobox-host-exec $(CURDIR)/flutterw"; fi)
|
||||
|
||||
build-container:
|
||||
./flutterw --build-container
|
||||
$(FLUTTERW) --build-container
|
||||
|
||||
clean:
|
||||
flutter clean
|
||||
[ -f $(DB) ] && rm $(DB)
|
||||
[ -f $(DATABASEFILE) ] && rm $(DATABASEFILE)
|
||||
|
||||
clean-container:
|
||||
rm -r .build-container-cache
|
||||
./flutterw clean
|
||||
$(FLUTTERW) clean
|
||||
|
||||
|
||||
pub-get-container:
|
||||
./flutterw pub get
|
||||
$(FLUTTERW) pub get
|
||||
|
||||
test:
|
||||
./flutterw test
|
||||
$(FLUTTERW) test
|
||||
|
||||
build-builders:
|
||||
./flutterw pub run build_runner build --delete-conflicting-outputs
|
||||
$(FLUTTERW) pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
build-android-release-container:
|
||||
./flutterw build apk --release
|
||||
$(FLUTTERW) build apk --release
|
||||
|
||||
build-linux-debug-container:
|
||||
./flutterw build linux --debug
|
||||
$(FLUTTERW) build linux --debug
|
||||
|
||||
build-web-debug-container:
|
||||
./flutterw build web --debug
|
||||
$(FLUTTERW) build web --debug
|
||||
|
||||
run-linux-debug-container:
|
||||
./flutterw run -d linux
|
||||
$(FLUTTERW) run -d linux
|
||||
|
||||
run-web-debug-container:
|
||||
./flutterw run --web-port $${WEB_PORT:-8081} -d web-server
|
||||
$(FLUTTERW) run --web-port $${WEB_PORT:-8081} -d web-server
|
||||
|
||||
run-linux-debug-native:
|
||||
flutter run -d linux
|
||||
|
|
|
|||
3
TODO.md
3
TODO.md
|
|
@ -79,5 +79,6 @@
|
|||
- It will show a screen with a text editor with dart syntax and a button "paste".
|
||||
- The "paste" button will copy the clipboard into the text editor.
|
||||
- A second button "import" will use the import preview screen
|
||||
- Make formulaSolver() asyncronous, and show a CircularProgressIndicator while the formula is being solved. Honor a new optinal parameter "timeout" in formulaSolver, that will throw a TimeoutException.
|
||||
- [ ] Add a uuid column to the table or FormulaElements, so it is not necessary to load all the formulas to find a formula by uuid. This will improve performance when updating and deleting.
|
||||
- [ ] Make formulaSolver() asyncronous, and show a CircularProgressIndicator while the formula is being solved. Honor a new optinal parameter "timeout" in formulaSolver, that will throw a TimeoutException.
|
||||
- [ ] When importing FormulaElements, save the FormulaElements in the database (currently, they are only added to the Corpus in memory).
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import '../database/database_service.dart';
|
|||
import '../service_locator.dart';
|
||||
import 'formula_screen.dart';
|
||||
import 'unit_dropdown.dart';
|
||||
import 'package:flutter_code_editor/flutter_code_editor.dart';
|
||||
import 'package:flutter_highlight/themes/monokai-sublime.dart';
|
||||
import 'package:highlight/languages/dart.dart';
|
||||
|
||||
/// A screen for editing a Formula's properties including name, description,
|
||||
/// input/output variables, and d4rt code.
|
||||
|
|
@ -17,12 +20,7 @@ class FormulaEditor extends StatefulWidget {
|
|||
final Corpus corpus;
|
||||
final Function(Formula)? onSave;
|
||||
|
||||
const FormulaEditor({
|
||||
super.key,
|
||||
required this.formula,
|
||||
required this.corpus,
|
||||
this.onSave,
|
||||
});
|
||||
const FormulaEditor({super.key, required this.formula, required this.corpus, this.onSave});
|
||||
|
||||
@override
|
||||
State<FormulaEditor> createState() => _FormulaEditorState();
|
||||
|
|
@ -32,7 +30,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
final _formKey = GlobalKey<FormState>();
|
||||
late TextEditingController _nameController;
|
||||
late TextEditingController _descriptionController;
|
||||
late TextEditingController _d4rtCodeController;
|
||||
late CodeController _d4rtCodeController;
|
||||
|
||||
// Track input variables
|
||||
final List<_InputVariableRowData> _inputVariables = [];
|
||||
|
|
@ -47,15 +45,17 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
super.initState();
|
||||
_nameController = TextEditingController(text: widget.formula.name);
|
||||
_descriptionController = TextEditingController(text: widget.formula.description ?? '');
|
||||
_d4rtCodeController = TextEditingController(text: widget.formula.d4rtCode);
|
||||
_d4rtCodeController = CodeController(language: dart, text: widget.formula.d4rtCode ?? '');
|
||||
|
||||
// Initialize input variables
|
||||
for (final input in widget.formula.input) {
|
||||
_inputVariables.add(_InputVariableRowData(
|
||||
_inputVariables.add(
|
||||
_InputVariableRowData(
|
||||
nameController: TextEditingController(text: input.name),
|
||||
unit: input.unit,
|
||||
values: input.values != null ? List.from(input.values!) : null,
|
||||
));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize output variable
|
||||
|
|
@ -79,11 +79,13 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
|
||||
void _addInputVariable() {
|
||||
setState(() {
|
||||
_inputVariables.add(_InputVariableRowData(
|
||||
_inputVariables.add(
|
||||
_InputVariableRowData(
|
||||
nameController: TextEditingController(text: 'var${_inputVariables.length + 1}'),
|
||||
unit: null,
|
||||
values: null,
|
||||
));
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +119,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FormulaScreen(
|
||||
formula: formula,
|
||||
corpus: widget.corpus,
|
||||
),
|
||||
builder: (context) => FormulaScreen(formula: formula, corpus: widget.corpus),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -159,17 +158,12 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
try {
|
||||
final input = <VariableSpec>[];
|
||||
for (final variable in _inputVariables) {
|
||||
input.add(VariableSpec(
|
||||
name: variable.nameController.text.trim(),
|
||||
unit: variable.unit,
|
||||
values: variable.values,
|
||||
));
|
||||
input.add(
|
||||
VariableSpec(name: variable.nameController.text.trim(), unit: variable.unit, values: variable.values),
|
||||
);
|
||||
}
|
||||
|
||||
final output = VariableSpec(
|
||||
name: _outputVariable.nameController.text.trim(),
|
||||
unit: _outputVariable.unit,
|
||||
);
|
||||
final output = VariableSpec(name: _outputVariable.nameController.text.trim(), unit: _outputVariable.unit);
|
||||
|
||||
return Formula(
|
||||
uuid: widget.formula.uuid,
|
||||
|
|
@ -177,7 +171,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
description: _descriptionController.text.isEmpty ? null : _descriptionController.text,
|
||||
input: input,
|
||||
output: output,
|
||||
d4rtCode: _d4rtCodeController.text,
|
||||
d4rtCode: _d4rtCodeController.fullText,
|
||||
tags: widget.formula.tags, // Preserve existing tags
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
@ -296,21 +290,9 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
appBar: AppBar(
|
||||
title: const Text('Edit Formula'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.play_arrow),
|
||||
onPressed: _testFormula,
|
||||
tooltip: 'Test Formula',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.copy),
|
||||
onPressed: _saveFormulaAsCopy,
|
||||
tooltip: 'Save as copy',
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.save),
|
||||
onPressed: _saveFormula,
|
||||
tooltip: 'Save',
|
||||
),
|
||||
IconButton(icon: const Icon(Icons.play_arrow), onPressed: _testFormula, tooltip: 'Test Formula'),
|
||||
IconButton(icon: const Icon(Icons.copy), onPressed: _saveFormulaAsCopy, tooltip: 'Save as copy'),
|
||||
IconButton(icon: const Icon(Icons.save), onPressed: _saveFormula, tooltip: 'Save'),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
|
|
@ -363,13 +345,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Description (Markdown)',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Text('Description (Markdown)', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
@ -400,13 +376,8 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
child: Markdown(
|
||||
data: _descriptionController.text,
|
||||
shrinkWrap: true,
|
||||
builders: {
|
||||
'latex': LatexElementBuilder(),
|
||||
},
|
||||
extensionSet: markdown.ExtensionSet(
|
||||
[LatexBlockSyntax()],
|
||||
[LatexInlineSyntax()],
|
||||
),
|
||||
builders: {'latex': LatexElementBuilder()},
|
||||
extensionSet: markdown.ExtensionSet([LatexBlockSyntax()], [LatexInlineSyntax()]),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
@ -432,13 +403,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Input Variables',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Text('Input Variables', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
..._inputVariables.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
|
|
@ -470,10 +435,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
flex: 2,
|
||||
child: TextFormField(
|
||||
controller: variable.nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
decoration: const InputDecoration(labelText: 'Name', border: OutlineInputBorder()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
|
@ -530,7 +492,8 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
final unitSpec = widget.corpus.getUnit(unit);
|
||||
return DropdownMenuItem<String?>(
|
||||
value: unit,
|
||||
child: Text('${unitSpec.symbol} - ${unit}',
|
||||
child: Text(
|
||||
'${unitSpec.symbol} - ${unit}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -567,13 +530,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Output Variable',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const Text('Output Variable', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
|
|
@ -581,10 +538,7 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
flex: 2,
|
||||
child: TextFormField(
|
||||
controller: _outputVariable.nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Name',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
decoration: const InputDecoration(labelText: 'Name', border: OutlineInputBorder()),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
|
@ -641,7 +595,8 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
final unitSpec = widget.corpus.getUnit(unit);
|
||||
return DropdownMenuItem<String?>(
|
||||
value: unit,
|
||||
child: Text('${unitSpec.symbol} - ${unit}',
|
||||
child: Text(
|
||||
'${unitSpec.symbol} - ${unit}',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -667,67 +622,18 @@ class _FormulaEditorState extends State<FormulaEditor> {
|
|||
|
||||
Widget _buildD4rtCodeSection() {
|
||||
return Card(
|
||||
child: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'D4RT Code',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(styles: monokaiSublimeTheme),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SingleChildScrollView(
|
||||
child: CodeField(controller: _d4rtCodeController),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Theme.of(context).dividerColor),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(4),
|
||||
topRight: Radius.circular(4),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.code, size: 16, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Dart Syntax',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _d4rtCodeController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Enter D4RT/Dart code here',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.all(12),
|
||||
),
|
||||
maxLines: 10,
|
||||
style: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
@ -763,19 +669,12 @@ class _InputVariableRowData {
|
|||
String? unit;
|
||||
List<dynamic>? values;
|
||||
|
||||
_InputVariableRowData({
|
||||
required this.nameController,
|
||||
this.unit,
|
||||
this.values,
|
||||
});
|
||||
_InputVariableRowData({required this.nameController, this.unit, this.values});
|
||||
}
|
||||
|
||||
class _OutputVariableRowData {
|
||||
final TextEditingController nameController;
|
||||
String? unit;
|
||||
|
||||
_OutputVariableRowData({
|
||||
required this.nameController,
|
||||
this.unit,
|
||||
});
|
||||
_OutputVariableRowData({required this.nameController, this.unit});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ extension CorpusDatabaseExtension on FormulasDatabase {
|
|||
for (final element in elements) {
|
||||
try {
|
||||
final parsed = SetUtils.parseCorpusElements('[${element.elementText}]');
|
||||
print("PARSED:$element");
|
||||
parsedElements.addAll(parsed);
|
||||
} catch (e) {
|
||||
print('Error parsing database element: $e');
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ abstract class SetUtils {
|
|||
}
|
||||
|
||||
/// Escapes special characters in a string for use in D4RT literals
|
||||
@deprecated
|
||||
static String escapeD4rtString(String input) {
|
||||
return input
|
||||
.replaceAll(r'\\', r'\\\\') // escape backslashes first
|
||||
|
|
@ -75,9 +74,9 @@ abstract class SetUtils {
|
|||
/// Uses JSON-like formatting but for Dart language, with proper indentation.
|
||||
static String prettyPrint(dynamic value, {int indent = 0}) {
|
||||
if (value is String) {
|
||||
return _prettyPrintString(value, indent);
|
||||
return _prettyPrintString(value);
|
||||
} else if (value is num) {
|
||||
return _prettyPrintNumber(value, indent);
|
||||
return _prettyPrintNumber(value);
|
||||
} else if (value is Set) {
|
||||
return _prettyPrintSet(value, indent);
|
||||
} else if (value is List) {
|
||||
|
|
@ -90,15 +89,15 @@ abstract class SetUtils {
|
|||
}
|
||||
|
||||
/// Pretty prints a simple string, escaping special characters if needed.
|
||||
static String _prettyPrintString(String s, int indent) {
|
||||
static String _prettyPrintString(String s) {
|
||||
// Check if the string needs raw string formatting (newlines, $, backslashes, quotes)
|
||||
final needsRawString = s.contains('\n') ||
|
||||
s.contains(r'$') ||
|
||||
s.contains(r'\\') ||
|
||||
s.contains('"');
|
||||
|
||||
if (needsRawString) {
|
||||
return _prettyPrintRawString(s, indent);
|
||||
if (needsRawString && s != '"' ) {
|
||||
return _prettyPrintRawString(s);
|
||||
}
|
||||
|
||||
// Simple string with escaped quotes
|
||||
|
|
@ -107,7 +106,7 @@ abstract class SetUtils {
|
|||
}
|
||||
|
||||
/// Pretty prints a number.
|
||||
static String _prettyPrintNumber(num n, int indent) {
|
||||
static String _prettyPrintNumber(num n) {
|
||||
return n.toString();
|
||||
}
|
||||
|
||||
|
|
@ -157,9 +156,16 @@ abstract class SetUtils {
|
|||
|
||||
/// Pretty prints a raw string (for strings containing newlines, $, backslashes, etc.)
|
||||
/// Uses Dart's raw string syntax r"""..."""
|
||||
static String _prettyPrintRawString(String s, int indent) {
|
||||
// Escape triple quotes by replacing """ with ""\"
|
||||
final escaped = s.replaceAll('"""', r'""\\"');
|
||||
return 'r"""$escaped"""';
|
||||
static String _prettyPrintRawString(String s) {
|
||||
if( s == '"'){
|
||||
return "'\"";
|
||||
}
|
||||
if( s.contains('"""') && s.contains("'''") ){
|
||||
return escapeD4rtString(s);
|
||||
}
|
||||
if( s.contains('"""') ){
|
||||
return "r'''$s'''";
|
||||
}
|
||||
return 'r"""$s"""';
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue