@@ -6,20 +6,30 @@ import 'package:collection/collection.dart';
6
6
import 'package:flutter/material.dart' ;
7
7
import 'package:flutter/services.dart' ;
8
8
import 'package:highlight/highlight_core.dart' ;
9
+ import 'package:meta/meta.dart' ;
9
10
10
11
import '../../flutter_code_editor.dart' ;
11
12
import '../autocomplete/autocompleter.dart' ;
12
13
import '../code/code_edit_result.dart' ;
14
+ import '../code/key_event.dart' ;
13
15
import '../history/code_history_controller.dart' ;
14
16
import '../history/code_history_record.dart' ;
17
+ import '../search/controller.dart' ;
18
+ import '../search/result.dart' ;
19
+ import '../search/search_navigation_controller.dart' ;
20
+ import '../search/settings_controller.dart' ;
15
21
import '../single_line_comments/parser/single_line_comments.dart' ;
16
22
import '../wip/autocomplete/popup_controller.dart' ;
17
23
import 'actions/comment_uncomment.dart' ;
18
24
import 'actions/copy.dart' ;
25
+ import 'actions/dismiss.dart' ;
26
+ import 'actions/enter_key.dart' ;
19
27
import 'actions/indent.dart' ;
20
28
import 'actions/outdent.dart' ;
21
29
import 'actions/redo.dart' ;
30
+ import 'actions/search.dart' ;
22
31
import 'actions/undo.dart' ;
32
+ import 'search_result_highlighted_builder.dart' ;
23
33
import 'span_builder.dart' ;
24
34
25
35
class CodeController extends TextEditingController {
@@ -80,6 +90,11 @@ class CodeController extends TextEditingController {
80
90
///If it is not empty, all another code except specified will be hidden.
81
91
Set <String > _visibleSectionNames = {};
82
92
93
+ /// Makes the text un-editable, but allows to set the full text.
94
+ /// Focusing and moving the selection inside of a [CodeField] will
95
+ /// still be possible.
96
+ final bool readonly;
97
+
83
98
String get languageId => _languageId;
84
99
85
100
Code _code;
@@ -91,6 +106,17 @@ class CodeController extends TextEditingController {
91
106
final autocompleter = Autocompleter ();
92
107
late final historyController = CodeHistoryController (codeController: this );
93
108
109
+ @internal
110
+ late final searchController = CodeSearchController (codeController: this );
111
+
112
+ SearchSettingsController get _searchSettingsController =>
113
+ searchController.settingsController;
114
+ SearchNavigationController get _searchNavigationController =>
115
+ searchController.navigationController;
116
+
117
+ @internal
118
+ SearchResult fullSearchResult = SearchResult .empty;
119
+
94
120
/// The last [TextSpan] returned from [buildTextSpan] .
95
121
///
96
122
/// This can be used in tests to make sure that the updated text was actually
@@ -105,6 +131,9 @@ class CodeController extends TextEditingController {
105
131
OutdentIntent : OutdentIntentAction (controller: this ),
106
132
RedoTextIntent : RedoAction (controller: this ),
107
133
UndoTextIntent : UndoAction (controller: this ),
134
+ SearchIntent : SearchAction (controller: this ),
135
+ DismissIntent : CustomDismissAction (controller: this ),
136
+ EnterKeyIntent : EnterKeyAction (controller: this ),
108
137
};
109
138
110
139
CodeController ({
@@ -118,6 +147,7 @@ class CodeController extends TextEditingController {
118
147
Map <String , TextStyle >? theme,
119
148
this .analysisResult = const AnalysisResult (issues: []),
120
149
this .patternMap,
150
+ this .readonly = false ,
121
151
this .stringMap,
122
152
this .params = const EditorParams (),
123
153
this .modifiers = const [
@@ -135,6 +165,11 @@ class CodeController extends TextEditingController {
135
165
fullText = text ?? '' ;
136
166
137
167
addListener (_scheduleAnalysis);
168
+ addListener (_updateSearchResult);
169
+ _searchSettingsController.addListener (_updateSearchResult);
170
+ // This listener is called when search controller notifies about
171
+ // showing or hiding the search popup.
172
+ searchController.addListener (_updateSearchResult);
138
173
139
174
// Create modifier map
140
175
for (final el in modifiers) {
@@ -158,6 +193,20 @@ class CodeController extends TextEditingController {
158
193
unawaited (analyzeCode ());
159
194
}
160
195
196
+ void _updateSearchResult () {
197
+ final result = searchController.search (
198
+ code,
199
+ settings: _searchSettingsController.value,
200
+ );
201
+
202
+ if (result == fullSearchResult) {
203
+ return ;
204
+ }
205
+
206
+ fullSearchResult = result;
207
+ notifyListeners ();
208
+ }
209
+
161
210
void _scheduleAnalysis () {
162
211
_debounce? .cancel ();
163
212
@@ -268,6 +317,11 @@ class CodeController extends TextEditingController {
268
317
}
269
318
270
319
KeyEventResult _onKeyDownRepeat (KeyEvent event) {
320
+ if (event.isCtrlF (HardwareKeyboard .instance.logicalKeysPressed)) {
321
+ showSearch ();
322
+ return KeyEventResult .handled;
323
+ }
324
+
271
325
if (popupController.shouldShow) {
272
326
if (event.logicalKey == LogicalKeyboardKey .arrowUp) {
273
327
popupController.scrollByArrow (ScrollDirection .up);
@@ -277,19 +331,34 @@ class CodeController extends TextEditingController {
277
331
popupController.scrollByArrow (ScrollDirection .down);
278
332
return KeyEventResult .handled;
279
333
}
280
- if (event.logicalKey == LogicalKeyboardKey .enter) {
281
- insertSelectedWord ();
282
- return KeyEventResult .handled;
283
- }
284
- if (event.logicalKey == LogicalKeyboardKey .escape) {
285
- popupController.hide ();
286
- return KeyEventResult .handled;
287
- }
288
334
}
289
335
290
336
return KeyEventResult .ignored; // The framework will handle.
291
337
}
292
338
339
+ void onEnterKeyAction () {
340
+ if (popupController.shouldShow) {
341
+ insertSelectedWord ();
342
+ return ;
343
+ }
344
+
345
+ final currentMatchIndex =
346
+ _searchNavigationController.value.currentMatchIndex;
347
+
348
+ if (searchController.shouldShow && currentMatchIndex != null ) {
349
+ final fullSelection = code.hiddenRanges.recoverSelection (selection);
350
+ final currentMatch = fullSearchResult.matches[currentMatchIndex];
351
+
352
+ if (fullSelection.start == currentMatch.start &&
353
+ fullSelection.end == currentMatch.end) {
354
+ _searchNavigationController.moveNext ();
355
+ return ;
356
+ }
357
+ }
358
+
359
+ insertStr ('\n ' );
360
+ }
361
+
293
362
/// Inserts the word selected from the list of completions
294
363
void insertSelectedWord () {
295
364
final previousSelection = selection;
@@ -585,6 +654,10 @@ class CodeController extends TextEditingController {
585
654
void modifySelectedLines (
586
655
String Function (String line) modifierCallback,
587
656
) {
657
+ if (readonly) {
658
+ return ;
659
+ }
660
+
588
661
if (selection.start == - 1 || selection.end == - 1 ) {
589
662
return ;
590
663
}
@@ -660,6 +733,10 @@ class CodeController extends TextEditingController {
660
733
Code get code => _code;
661
734
662
735
CodeEditResult ? _getEditResultNotBreakingReadOnly (TextEditingValue newValue) {
736
+ if (readonly) {
737
+ return null ;
738
+ }
739
+
663
740
final editResult = _code.getEditResult (value.selection, newValue);
664
741
if (! _code.isReadOnlyInLineRange (editResult.linesChanged)) {
665
742
return editResult;
@@ -802,8 +879,23 @@ class CodeController extends TextEditingController {
802
879
TextStyle ? style,
803
880
bool ? withComposing,
804
881
}) {
882
+ final spanBeforeSearch = _createTextSpan (
883
+ context: context,
884
+ style: style,
885
+ );
886
+
887
+ final visibleSearchResult =
888
+ _code.hiddenRanges.cutSearchResult (fullSearchResult);
889
+
805
890
// TODO(alexeyinkin): Return cached if the value did not change, https://github.com/akvelon/flutter-code-editor/issues/127
806
- return lastTextSpan = _createTextSpan (context: context, style: style);
891
+ lastTextSpan = SearchResultHighlightedBuilder (
892
+ searchResult: visibleSearchResult,
893
+ rootStyle: style,
894
+ textSpan: spanBeforeSearch,
895
+ searchNavigationState: _searchNavigationController.value,
896
+ ).build ();
897
+
898
+ return lastTextSpan! ;
807
899
}
808
900
809
901
TextSpan _createTextSpan ({
@@ -864,10 +956,30 @@ class CodeController extends TextEditingController {
864
956
return CodeTheme .of (context) ?? CodeThemeData ();
865
957
}
866
958
959
+ void dismiss () {
960
+ _dismissSuggestions ();
961
+ _dismissSearch ();
962
+ }
963
+
964
+ void _dismissSuggestions () {
965
+ if (popupController.enabled) {
966
+ popupController.hide ();
967
+ }
968
+ }
969
+
970
+ void _dismissSearch () {
971
+ searchController.hideSearch (returnFocusToCodeField: true );
972
+ }
973
+
974
+ void showSearch () {
975
+ searchController.showSearch ();
976
+ }
977
+
867
978
@override
868
979
void dispose () {
869
980
_debounce? .cancel ();
870
981
historyController.dispose ();
982
+ searchController.dispose ();
871
983
872
984
super .dispose ();
873
985
}
0 commit comments