@@ -6,21 +6,31 @@ import 'package:collection/collection.dart';
66import 'package:flutter/material.dart' ;
77import 'package:flutter/services.dart' ;
88import 'package:highlight/highlight_core.dart' ;
9+ import 'package:meta/meta.dart' ;
910
1011import '../../flutter_code_editor.dart' ;
1112import '../autocomplete/autocompleter.dart' ;
1213import '../code/code_edit_result.dart' ;
14+ import '../code/key_event.dart' ;
1315import '../code_modifiers/insertion.dart' ;
1416import '../history/code_history_controller.dart' ;
1517import '../history/code_history_record.dart' ;
18+ import '../search/controller.dart' ;
19+ import '../search/result.dart' ;
20+ import '../search/search_navigation_controller.dart' ;
21+ import '../search/settings_controller.dart' ;
1622import '../single_line_comments/parser/single_line_comments.dart' ;
1723import '../wip/autocomplete/popup_controller.dart' ;
1824import 'actions/comment_uncomment.dart' ;
1925import 'actions/copy.dart' ;
26+ import 'actions/dismiss.dart' ;
27+ import 'actions/enter_key.dart' ;
2028import 'actions/indent.dart' ;
2129import 'actions/outdent.dart' ;
2230import 'actions/redo.dart' ;
31+ import 'actions/search.dart' ;
2332import 'actions/undo.dart' ;
33+ import 'search_result_highlighted_builder.dart' ;
2434import 'span_builder.dart' ;
2535
2636class CodeController extends TextEditingController {
@@ -81,6 +91,11 @@ class CodeController extends TextEditingController {
8191 ///If it is not empty, all another code except specified will be hidden.
8292 Set <String > _visibleSectionNames = {};
8393
94+ /// Makes the text un-editable, but allows to set the full text.
95+ /// Focusing and moving the selection inside of a [CodeField] will
96+ /// still be possible.
97+ final bool readOnly;
98+
8499 String get languageId => _languageId;
85100
86101 Code _code;
@@ -92,6 +107,17 @@ class CodeController extends TextEditingController {
92107 final autocompleter = Autocompleter ();
93108 late final historyController = CodeHistoryController (codeController: this );
94109
110+ @internal
111+ late final searchController = CodeSearchController (codeController: this );
112+
113+ SearchSettingsController get _searchSettingsController =>
114+ searchController.settingsController;
115+ SearchNavigationController get _searchNavigationController =>
116+ searchController.navigationController;
117+
118+ @internal
119+ SearchResult fullSearchResult = SearchResult .empty;
120+
95121 /// The last [TextSpan] returned from [buildTextSpan] .
96122 ///
97123 /// This can be used in tests to make sure that the updated text was actually
@@ -106,6 +132,9 @@ class CodeController extends TextEditingController {
106132 OutdentIntent : OutdentIntentAction (controller: this ),
107133 RedoTextIntent : RedoAction (controller: this ),
108134 UndoTextIntent : UndoAction (controller: this ),
135+ SearchIntent : SearchAction (controller: this ),
136+ DismissIntent : CustomDismissAction (controller: this ),
137+ EnterKeyIntent : EnterKeyAction (controller: this ),
109138 };
110139
111140 CodeController ({
@@ -119,6 +148,7 @@ class CodeController extends TextEditingController {
119148 Map <String , TextStyle >? theme,
120149 this .analysisResult = const AnalysisResult (issues: []),
121150 this .patternMap,
151+ this .readOnly = false ,
122152 this .stringMap,
123153 this .params = const EditorParams (),
124154 this .modifiers = const [
@@ -142,6 +172,11 @@ class CodeController extends TextEditingController {
142172 fullText = text ?? '' ;
143173
144174 addListener (_scheduleAnalysis);
175+ addListener (_updateSearchResult);
176+ _searchSettingsController.addListener (_updateSearchResult);
177+ // This listener is called when search controller notifies about
178+ // showing or hiding the search popup.
179+ searchController.addListener (_updateSearchResult);
145180
146181 // Create modifier map
147182 for (final el in modifiers) {
@@ -165,6 +200,20 @@ class CodeController extends TextEditingController {
165200 unawaited (analyzeCode ());
166201 }
167202
203+ void _updateSearchResult () {
204+ final result = searchController.search (
205+ code,
206+ settings: _searchSettingsController.value,
207+ );
208+
209+ if (result == fullSearchResult) {
210+ return ;
211+ }
212+
213+ fullSearchResult = result;
214+ notifyListeners ();
215+ }
216+
168217 void _scheduleAnalysis () {
169218 _debounce? .cancel ();
170219
@@ -275,6 +324,11 @@ class CodeController extends TextEditingController {
275324 }
276325
277326 KeyEventResult _onKeyDownRepeat (KeyEvent event) {
327+ if (event.isCtrlF (HardwareKeyboard .instance.logicalKeysPressed)) {
328+ showSearch ();
329+ return KeyEventResult .handled;
330+ }
331+
278332 if (popupController.shouldShow) {
279333 if (event.logicalKey == LogicalKeyboardKey .arrowUp) {
280334 popupController.scrollByArrow (ScrollDirection .up);
@@ -284,19 +338,34 @@ class CodeController extends TextEditingController {
284338 popupController.scrollByArrow (ScrollDirection .down);
285339 return KeyEventResult .handled;
286340 }
287- if (event.logicalKey == LogicalKeyboardKey .enter) {
288- insertSelectedWord ();
289- return KeyEventResult .handled;
290- }
291- if (event.logicalKey == LogicalKeyboardKey .escape) {
292- popupController.hide ();
293- return KeyEventResult .handled;
294- }
295341 }
296342
297343 return KeyEventResult .ignored; // The framework will handle.
298344 }
299345
346+ void onEnterKeyAction () {
347+ if (popupController.shouldShow) {
348+ insertSelectedWord ();
349+ return ;
350+ }
351+
352+ final currentMatchIndex =
353+ _searchNavigationController.value.currentMatchIndex;
354+
355+ if (searchController.shouldShow && currentMatchIndex != null ) {
356+ final fullSelection = code.hiddenRanges.recoverSelection (selection);
357+ final currentMatch = fullSearchResult.matches[currentMatchIndex];
358+
359+ if (fullSelection.start == currentMatch.start &&
360+ fullSelection.end == currentMatch.end) {
361+ _searchNavigationController.moveNext ();
362+ return ;
363+ }
364+ }
365+
366+ insertStr ('\n ' );
367+ }
368+
300369 /// Inserts the word selected from the list of completions
301370 void insertSelectedWord () {
302371 final previousSelection = selection;
@@ -592,6 +661,10 @@ class CodeController extends TextEditingController {
592661 void modifySelectedLines (
593662 String Function (String line) modifierCallback,
594663 ) {
664+ if (readOnly) {
665+ return ;
666+ }
667+
595668 if (selection.start == - 1 || selection.end == - 1 ) {
596669 return ;
597670 }
@@ -667,6 +740,10 @@ class CodeController extends TextEditingController {
667740 Code get code => _code;
668741
669742 CodeEditResult ? _getEditResultNotBreakingReadOnly (TextEditingValue newValue) {
743+ if (readOnly) {
744+ return null ;
745+ }
746+
670747 final editResult = _code.getEditResult (value.selection, newValue);
671748 if (! _code.isReadOnlyInLineRange (editResult.linesChanged)) {
672749 return editResult;
@@ -809,8 +886,23 @@ class CodeController extends TextEditingController {
809886 TextStyle ? style,
810887 bool ? withComposing,
811888 }) {
889+ final spanBeforeSearch = _createTextSpan (
890+ context: context,
891+ style: style,
892+ );
893+
894+ final visibleSearchResult =
895+ _code.hiddenRanges.cutSearchResult (fullSearchResult);
896+
812897 // TODO(alexeyinkin): Return cached if the value did not change, https://github.com/akvelon/flutter-code-editor/issues/127
813- return lastTextSpan = _createTextSpan (context: context, style: style);
898+ lastTextSpan = SearchResultHighlightedBuilder (
899+ searchResult: visibleSearchResult,
900+ rootStyle: style,
901+ textSpan: spanBeforeSearch,
902+ searchNavigationState: _searchNavigationController.value,
903+ ).build ();
904+
905+ return lastTextSpan! ;
814906 }
815907
816908 TextSpan _createTextSpan ({
@@ -871,10 +963,30 @@ class CodeController extends TextEditingController {
871963 return CodeTheme .of (context) ?? CodeThemeData ();
872964 }
873965
966+ void dismiss () {
967+ _dismissSuggestions ();
968+ _dismissSearch ();
969+ }
970+
971+ void _dismissSuggestions () {
972+ if (popupController.enabled) {
973+ popupController.hide ();
974+ }
975+ }
976+
977+ void _dismissSearch () {
978+ searchController.hideSearch (returnFocusToCodeField: true );
979+ }
980+
981+ void showSearch () {
982+ searchController.showSearch ();
983+ }
984+
874985 @override
875986 void dispose () {
876987 _debounce? .cancel ();
877988 historyController.dispose ();
989+ searchController.dispose ();
878990
879991 super .dispose ();
880992 }
0 commit comments