@@ -6,20 +6,30 @@ 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 '../history/code_history_controller.dart' ;
1416import '../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' ;
1521import '../single_line_comments/parser/single_line_comments.dart' ;
1622import '../wip/autocomplete/popup_controller.dart' ;
1723import 'actions/comment_uncomment.dart' ;
1824import 'actions/copy.dart' ;
25+ import 'actions/dismiss.dart' ;
26+ import 'actions/enter_key.dart' ;
1927import 'actions/indent.dart' ;
2028import 'actions/outdent.dart' ;
2129import 'actions/redo.dart' ;
30+ import 'actions/search.dart' ;
2231import 'actions/undo.dart' ;
32+ import 'search_result_highlighted_builder.dart' ;
2333import 'span_builder.dart' ;
2434
2535class CodeController extends TextEditingController {
@@ -80,6 +90,11 @@ class CodeController extends TextEditingController {
8090 ///If it is not empty, all another code except specified will be hidden.
8191 Set <String > _visibleSectionNames = {};
8292
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+
8398 String get languageId => _languageId;
8499
85100 Code _code;
@@ -91,6 +106,17 @@ class CodeController extends TextEditingController {
91106 final autocompleter = Autocompleter ();
92107 late final historyController = CodeHistoryController (codeController: this );
93108
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+
94120 /// The last [TextSpan] returned from [buildTextSpan] .
95121 ///
96122 /// This can be used in tests to make sure that the updated text was actually
@@ -105,6 +131,9 @@ class CodeController extends TextEditingController {
105131 OutdentIntent : OutdentIntentAction (controller: this ),
106132 RedoTextIntent : RedoAction (controller: this ),
107133 UndoTextIntent : UndoAction (controller: this ),
134+ SearchIntent : SearchAction (controller: this ),
135+ DismissIntent : CustomDismissAction (controller: this ),
136+ EnterKeyIntent : EnterKeyAction (controller: this ),
108137 };
109138
110139 CodeController ({
@@ -118,6 +147,7 @@ class CodeController extends TextEditingController {
118147 Map <String , TextStyle >? theme,
119148 this .analysisResult = const AnalysisResult (issues: []),
120149 this .patternMap,
150+ this .readonly = false ,
121151 this .stringMap,
122152 this .params = const EditorParams (),
123153 this .modifiers = const [
@@ -135,6 +165,11 @@ class CodeController extends TextEditingController {
135165 fullText = text ?? '' ;
136166
137167 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);
138173
139174 // Create modifier map
140175 for (final el in modifiers) {
@@ -158,6 +193,20 @@ class CodeController extends TextEditingController {
158193 unawaited (analyzeCode ());
159194 }
160195
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+
161210 void _scheduleAnalysis () {
162211 _debounce? .cancel ();
163212
@@ -268,6 +317,11 @@ class CodeController extends TextEditingController {
268317 }
269318
270319 KeyEventResult _onKeyDownRepeat (KeyEvent event) {
320+ if (event.isCtrlF (HardwareKeyboard .instance.logicalKeysPressed)) {
321+ showSearch ();
322+ return KeyEventResult .handled;
323+ }
324+
271325 if (popupController.shouldShow) {
272326 if (event.logicalKey == LogicalKeyboardKey .arrowUp) {
273327 popupController.scrollByArrow (ScrollDirection .up);
@@ -277,19 +331,34 @@ class CodeController extends TextEditingController {
277331 popupController.scrollByArrow (ScrollDirection .down);
278332 return KeyEventResult .handled;
279333 }
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- }
288334 }
289335
290336 return KeyEventResult .ignored; // The framework will handle.
291337 }
292338
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+
293362 /// Inserts the word selected from the list of completions
294363 void insertSelectedWord () {
295364 final previousSelection = selection;
@@ -585,6 +654,10 @@ class CodeController extends TextEditingController {
585654 void modifySelectedLines (
586655 String Function (String line) modifierCallback,
587656 ) {
657+ if (readonly) {
658+ return ;
659+ }
660+
588661 if (selection.start == - 1 || selection.end == - 1 ) {
589662 return ;
590663 }
@@ -660,6 +733,10 @@ class CodeController extends TextEditingController {
660733 Code get code => _code;
661734
662735 CodeEditResult ? _getEditResultNotBreakingReadOnly (TextEditingValue newValue) {
736+ if (readonly) {
737+ return null ;
738+ }
739+
663740 final editResult = _code.getEditResult (value.selection, newValue);
664741 if (! _code.isReadOnlyInLineRange (editResult.linesChanged)) {
665742 return editResult;
@@ -802,8 +879,23 @@ class CodeController extends TextEditingController {
802879 TextStyle ? style,
803880 bool ? withComposing,
804881 }) {
882+ final spanBeforeSearch = _createTextSpan (
883+ context: context,
884+ style: style,
885+ );
886+
887+ final visibleSearchResult =
888+ _code.hiddenRanges.cutSearchResult (fullSearchResult);
889+
805890 // 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! ;
807899 }
808900
809901 TextSpan _createTextSpan ({
@@ -864,10 +956,30 @@ class CodeController extends TextEditingController {
864956 return CodeTheme .of (context) ?? CodeThemeData ();
865957 }
866958
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+
867978 @override
868979 void dispose () {
869980 _debounce? .cancel ();
870981 historyController.dispose ();
982+ searchController.dispose ();
871983
872984 super .dispose ();
873985 }
0 commit comments