Skip to content

Commit 36cd770

Browse files
Minor improvements based on feedback at PR#231
1 parent b0e893b commit 36cd770

File tree

10 files changed

+263
-87
lines changed

10 files changed

+263
-87
lines changed

lib/src/code_field/actions/tab.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:flutter/material.dart';
2+
3+
import '../code_controller.dart';
4+
5+
class TabKeyIntent extends Intent {
6+
const TabKeyIntent();
7+
}
8+
9+
class TabKeyAction extends Action<TabKeyIntent> {
10+
final CodeController controller;
11+
12+
TabKeyAction({
13+
required this.controller,
14+
});
15+
16+
@override
17+
Object? invoke(TabKeyIntent intent) {
18+
controller.onTabKeyAction();
19+
return null;
20+
}
21+
}

lib/src/code_field/code_controller.dart

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import 'package:meta/meta.dart';
1111
import '../../flutter_code_editor.dart';
1212
import '../autocomplete/autocompleter.dart';
1313
import '../code/code_edit_result.dart';
14-
import '../code_modifiers/insertion.dart';
1514
import '../code/key_event.dart';
15+
import '../code_modifiers/insertion.dart';
1616
import '../history/code_history_controller.dart';
1717
import '../history/code_history_record.dart';
1818
import '../search/controller.dart';
1919
import '../search/result.dart';
2020
import '../search/search_navigation_controller.dart';
2121
import '../search/settings_controller.dart';
2222
import '../single_line_comments/parser/single_line_comments.dart';
23+
import '../util/string_util.dart';
2324
import '../wip/autocomplete/popup_controller.dart';
2425
import 'actions/comment_uncomment.dart';
2526
import 'actions/copy.dart';
@@ -29,6 +30,7 @@ import 'actions/indent.dart';
2930
import 'actions/outdent.dart';
3031
import 'actions/redo.dart';
3132
import 'actions/search.dart';
33+
import 'actions/tab.dart';
3234
import 'actions/undo.dart';
3335
import 'search_result_highlighted_builder.dart';
3436
import '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

lib/src/code_field/code_field.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import 'actions/enter_key.dart';
1515
import 'actions/indent.dart';
1616
import 'actions/outdent.dart';
1717
import 'actions/search.dart';
18+
import 'actions/tab.dart';
1819
import 'code_controller.dart';
1920
import 'default_styles.dart';
2021
import 'js_workarounds/js_workarounds.dart';
@@ -110,6 +111,11 @@ final _shortcuts = <ShortcutActivator, Intent>{
110111
LogicalKeySet(
111112
LogicalKeyboardKey.enter,
112113
): const EnterKeyIntent(),
114+
115+
// TabKey
116+
LogicalKeySet(
117+
LogicalKeyboardKey.tab,
118+
): const TabKeyIntent(),
113119
};
114120

115121
class CodeField extends StatefulWidget {

lib/src/code_modifiers/close_block_code_modifier.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import 'package:flutter/widgets.dart';
55
import '../code_field/editor_params.dart';
66
import 'code_modifier.dart';
77

8-
/// [CloseBlockModifier] is an implementation of [CodeModifier]
8+
/// [CloseBlockModifier] is an implementation of [CodeModifier]
99
/// that remove spaces before the closing bracket, if required.
1010
class CloseBlockModifier extends CodeModifier {
1111
const CloseBlockModifier() : super('}');

lib/src/code_modifiers/code_modifier.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ abstract class CodeModifier {
77

88
const CodeModifier(this.char);
99

10-
// Helper to insert [str] in [text] between [start] and [end]
10+
/// Helper to insert [str] in [text] between [start] and [end]
1111
TextEditingValue replace(String text, int start, int end, String str) {
1212
final len = str.length;
1313
return TextEditingValue(

lib/src/util/string_util.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class StringUtil {
2+
static bool isDigit(String char) {
3+
if (char.isEmpty || char.length > 1) return false;
4+
final codeUnit = char.codeUnitAt(0);
5+
return codeUnit >= 48 && codeUnit <= 57; // Unicode range for '0' to '9'
6+
}
7+
8+
static bool isLetterEng(String char) {
9+
if (char.isEmpty || char.length > 1) return false;
10+
final codeUnit = char.codeUnitAt(0);
11+
return (codeUnit >= 65 && codeUnit <= 90) || // A-Z
12+
(codeUnit >= 97 && codeUnit <= 122); // a-z
13+
}
14+
}

lib/src/wip/autocomplete/popup.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ class Popup extends StatefulWidget {
4545

4646
class PopupState extends State<Popup> {
4747
final pageStorageBucket = PageStorageBucket();
48+
4849
@override
4950
void initState() {
50-
widget.controller.reset();
5151
widget.controller.addListener(rebuild);
5252
super.initState();
5353
}
@@ -69,6 +69,13 @@ class PopupState extends State<Popup> {
6969
(widget.editorOffset?.dx ?? 0) -
7070
100;
7171

72+
// Fixes assertion error when ISC isn't attached but _attach method
73+
// of ISC instance are being called
74+
ItemScrollController? isc;
75+
if (widget.controller.itemScrollController.isAttached) {
76+
isc = widget.controller.itemScrollController;
77+
}
78+
7279
return PageStorage(
7380
bucket: pageStorageBucket,
7481
child: Positioned(
@@ -98,7 +105,7 @@ class PopupState extends State<Popup> {
98105
child: ScrollablePositionedList.builder(
99106
shrinkWrap: true,
100107
physics: const ClampingScrollPhysics(),
101-
itemScrollController: widget.controller.itemScrollController,
108+
itemScrollController: isc,
102109
itemPositionsListener: widget.controller.itemPositionsListener,
103110
itemCount: widget.controller.suggestions.length,
104111
itemBuilder: (context, index) {

lib/src/wip/autocomplete/popup_controller.dart

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class PopupController extends ChangeNotifier {
77
bool shouldShow = false;
88
bool enabled = true;
99

10-
ItemScrollController itemScrollController = ItemScrollController();
10+
final ItemScrollController itemScrollController = ItemScrollController();
1111
final ItemPositionsListener itemPositionsListener =
1212
ItemPositionsListener.create();
1313

@@ -23,12 +23,8 @@ class PopupController extends ChangeNotifier {
2323

2424
int get selectedIndex => _selectedIndex;
2525

26-
void reset() {
27-
itemScrollController = ItemScrollController();
28-
}
29-
3026
void show(List<String> suggestions) {
31-
if (enabled == false) {
27+
if (!enabled) {
3228
return;
3329
}
3430

0 commit comments

Comments
 (0)