Search enhancements (#1341)

pull/1342/head
Alspb 2 years ago committed by GitHub
parent f74ab592ad
commit 6984b78d7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 33
      lib/src/models/documents/document.dart
  2. 216
      lib/src/widgets/toolbar/search_dialog.dart

@ -195,16 +195,21 @@ class Document {
return block.queryChild(res.offset, true);
}
/// Search the whole document for any substring matching the pattern
/// Returns the offsets that matches the pattern
List<int> search(Pattern other) {
/// Search given [substring] in the whole document
/// Supports [caseSensitive] and [wholeWord] options
/// Returns correspondent offsets
List<int> search(
String substring, {
bool caseSensitive = false,
bool wholeWord = false,
}) {
final matches = <int>[];
for (final node in _root.children) {
if (node is Line) {
_searchLine(other, node, matches);
_searchLine(substring, caseSensitive, wholeWord, node, matches);
} else if (node is Block) {
for (final line in Iterable.castFrom<dynamic, Line>(node.children)) {
_searchLine(other, line, matches);
_searchLine(substring, caseSensitive, wholeWord, line, matches);
}
} else {
throw StateError('Unreachable.');
@ -213,10 +218,22 @@ class Document {
return matches;
}
void _searchLine(Pattern other, Line line, List<int> matches) {
void _searchLine(
String substring,
bool caseSensitive,
bool wholeWord,
Line line,
List<int> matches,
) {
var index = -1;
while (true) {
index = line.toPlainText().indexOf(other, index + 1);
final lineText = line.toPlainText();
var pattern = RegExp.escape(substring);
if (wholeWord) {
pattern = r'\b' + pattern + r'\b';
}
final searchExpression = RegExp(pattern, caseSensitive: caseSensitive);
while (true) {
index = lineText.indexOf(searchExpression, index + 1);
if (index < 0) {
break;
}

@ -23,6 +23,8 @@ class _SearchDialogState extends State<SearchDialog> {
late TextEditingController _controller;
late List<int>? _offsets;
late int _index;
bool _case_sensitive = false;
bool _whole_word = false;
@override
void initState() {
@ -35,87 +37,113 @@ class _SearchDialogState extends State<SearchDialog> {
@override
Widget build(BuildContext context) {
return StatefulBuilder(builder: (context, setState) {
var label = '';
if (_offsets != null) {
label = '${_offsets!.length} ${'matches'.i18n}';
if (_offsets!.isNotEmpty) {
label += ', ${'showing match'.i18n} ${_index + 1}';
}
var matchShown = '';
if (_offsets != null) {
if (_offsets!.isEmpty) {
matchShown = '0/0';
} else {
matchShown = '${_index + 1}/${_offsets!.length}';
}
return AlertDialog(
backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
content: Container(
height: 100,
child: Column(
children: [
TextField(
keyboardType: TextInputType.multiline,
style: widget.dialogTheme?.inputTextStyle,
decoration: InputDecoration(
labelText: 'Search'.i18n,
labelStyle: widget.dialogTheme?.labelTextStyle,
floatingLabelStyle: widget.dialogTheme?.labelTextStyle),
autofocus: true,
onChanged: _textChanged,
controller: _controller,
}
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5),
),
backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
alignment: Alignment.bottomCenter,
insetPadding: EdgeInsets.zero,
child: SizedBox(
height: 45,
child: Row(
children: [
Tooltip(
message: 'Case sensitivity and whole word search',
child: ToggleButtons(
onPressed: (int index) {
if (index == 0) {
_changeCaseSensitivity();
} else if (index == 1) {
_changeWholeWord();
}
},
borderRadius: const BorderRadius.all(Radius.circular(2)),
isSelected: [_case_sensitive, _whole_word],
children: const [
Text(
'\u0391\u03b1',
style: TextStyle(
fontFamily: 'MaterialIcons',
fontSize: 24,
),
),
Text(
'\u201c\u2026\u201d',
style: TextStyle(
fontFamily: 'MaterialIcons',
fontSize: 24,
),
),
],
),
if (_offsets != null)
Padding(
padding: const EdgeInsets.all(8),
child: Text(label, textAlign: TextAlign.left),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(bottom: 12, left: 5),
child: TextField(
style: widget.dialogTheme?.inputTextStyle,
decoration: InputDecoration(
isDense: true,
suffixText: (_offsets != null) ? matchShown : '',
suffixStyle: widget.dialogTheme?.labelTextStyle,
),
autofocus: true,
onChanged: _textChanged,
textInputAction: TextInputAction.done,
onEditingComplete: _findText,
controller: _controller,
),
],
),
),
actions: [
if (_offsets != null && _offsets!.isNotEmpty && _index > 0)
TextButton(
onPressed: () {
setState(() {
_index -= 1;
});
_moveToPosition();
},
child: Text(
'Prev'.i18n,
style: widget.dialogTheme?.labelTextStyle,
),
),
if (_offsets != null &&
_offsets!.isNotEmpty &&
_index < _offsets!.length - 1)
TextButton(
onPressed: () {
setState(() {
_index += 1;
});
_moveToPosition();
},
child: Text(
'Next'.i18n,
style: widget.dialogTheme?.labelTextStyle,
if (_offsets == null)
IconButton(
icon: const Icon(Icons.search),
tooltip: 'Find text',
onPressed: _findText,
),
),
if (_offsets == null && _text.isNotEmpty)
TextButton(
onPressed: () {
setState(() {
_offsets = widget.controller.document.search(_text);
_index = 0;
});
if (_offsets!.isNotEmpty) {
_moveToPosition();
}
},
child: Text(
'Ok'.i18n,
style: widget.dialogTheme?.labelTextStyle,
if (_offsets != null)
IconButton(
icon: const Icon(Icons.keyboard_arrow_up),
tooltip: 'Move to previous occurrence',
onPressed: (_offsets!.isNotEmpty) ? _moveToPrevious : null,
),
),
],
if (_offsets != null)
IconButton(
icon: const Icon(Icons.keyboard_arrow_down),
tooltip: 'Move to next occurrence',
onPressed: (_offsets!.isNotEmpty) ? _moveToNext : null,
),
],
),
),
);
}
void _findText() {
if (_text.isEmpty) {
return;
}
setState(() {
_offsets = widget.controller.document.search(
_text,
caseSensitive: _case_sensitive,
wholeWord: _whole_word,
);
_index = 0;
});
if (_offsets!.isNotEmpty) {
_moveToPosition();
}
}
void _moveToPosition() {
@ -126,6 +154,34 @@ class _SearchDialogState extends State<SearchDialog> {
ChangeSource.LOCAL);
}
void _moveToPrevious() {
if (_offsets!.isEmpty) {
return;
}
setState(() {
if (_index > 0) {
_index -= 1;
} else {
_index = _offsets!.length - 1;
}
});
_moveToPosition();
}
void _moveToNext() {
if (_offsets!.isEmpty) {
return;
}
setState(() {
if (_index < _offsets!.length - 1) {
_index += 1;
} else {
_index = 0;
}
});
_moveToPosition();
}
void _textChanged(String value) {
setState(() {
_text = value;
@ -133,4 +189,20 @@ class _SearchDialogState extends State<SearchDialog> {
_index = 0;
});
}
void _changeCaseSensitivity() {
setState(() {
_case_sensitive = !_case_sensitive;
_offsets = null;
_index = 0;
});
}
void _changeWholeWord() {
setState(() {
_whole_word = !_whole_word;
_offsets = null;
_index = 0;
});
}
}

Loading…
Cancel
Save