diff --git a/lib/widgets/autocompleter.dart b/lib/autocompletion/widget.dart similarity index 58% rename from lib/widgets/autocompleter.dart rename to lib/autocompletion/widget.dart index 449fa597..3359bf41 100644 --- a/lib/widgets/autocompleter.dart +++ b/lib/autocompletion/widget.dart @@ -4,33 +4,29 @@ import 'package:flutter/material.dart'; import 'package:time/time.dart'; -class AutoCompleter extends StatefulWidget { +class AutoCompletionWidget extends StatefulWidget { final FocusNode textFieldFocusNode; final GlobalKey textFieldKey; final TextStyle textFieldStyle; final TextEditingController textController; final Widget child; - final String startToken; - final String endToken; - - AutoCompleter({ + AutoCompletionWidget({ @required this.textFieldFocusNode, @required this.textFieldKey, @required this.textFieldStyle, @required this.textController, @required this.child, - @required this.startToken, - @required this.endToken, }); @override - _AutoCompleterState createState() => _AutoCompleterState(); + _AutoCompletionWidgetState createState() => _AutoCompletionWidgetState(); } -class _AutoCompleterState extends State { +class _AutoCompletionWidgetState extends State { OverlayEntry overlayEntry; - String prevText; + + var autoCompleter = TagsAutoCompleter(); @override void initState() { @@ -52,20 +48,25 @@ class _AutoCompleterState extends State { void _textChanged() { var selection = widget.textController.selection; - var cursorPos = selection.baseOffset; var text = widget.textController.text; - var start = text.lastIndexOf(RegExp(r' |^'), cursorPos - 1) + 1; - var word = text.substring(start, cursorPos); - print('text: $word'); - if (word.startsWith(widget.startToken)) { - _showOverlayTag(context, text.substring(0, cursorPos)); - } else if (word.endsWith(widget.endToken)) { - // Hide when ]] is added - _hideOverlay(); + var prefix = ""; + try { + prefix = + autoCompleter.textChanged(EditorState(text, selection.baseOffset)); + } catch (e) { + print(e); } - prevText = text; + if (prefix.isEmpty) { + _hideOverlay(); + return; + } + + if (prefix == "\n") { + } else { + _showOverlayTag(context, prefix); + } } /// newText is used to calculate where to put the completion box @@ -134,19 +135,28 @@ class _AutoCompleterState extends State { } } +/* /// if endToken is empty, then the token can only be alpha numeric String extractToken( String text, int cursorPos, String startToken, String endToken) { - var start = text.lastIndexOf(RegExp(r' |^'), cursorPos - 1); + var start = text.lastIndexOf(RegExp(r' '), cursorPos - 1); if (start == -1) { - var word = text.substring(0, cursorPos); - if (word.startsWith('[[')) { - return word.substring(2, cursorPos); - } - return ""; + start = 0; + } + print("start: $start"); + + var end = text.indexOf(RegExp(r' |$'), cursorPos); + if (end == -1) { + end = cursorPos; + } + print("end: $end"); + + var word = text.substring(start, end); + if (word.startsWith('[[')) { + return word.substring(2, cursorPos); } - return text; + return ""; } bool enterPressed(String oldText, String newText, int cursorPos) { @@ -160,20 +170,13 @@ bool enterPressed(String oldText, String newText, int cursorPos) { } return false; } +*/ -class CompletionResult { +class EditorState { String text; int cursorPos; - CompletionResult(this.text, this.cursorPos); -} - -CompletionResult completeText(String oldText, String newText, int cursorPos) { - return null; -} - -bool hideAutoCompleter(String oldText, String newText, int cursorPos) { - return false; + EditorState(this.text, this.cursorPos); } // https://levelup.gitconnected.com/flutter-medium-like-text-editor-b41157f50f0e @@ -189,3 +192,92 @@ bool hideAutoCompleter(String oldText, String newText, int cursorPos) { // or an existing wiki link which has the closing brackets // Bug : Show auto-completion on top if no space at the bottom // Bug : Handle physical tab or Enter key + +abstract class AutoCompletionLogic { + /// Return an empty string if the overlay should be hidden + /// Return \n if enter has been pressed + /// Return the prefix if an overlay should be shown + String textChanged(EditorState es); + + EditorState completeText(String text); +} + +/* +class WikiLinksAutoCompleter implements AutoCompletionLogic { + var _oldState = EditorState("", 0); + + bool inBracket1 = false; + bool inBracket2 = false; + bool outBracket2 = false; + + var newText = ""; + + @override + void textChanged(EditorState es) { + var oldState = _oldState; + _oldState = es; + + // This could result in an Add / Remove / Replace + + if (es.text.length > oldState.text.length) { + // Probably an add + if (oldState.cursorPos < es.cursorPos) { + newText = es.text.substring(oldState.cursorPos, es.cursorPos); + return; + } + return; + } + } + + @override + EditorState completeText(String text) { + return null; + } + + @override + bool get enterPressed => false; + + @override + bool get showOverlay => false; +} +*/ + +class TagsAutoCompleter implements AutoCompletionLogic { + var _oldState = EditorState("", 0); + + @override + String textChanged(EditorState es) { + _oldState = es; + + //print("${es.text} ${es.cursorPos}"); + var start = es.text.lastIndexOf(RegExp(r'^|[ #.?!]'), es.cursorPos); + if (start < 0) { + start = 0; + } + + var end = es.text.indexOf(RegExp(r' |$'), es.cursorPos); + if (end == -1) { + end = es.cursorPos; + } + + // print("start end: $start $end"); + var text = es.text.substring(start, end).trim(); + // print("text $text"); + if (!text.startsWith('#')) { + return ""; + } + + return text.substring(1); + } + + @override + EditorState completeText(String text) { + var start = _oldState.text.lastIndexOf(r'#', _oldState.cursorPos); + if (start == -1) { + throw Exception("completeText should not have been called"); + } + + var es = _oldState; + return es; + } +} diff --git a/lib/main_autocomplete.dart b/lib/main_autocomplete.dart index 7468b6cf..7ee861aa 100644 --- a/lib/main_autocomplete.dart +++ b/lib/main_autocomplete.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:gitjournal/widgets/autocompleter.dart'; +import 'package:gitjournal/autocompletion/widget.dart'; void main() => runApp(MyApp()); @@ -52,13 +52,11 @@ class _MyHomePageState extends State { maxLines: 300, ); - textField = AutoCompleter( + textField = AutoCompletionWidget( textFieldStyle: _textFieldStyle, textFieldKey: _textFieldKey, textFieldFocusNode: _focusNode, textController: _textController, - startToken: '[[', - endToken: ']]', child: textField, ); return Scaffold( diff --git a/test/autocompletion/tags_test.dart b/test/autocompletion/tags_test.dart new file mode 100644 index 00000000..94f597dc --- /dev/null +++ b/test/autocompletion/tags_test.dart @@ -0,0 +1,22 @@ +import 'package:test/test.dart'; + +import 'package:gitjournal/autocompletion/widget.dart'; + +void main() { + var c = TagsAutoCompleter(); + + test('Extract second word', () { + var p = c.textChanged(EditorState("Hi #Hel", 7)); + expect(p, "Hel"); + }); + + test('Extract second word - cursor not at end', () { + var p = c.textChanged(EditorState("Hi #Hell", 7)); + expect(p, "Hell"); + }); + + test("Second word with dot", () { + var p = c.textChanged(EditorState("Hi.#Hel", 6)); + expect(p, "Hel"); + }); +} diff --git a/test/autocompleter_test.dart b/test/autocompletion/test.dart similarity index 95% rename from test/autocompleter_test.dart rename to test/autocompletion/test.dart index 4abab153..848b75c2 100644 --- a/test/autocompleter_test.dart +++ b/test/autocompletion/test.dart @@ -1,6 +1,6 @@ -import 'package:test/test.dart'; +/* -import 'package:gitjournal/widgets/autocompleter.dart'; +import 'package:test/test.dart'; void main() { test('Extract word at start', () { @@ -11,7 +11,7 @@ void main() { test('Extract word at start - cursor not at end', () { var result = extractToken("[[Hel", 4, '[[', ']]'); expect(result, "Hel"); - }); + }, solo: true); test('Extract second word', () { var result = extractToken("Hi [[Hel", 8, '[[', ']]'); @@ -43,3 +43,5 @@ void main() { expect(result, "Hello There"); }); } + +*/