@@ -12,13 +12,15 @@ import '../../flutter_code_editor.dart';
1212import '../autocomplete/autocompleter.dart' ;
1313import '../code/code_edit_result.dart' ;
1414import '../code/key_event.dart' ;
15+ import '../code_modifiers/insertion.dart' ;
1516import '../history/code_history_controller.dart' ;
1617import '../history/code_history_record.dart' ;
1718import '../search/controller.dart' ;
1819import '../search/result.dart' ;
1920import '../search/search_navigation_controller.dart' ;
2021import '../search/settings_controller.dart' ;
2122import '../single_line_comments/parser/single_line_comments.dart' ;
23+ import '../util/string_util.dart' ;
2224import '../wip/autocomplete/popup_controller.dart' ;
2325import 'actions/comment_uncomment.dart' ;
2426import 'actions/copy.dart' ;
@@ -28,6 +30,7 @@ import 'actions/indent.dart';
2830import 'actions/outdent.dart' ;
2931import 'actions/redo.dart' ;
3032import 'actions/search.dart' ;
33+ import 'actions/tab.dart' ;
3134import 'actions/undo.dart' ;
3235import 'search_result_highlighted_builder.dart' ;
3336import 'span_builder.dart' ;
@@ -50,6 +53,7 @@ class CodeController extends TextEditingController {
5053 /// Calls [AbstractAnalyzer.analyze] after change with 500ms debounce.
5154 AbstractAnalyzer get analyzer => _analyzer;
5255 AbstractAnalyzer _analyzer;
56+
5357 set analyzer (AbstractAnalyzer analyzer) {
5458 if (_analyzer == analyzer) {
5559 return ;
@@ -107,6 +111,7 @@ class CodeController extends TextEditingController {
107111
108112 SearchSettingsController get _searchSettingsController =>
109113 searchController.settingsController;
114+
110115 SearchNavigationController get _searchNavigationController =>
111116 searchController.navigationController;
112117
@@ -130,8 +135,21 @@ class CodeController extends TextEditingController {
130135 SearchIntent : SearchAction (controller: this ),
131136 DismissIntent : CustomDismissAction (controller: this ),
132137 EnterKeyIntent : EnterKeyAction (controller: this ),
138+ TabKeyIntent : TabKeyAction (controller: this ),
133139 };
134140
141+ static const defaultCodeModifiers = [
142+ IndentModifier (),
143+ CloseBlockModifier (),
144+ TabModifier (),
145+ InsertionCodeModifier .backticks,
146+ InsertionCodeModifier .braces,
147+ InsertionCodeModifier .brackets,
148+ InsertionCodeModifier .doubleQuotes,
149+ InsertionCodeModifier .parentheses,
150+ InsertionCodeModifier .singleQuotes,
151+ ];
152+
135153 CodeController ({
136154 String ? text,
137155 Mode ? language,
@@ -143,11 +161,7 @@ class CodeController extends TextEditingController {
143161 this .patternMap,
144162 this .readOnly = false ,
145163 this .params = const EditorParams (),
146- this .modifiers = const [
147- IndentModifier (),
148- CloseBlockModifier (),
149- TabModifier (),
150- ],
164+ this .modifiers = defaultCodeModifiers,
151165 }) : _analyzer = analyzer,
152166 _readOnlySectionNames = readOnlySectionNames,
153167 _code = Code .empty,
@@ -347,30 +361,60 @@ class CodeController extends TextEditingController {
347361 insertStr ('\n ' );
348362 }
349363
364+ void onTabKeyAction () {
365+ if (popupController.shouldShow) {
366+ insertSelectedWord ();
367+ return ;
368+ }
369+
370+ insertStr (' ' * params.tabSpaces);
371+ }
372+
350373 /// Inserts the word selected from the list of completions
351374 void insertSelectedWord () {
352375 final previousSelection = selection;
353376 final selectedWord = popupController.getSelectedWord ();
354377 final startPosition = value.wordAtCursorStart;
378+ final currentWord = value.wordAtCursor;
355379
356- if (startPosition != null ) {
357- final replacedText = text.replaceRange (
358- startPosition,
359- selection.baseOffset,
360- selectedWord,
361- );
380+ if (startPosition == null || currentWord == null ) {
381+ popupController.hide ();
382+ return ;
383+ }
362384
363- final adjustedSelection = previousSelection.copyWith (
364- baseOffset: startPosition + selectedWord.length,
365- extentOffset: startPosition + selectedWord.length,
366- );
385+ final endReplacingPosition = startPosition + currentWord.length;
386+ final endSelectionPosition = startPosition + selectedWord.length;
367387
368- value = TextEditingValue (
369- text: replacedText,
370- selection: adjustedSelection,
371- );
388+ var additionalSpaceIfEnd = '' ;
389+ var offsetIfEndsWithSpace = 1 ;
390+ if (text.length < endReplacingPosition + 1 ) {
391+ additionalSpaceIfEnd = ' ' ;
392+ } else {
393+ final charAfterText = text[endReplacingPosition];
394+ if (charAfterText != ' ' &&
395+ ! StringUtil .isDigit (charAfterText) &&
396+ ! StringUtil .isLetterEng (charAfterText)) {
397+ // ex. case ';' or other finalizer, or symbol
398+ offsetIfEndsWithSpace = 0 ;
399+ }
372400 }
373401
402+ final replacedText = text.replaceRange (
403+ startPosition,
404+ endReplacingPosition,
405+ '$selectedWord $additionalSpaceIfEnd ' ,
406+ );
407+
408+ final adjustedSelection = previousSelection.copyWith (
409+ baseOffset: endSelectionPosition + offsetIfEndsWithSpace,
410+ extentOffset: endSelectionPosition + offsetIfEndsWithSpace,
411+ );
412+
413+ value = TextEditingValue (
414+ text: replacedText,
415+ selection: adjustedSelection,
416+ );
417+
374418 popupController.hide ();
375419 }
376420
0 commit comments