mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-28 09:47:35 +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';
|
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;
|
||||||
if (word.startsWith('[[')) {
|
}
|
||||||
return word.substring(2, cursorPos);
|
print("start: $start");
|
||||||
}
|
|
||||||
return "";
|
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) {
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -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(
|
||||||
|
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() {
|
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");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*/
|
Reference in New Issue
Block a user