AutoCompletion: Add tests for tags autocompletion

Also move the file. I tried using the same code base for both the types
of auto-completion and this has resulted in a huge mess. It's best to
keep the logic completely seperate for the two.
This commit is contained in:
Vishesh Handa
2021-01-28 14:54:59 +01:00
parent 07947d637a
commit 79207d9422
4 changed files with 158 additions and 44 deletions

View File

@ -4,33 +4,29 @@ import 'package:flutter/material.dart';
import 'package:time/time.dart'; import 'package:time/time.dart';
class AutoCompleter extends StatefulWidget { class AutoCompletionWidget extends StatefulWidget {
final FocusNode textFieldFocusNode; final FocusNode textFieldFocusNode;
final GlobalKey textFieldKey; final GlobalKey textFieldKey;
final TextStyle textFieldStyle; final TextStyle textFieldStyle;
final TextEditingController textController; final TextEditingController textController;
final Widget child; final Widget child;
final String startToken; AutoCompletionWidget({
final String endToken;
AutoCompleter({
@required this.textFieldFocusNode, @required this.textFieldFocusNode,
@required this.textFieldKey, @required this.textFieldKey,
@required this.textFieldStyle, @required this.textFieldStyle,
@required this.textController, @required this.textController,
@required this.child, @required this.child,
@required this.startToken,
@required this.endToken,
}); });
@override @override
_AutoCompleterState createState() => _AutoCompleterState(); _AutoCompletionWidgetState createState() => _AutoCompletionWidgetState();
} }
class _AutoCompleterState extends State<AutoCompleter> { class _AutoCompletionWidgetState extends State<AutoCompletionWidget> {
OverlayEntry overlayEntry; OverlayEntry overlayEntry;
String prevText;
var autoCompleter = TagsAutoCompleter();
@override @override
void initState() { void initState() {
@ -52,20 +48,25 @@ class _AutoCompleterState extends State<AutoCompleter> {
void _textChanged() { void _textChanged() {
var selection = widget.textController.selection; var selection = widget.textController.selection;
var cursorPos = selection.baseOffset;
var text = widget.textController.text; var text = widget.textController.text;
var start = text.lastIndexOf(RegExp(r' |^'), cursorPos - 1) + 1; var prefix = "";
var word = text.substring(start, cursorPos); try {
print('text: $word'); prefix =
if (word.startsWith(widget.startToken)) { autoCompleter.textChanged(EditorState(text, selection.baseOffset));
_showOverlayTag(context, text.substring(0, cursorPos)); } catch (e) {
} else if (word.endsWith(widget.endToken)) { print(e);
// Hide when ]] is added
_hideOverlay();
} }
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 /// newText is used to calculate where to put the completion box
@ -134,19 +135,28 @@ class _AutoCompleterState extends State<AutoCompleter> {
} }
} }
/*
/// if endToken is empty, then the token can only be alpha numeric /// if endToken is empty, then the token can only be alpha numeric
String extractToken( String extractToken(
String text, int cursorPos, String startToken, String endToken) { 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) { if (start == -1) {
var word = text.substring(0, cursorPos); 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('[[')) { if (word.startsWith('[[')) {
return word.substring(2, cursorPos); return word.substring(2, cursorPos);
} }
return "";
}
return text; return "";
} }
bool enterPressed(String oldText, String newText, int cursorPos) { bool enterPressed(String oldText, String newText, int cursorPos) {
@ -160,20 +170,13 @@ bool enterPressed(String oldText, String newText, int cursorPos) {
} }
return false; return false;
} }
*/
class CompletionResult { class EditorState {
String text; String text;
int cursorPos; int cursorPos;
CompletionResult(this.text, this.cursorPos); EditorState(this.text, this.cursorPos);
}
CompletionResult completeText(String oldText, String newText, int cursorPos) {
return null;
}
bool hideAutoCompleter(String oldText, String newText, int cursorPos) {
return false;
} }
// https://levelup.gitconnected.com/flutter-medium-like-text-editor-b41157f50f0e // 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 // or an existing wiki link which has the closing brackets
// Bug : Show auto-completion on top if no space at the bottom // Bug : Show auto-completion on top if no space at the bottom
// Bug : Handle physical tab or Enter key // 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;
}
}

View File

@ -2,7 +2,7 @@ import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gitjournal/widgets/autocompleter.dart'; import 'package:gitjournal/autocompletion/widget.dart';
void main() => runApp(MyApp()); void main() => runApp(MyApp());
@ -52,13 +52,11 @@ class _MyHomePageState extends State<MyHomePage> {
maxLines: 300, maxLines: 300,
); );
textField = AutoCompleter( textField = AutoCompletionWidget(
textFieldStyle: _textFieldStyle, textFieldStyle: _textFieldStyle,
textFieldKey: _textFieldKey, textFieldKey: _textFieldKey,
textFieldFocusNode: _focusNode, textFieldFocusNode: _focusNode,
textController: _textController, textController: _textController,
startToken: '[[',
endToken: ']]',
child: textField, child: textField,
); );
return Scaffold( return Scaffold(

View File

@ -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");
});
}

View File

@ -1,6 +1,6 @@
import 'package:test/test.dart'; /*
import 'package:gitjournal/widgets/autocompleter.dart'; import 'package:test/test.dart';
void main() { void main() {
test('Extract word at start', () { test('Extract word at start', () {
@ -11,7 +11,7 @@ void main() {
test('Extract word at start - cursor not at end', () { test('Extract word at start - cursor not at end', () {
var result = extractToken("[[Hel", 4, '[[', ']]'); var result = extractToken("[[Hel", 4, '[[', ']]');
expect(result, "Hel"); expect(result, "Hel");
}); }, solo: true);
test('Extract second word', () { test('Extract second word', () {
var result = extractToken("Hi [[Hel", 8, '[[', ']]'); var result = extractToken("Hi [[Hel", 8, '[[', ']]');
@ -43,3 +43,5 @@ void main() {
expect(result, "Hello There"); expect(result, "Hello There");
}); });
} }
*/