mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-27 09:06:43 +08:00
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:
@ -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<AutoCompleter> {
|
||||
class _AutoCompletionWidgetState extends State<AutoCompletionWidget> {
|
||||
OverlayEntry overlayEntry;
|
||||
String prevText;
|
||||
|
||||
var autoCompleter = TagsAutoCompleter();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -52,20 +48,25 @@ class _AutoCompleterState extends State<AutoCompleter> {
|
||||
|
||||
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<AutoCompleter> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// 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;
|
||||
}
|
||||
}
|
@ -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<MyHomePage> {
|
||||
maxLines: 300,
|
||||
);
|
||||
|
||||
textField = AutoCompleter(
|
||||
textField = AutoCompletionWidget(
|
||||
textFieldStyle: _textFieldStyle,
|
||||
textFieldKey: _textFieldKey,
|
||||
textFieldFocusNode: _focusNode,
|
||||
textController: _textController,
|
||||
startToken: '[[',
|
||||
endToken: ']]',
|
||||
child: textField,
|
||||
);
|
||||
return Scaffold(
|
||||
|
22
test/autocompletion/tags_test.dart
Normal file
22
test/autocompletion/tags_test.dart
Normal 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");
|
||||
});
|
||||
}
|
@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
*/
|
Reference in New Issue
Block a user