@@ -11,15 +11,16 @@ import 'package:meta/meta.dart';
1111import '../../flutter_code_editor.dart' ;
1212import '../autocomplete/autocompleter.dart' ;
1313import '../code/code_edit_result.dart' ;
14- import '../code_modifiers/insertion.dart' ;
1514import '../code/key_event.dart' ;
15+ import '../code_modifiers/insertion.dart' ;
1616import '../history/code_history_controller.dart' ;
1717import '../history/code_history_record.dart' ;
1818import '../search/controller.dart' ;
1919import '../search/result.dart' ;
2020import '../search/search_navigation_controller.dart' ;
2121import '../search/settings_controller.dart' ;
2222import '../single_line_comments/parser/single_line_comments.dart' ;
23+ import '../util/string_util.dart' ;
2324import '../wip/autocomplete/popup_controller.dart' ;
2425import 'actions/comment_uncomment.dart' ;
2526import 'actions/copy.dart' ;
@@ -29,6 +30,7 @@ import 'actions/indent.dart';
2930import 'actions/outdent.dart' ;
3031import 'actions/redo.dart' ;
3132import 'actions/search.dart' ;
33+ import 'actions/tab.dart' ;
3234import 'actions/undo.dart' ;
3335import 'search_result_highlighted_builder.dart' ;
3436import 'span_builder.dart' ;
@@ -51,6 +53,7 @@ class CodeController extends TextEditingController {
5153 /// Calls [AbstractAnalyzer.analyze] after change with 500ms debounce.
5254 AbstractAnalyzer get analyzer => _analyzer;
5355 AbstractAnalyzer _analyzer;
56+
5457 set analyzer (AbstractAnalyzer analyzer) {
5558 if (_analyzer == analyzer) {
5659 return ;
@@ -108,6 +111,7 @@ class CodeController extends TextEditingController {
108111
109112 SearchSettingsController get _searchSettingsController =>
110113 searchController.settingsController;
114+
111115 SearchNavigationController get _searchNavigationController =>
112116 searchController.navigationController;
113117
@@ -131,19 +135,20 @@ class CodeController extends TextEditingController {
131135 SearchIntent : SearchAction (controller: this ),
132136 DismissIntent : CustomDismissAction (controller: this ),
133137 EnterKeyIntent : EnterKeyAction (controller: this ),
138+ TabKeyIntent : TabKeyAction (controller: this ),
134139 };
135140
136141 static const defaultCodeModifiers = [
137- IndentModifier (),
138- CloseBlockModifier (),
139- TabModifier (),
140- InsertionCodeModifier .backticks,
141- InsertionCodeModifier .braces,
142- InsertionCodeModifier .brackets,
143- InsertionCodeModifier .doubleQuotes,
144- InsertionCodeModifier .parentheses,
145- InsertionCodeModifier .singleQuotes,
146- ];
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+ ];
147152
148153 CodeController ({
149154 String ? text,
@@ -356,30 +361,60 @@ class CodeController extends TextEditingController {
356361 insertStr ('\n ' );
357362 }
358363
364+ void onTabKeyAction () {
365+ if (popupController.shouldShow) {
366+ insertSelectedWord ();
367+ return ;
368+ }
369+
370+ insertStr (' ' * params.tabSpaces);
371+ }
372+
359373 /// Inserts the word selected from the list of completions
360374 void insertSelectedWord () {
361375 final previousSelection = selection;
362376 final selectedWord = popupController.getSelectedWord ();
363377 final startPosition = value.wordAtCursorStart;
378+ final currentWord = value.wordAtCursor;
364379
365- if (startPosition != null ) {
366- final replacedText = text.replaceRange (
367- startPosition,
368- selection.baseOffset,
369- selectedWord,
370- );
380+ if (startPosition == null || currentWord == null ) {
381+ popupController.hide ();
382+ return ;
383+ }
371384
372- final adjustedSelection = previousSelection.copyWith (
373- baseOffset: startPosition + selectedWord.length,
374- extentOffset: startPosition + selectedWord.length,
375- );
385+ final endReplacingPosition = startPosition + currentWord.length;
386+ final endSelectionPosition = startPosition + selectedWord.length;
376387
377- value = TextEditingValue (
378- text: replacedText,
379- selection: adjustedSelection,
380- );
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+ }
381400 }
382401
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+
383418 popupController.hide ();
384419 }
385420
0 commit comments