Skip to content

Commit 6984b78

Browse files
authored
Search enhancements (singerdmx#1341)
1 parent f74ab59 commit 6984b78

File tree

2 files changed

+169
-80
lines changed

2 files changed

+169
-80
lines changed

lib/src/models/documents/document.dart

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,16 +195,21 @@ class Document {
195195
return block.queryChild(res.offset, true);
196196
}
197197

198-
/// Search the whole document for any substring matching the pattern
199-
/// Returns the offsets that matches the pattern
200-
List<int> search(Pattern other) {
198+
/// Search given [substring] in the whole document
199+
/// Supports [caseSensitive] and [wholeWord] options
200+
/// Returns correspondent offsets
201+
List<int> search(
202+
String substring, {
203+
bool caseSensitive = false,
204+
bool wholeWord = false,
205+
}) {
201206
final matches = <int>[];
202207
for (final node in _root.children) {
203208
if (node is Line) {
204-
_searchLine(other, node, matches);
209+
_searchLine(substring, caseSensitive, wholeWord, node, matches);
205210
} else if (node is Block) {
206211
for (final line in Iterable.castFrom<dynamic, Line>(node.children)) {
207-
_searchLine(other, line, matches);
212+
_searchLine(substring, caseSensitive, wholeWord, line, matches);
208213
}
209214
} else {
210215
throw StateError('Unreachable.');
@@ -213,10 +218,22 @@ class Document {
213218
return matches;
214219
}
215220

216-
void _searchLine(Pattern other, Line line, List<int> matches) {
221+
void _searchLine(
222+
String substring,
223+
bool caseSensitive,
224+
bool wholeWord,
225+
Line line,
226+
List<int> matches,
227+
) {
217228
var index = -1;
218-
while (true) {
219-
index = line.toPlainText().indexOf(other, index + 1);
229+
final lineText = line.toPlainText();
230+
var pattern = RegExp.escape(substring);
231+
if (wholeWord) {
232+
pattern = r'\b' + pattern + r'\b';
233+
}
234+
final searchExpression = RegExp(pattern, caseSensitive: caseSensitive);
235+
while (true) {
236+
index = lineText.indexOf(searchExpression, index + 1);
220237
if (index < 0) {
221238
break;
222239
}

lib/src/widgets/toolbar/search_dialog.dart

Lines changed: 144 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class _SearchDialogState extends State<SearchDialog> {
2323
late TextEditingController _controller;
2424
late List<int>? _offsets;
2525
late int _index;
26+
bool _case_sensitive = false;
27+
bool _whole_word = false;
2628

2729
@override
2830
void initState() {
@@ -35,87 +37,113 @@ class _SearchDialogState extends State<SearchDialog> {
3537

3638
@override
3739
Widget build(BuildContext context) {
38-
return StatefulBuilder(builder: (context, setState) {
39-
var label = '';
40-
if (_offsets != null) {
41-
label = '${_offsets!.length} ${'matches'.i18n}';
42-
if (_offsets!.isNotEmpty) {
43-
label += ', ${'showing match'.i18n} ${_index + 1}';
44-
}
40+
var matchShown = '';
41+
if (_offsets != null) {
42+
if (_offsets!.isEmpty) {
43+
matchShown = '0/0';
44+
} else {
45+
matchShown = '${_index + 1}/${_offsets!.length}';
4546
}
46-
return AlertDialog(
47-
backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
48-
content: Container(
49-
height: 100,
50-
child: Column(
51-
children: [
52-
TextField(
53-
keyboardType: TextInputType.multiline,
54-
style: widget.dialogTheme?.inputTextStyle,
55-
decoration: InputDecoration(
56-
labelText: 'Search'.i18n,
57-
labelStyle: widget.dialogTheme?.labelTextStyle,
58-
floatingLabelStyle: widget.dialogTheme?.labelTextStyle),
59-
autofocus: true,
60-
onChanged: _textChanged,
61-
controller: _controller,
47+
}
48+
49+
return Dialog(
50+
shape: RoundedRectangleBorder(
51+
borderRadius: BorderRadius.circular(5),
52+
),
53+
backgroundColor: widget.dialogTheme?.dialogBackgroundColor,
54+
alignment: Alignment.bottomCenter,
55+
insetPadding: EdgeInsets.zero,
56+
child: SizedBox(
57+
height: 45,
58+
child: Row(
59+
children: [
60+
Tooltip(
61+
message: 'Case sensitivity and whole word search',
62+
child: ToggleButtons(
63+
onPressed: (int index) {
64+
if (index == 0) {
65+
_changeCaseSensitivity();
66+
} else if (index == 1) {
67+
_changeWholeWord();
68+
}
69+
},
70+
borderRadius: const BorderRadius.all(Radius.circular(2)),
71+
isSelected: [_case_sensitive, _whole_word],
72+
children: const [
73+
Text(
74+
'\u0391\u03b1',
75+
style: TextStyle(
76+
fontFamily: 'MaterialIcons',
77+
fontSize: 24,
78+
),
79+
),
80+
Text(
81+
'\u201c\u2026\u201d',
82+
style: TextStyle(
83+
fontFamily: 'MaterialIcons',
84+
fontSize: 24,
85+
),
86+
),
87+
],
6288
),
63-
if (_offsets != null)
64-
Padding(
65-
padding: const EdgeInsets.all(8),
66-
child: Text(label, textAlign: TextAlign.left),
89+
),
90+
Expanded(
91+
child: Padding(
92+
padding: const EdgeInsets.only(bottom: 12, left: 5),
93+
child: TextField(
94+
style: widget.dialogTheme?.inputTextStyle,
95+
decoration: InputDecoration(
96+
isDense: true,
97+
suffixText: (_offsets != null) ? matchShown : '',
98+
suffixStyle: widget.dialogTheme?.labelTextStyle,
99+
),
100+
autofocus: true,
101+
onChanged: _textChanged,
102+
textInputAction: TextInputAction.done,
103+
onEditingComplete: _findText,
104+
controller: _controller,
67105
),
68-
],
69-
),
70-
),
71-
actions: [
72-
if (_offsets != null && _offsets!.isNotEmpty && _index > 0)
73-
TextButton(
74-
onPressed: () {
75-
setState(() {
76-
_index -= 1;
77-
});
78-
_moveToPosition();
79-
},
80-
child: Text(
81-
'Prev'.i18n,
82-
style: widget.dialogTheme?.labelTextStyle,
83106
),
84107
),
85-
if (_offsets != null &&
86-
_offsets!.isNotEmpty &&
87-
_index < _offsets!.length - 1)
88-
TextButton(
89-
onPressed: () {
90-
setState(() {
91-
_index += 1;
92-
});
93-
_moveToPosition();
94-
},
95-
child: Text(
96-
'Next'.i18n,
97-
style: widget.dialogTheme?.labelTextStyle,
108+
if (_offsets == null)
109+
IconButton(
110+
icon: const Icon(Icons.search),
111+
tooltip: 'Find text',
112+
onPressed: _findText,
98113
),
99-
),
100-
if (_offsets == null && _text.isNotEmpty)
101-
TextButton(
102-
onPressed: () {
103-
setState(() {
104-
_offsets = widget.controller.document.search(_text);
105-
_index = 0;
106-
});
107-
if (_offsets!.isNotEmpty) {
108-
_moveToPosition();
109-
}
110-
},
111-
child: Text(
112-
'Ok'.i18n,
113-
style: widget.dialogTheme?.labelTextStyle,
114+
if (_offsets != null)
115+
IconButton(
116+
icon: const Icon(Icons.keyboard_arrow_up),
117+
tooltip: 'Move to previous occurrence',
118+
onPressed: (_offsets!.isNotEmpty) ? _moveToPrevious : null,
114119
),
115-
),
116-
],
120+
if (_offsets != null)
121+
IconButton(
122+
icon: const Icon(Icons.keyboard_arrow_down),
123+
tooltip: 'Move to next occurrence',
124+
onPressed: (_offsets!.isNotEmpty) ? _moveToNext : null,
125+
),
126+
],
127+
),
128+
),
129+
);
130+
}
131+
132+
void _findText() {
133+
if (_text.isEmpty) {
134+
return;
135+
}
136+
setState(() {
137+
_offsets = widget.controller.document.search(
138+
_text,
139+
caseSensitive: _case_sensitive,
140+
wholeWord: _whole_word,
117141
);
142+
_index = 0;
118143
});
144+
if (_offsets!.isNotEmpty) {
145+
_moveToPosition();
146+
}
119147
}
120148

121149
void _moveToPosition() {
@@ -126,11 +154,55 @@ class _SearchDialogState extends State<SearchDialog> {
126154
ChangeSource.LOCAL);
127155
}
128156

157+
void _moveToPrevious() {
158+
if (_offsets!.isEmpty) {
159+
return;
160+
}
161+
setState(() {
162+
if (_index > 0) {
163+
_index -= 1;
164+
} else {
165+
_index = _offsets!.length - 1;
166+
}
167+
});
168+
_moveToPosition();
169+
}
170+
171+
void _moveToNext() {
172+
if (_offsets!.isEmpty) {
173+
return;
174+
}
175+
setState(() {
176+
if (_index < _offsets!.length - 1) {
177+
_index += 1;
178+
} else {
179+
_index = 0;
180+
}
181+
});
182+
_moveToPosition();
183+
}
184+
129185
void _textChanged(String value) {
130186
setState(() {
131187
_text = value;
132188
_offsets = null;
133189
_index = 0;
134190
});
135191
}
192+
193+
void _changeCaseSensitivity() {
194+
setState(() {
195+
_case_sensitive = !_case_sensitive;
196+
_offsets = null;
197+
_index = 0;
198+
});
199+
}
200+
201+
void _changeWholeWord() {
202+
setState(() {
203+
_whole_word = !_whole_word;
204+
_offsets = null;
205+
_index = 0;
206+
});
207+
}
136208
}

0 commit comments

Comments
 (0)