diff --git a/CLAUDE.md b/CLAUDE.md index fb77280..9d17b44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 diff --git a/Makefile b/Makefile index bae5bf4..add553f 100644 --- a/Makefile +++ b/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 +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 +build-android-release-container: + $(FLUTTERW) build apk --release -build-linux-debug-container: - ./flutterw build linux --debug +build-linux-debug-container: + $(FLUTTERW) build linux --debug -build-web-debug-container: - ./flutterw build web --debug +build-web-debug-container: + $(FLUTTERW) build web --debug -run-linux-debug-container: - ./flutterw run -d linux +run-linux-debug-container: + $(FLUTTERW) run -d linux -run-web-debug-container: - ./flutterw run --web-port $${WEB_PORT:-8081} -d web-server +run-web-debug-container: + $(FLUTTERW) run --web-port $${WEB_PORT:-8081} -d web-server run-linux-debug-native: flutter run -d linux @@ -50,4 +52,4 @@ ai: run-emulator: flutter emulators --launch Medium_Phone - flutter run -d emulator-5554 \ No newline at end of file + flutter run -d emulator-5554 diff --git a/TODO.md b/TODO.md index df8dbb0..b86211d 100644 --- a/TODO.md +++ b/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). diff --git a/lib/ai/formula_editor.dart b/lib/ai/formula_editor.dart index 8020496..50f980b 100644 --- a/lib/ai/formula_editor.dart +++ b/lib/ai/formula_editor.dart @@ -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 createState() => _FormulaEditorState(); @@ -32,14 +30,14 @@ class _FormulaEditorState extends State { final _formKey = GlobalKey(); late TextEditingController _nameController; late TextEditingController _descriptionController; - late TextEditingController _d4rtCodeController; - + late CodeController _d4rtCodeController; + // Track input variables final List<_InputVariableRowData> _inputVariables = []; - + // Output variable late _OutputVariableRowData _outputVariable; - + bool _isPreviewVisible = false; @override @@ -47,17 +45,19 @@ class _FormulaEditorState extends State { 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( - nameController: TextEditingController(text: input.name), - unit: input.unit, - values: input.values != null ? List.from(input.values!) : null, - )); + _inputVariables.add( + _InputVariableRowData( + nameController: TextEditingController(text: input.name), + unit: input.unit, + values: input.values != null ? List.from(input.values!) : null, + ), + ); } - + // Initialize output variable _outputVariable = _OutputVariableRowData( nameController: TextEditingController(text: widget.formula.output.name), @@ -79,11 +79,13 @@ class _FormulaEditorState extends State { void _addInputVariable() { setState(() { - _inputVariables.add(_InputVariableRowData( - nameController: TextEditingController(text: 'var${_inputVariables.length + 1}'), - unit: null, - values: null, - )); + _inputVariables.add( + _InputVariableRowData( + nameController: TextEditingController(text: 'var${_inputVariables.length + 1}'), + unit: null, + values: null, + ), + ); }); } @@ -117,10 +119,7 @@ class _FormulaEditorState extends State { 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 { try { final input = []; 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 { 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 { 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 { 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 { 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 { 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 { 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 { final unitSpec = widget.corpus.getUnit(unit); return DropdownMenuItem( 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 { 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 { 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 { final unitSpec = widget.corpus.getUnit(unit); return DropdownMenuItem( 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 { Widget _buildD4rtCodeSection() { return Card( - 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: Expanded( + child: Padding( + padding: const EdgeInsets.all(16.0), + 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? 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}); } diff --git a/lib/database/database_service.dart b/lib/database/database_service.dart index 532a38e..1767518 100644 --- a/lib/database/database_service.dart +++ b/lib/database/database_service.dart @@ -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'); diff --git a/lib/set_utils.dart b/lib/set_utils.dart index 2c2ca0b..26140e0 100644 --- a/lib/set_utils.dart +++ b/lib/set_utils.dart @@ -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"""'; } } \ No newline at end of file