Skip to content

Commit ab8d5d6

Browse files
authored
Merge pull request #290 from akvelon/issue192_paired_symbols_dzs
Issue192 paired symbols, issue199 yaml support [origin: @Malarg]
2 parents 63b4691 + 36cd770 commit ab8d5d6

File tree

14 files changed

+496
-23
lines changed

14 files changed

+496
-23
lines changed

example/lib/03.change_language_theme/constants.dart

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:highlight/languages/java.dart';
44
import 'package:highlight/languages/php.dart';
55
import 'package:highlight/languages/python.dart';
66
import 'package:highlight/languages/scala.dart';
7+
import 'package:highlight/languages/yaml.dart';
78

89
final builtinLanguages = {
910
'dart': dart,
@@ -12,6 +13,7 @@ final builtinLanguages = {
1213
'php': php,
1314
'python': python,
1415
'scala': scala,
16+
'yaml': yaml,
1517
};
1618

1719
const languageList = <String>[
@@ -21,6 +23,7 @@ const languageList = <String>[
2123
'php',
2224
'python',
2325
'scala',
26+
'yaml',
2427
];
2528

2629
const themeList = <String>[

lib/src/code_field/actions/tab.dart

+21
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

+63-19
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import '../../flutter_code_editor.dart';
1212
import '../autocomplete/autocompleter.dart';
1313
import '../code/code_edit_result.dart';
1414
import '../code/key_event.dart';
15+
import '../code_modifiers/insertion.dart';
1516
import '../history/code_history_controller.dart';
1617
import '../history/code_history_record.dart';
1718
import '../search/controller.dart';
1819
import '../search/result.dart';
1920
import '../search/search_navigation_controller.dart';
2021
import '../search/settings_controller.dart';
2122
import '../single_line_comments/parser/single_line_comments.dart';
23+
import '../util/string_util.dart';
2224
import '../wip/autocomplete/popup_controller.dart';
2325
import 'actions/comment_uncomment.dart';
2426
import 'actions/copy.dart';
@@ -28,6 +30,7 @@ import 'actions/indent.dart';
2830
import 'actions/outdent.dart';
2931
import 'actions/redo.dart';
3032
import 'actions/search.dart';
33+
import 'actions/tab.dart';
3134
import 'actions/undo.dart';
3235
import 'search_result_highlighted_builder.dart';
3336
import '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

lib/src/code_field/code_field.dart

+6
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

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ 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]
9+
/// that remove spaces before the closing bracket, if required.
810
class CloseBlockModifier extends CodeModifier {
911
const CloseBlockModifier() : super('}');
1012

lib/src/code_modifiers/code_modifier.dart

+1-1
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/code_modifiers/insertion.dart

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import 'package:flutter/services.dart';
2+
3+
import '../code_field/editor_params.dart';
4+
import 'code_modifier.dart';
5+
6+
class InsertionCodeModifier extends CodeModifier {
7+
final String openChar;
8+
final String closeString;
9+
10+
const InsertionCodeModifier({
11+
required this.openChar,
12+
required this.closeString,
13+
}) : super(openChar);
14+
15+
static const backticks =
16+
InsertionCodeModifier(openChar: '`', closeString: '`');
17+
18+
static const braces = InsertionCodeModifier(openChar: '{', closeString: '}');
19+
20+
static const brackets =
21+
InsertionCodeModifier(openChar: '[', closeString: ']');
22+
23+
static const doubleQuotes =
24+
InsertionCodeModifier(openChar: '"', closeString: '"');
25+
26+
static const parentheses =
27+
InsertionCodeModifier(openChar: '(', closeString: ')');
28+
29+
static const singleQuotes =
30+
InsertionCodeModifier(openChar: '\'', closeString: '\'');
31+
32+
@override
33+
TextEditingValue? updateString(
34+
String text,
35+
TextSelection sel,
36+
EditorParams params,
37+
) {
38+
final replaced = replace(text, sel.start, sel.end, '$openChar$closeString');
39+
40+
return replaced.copyWith(
41+
selection: TextSelection(
42+
baseOffset: replaced.selection.baseOffset - closeString.length,
43+
extentOffset: replaced.selection.extentOffset - closeString.length,
44+
),
45+
);
46+
}
47+
}

lib/src/folding/parsers/parser_factory.dart

+7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import 'package:highlight/highlight_core.dart';
22
import 'package:highlight/languages/java.dart';
33
import 'package:highlight/languages/python.dart';
4+
import 'package:highlight/languages/yaml.dart';
45

56
import 'abstract.dart';
67
import 'highlight.dart';
8+
import 'indent.dart';
79
import 'java.dart';
810
import 'python.dart';
911

@@ -15,6 +17,11 @@ class FoldableBlockParserFactory {
1517
if (mode == java) {
1618
return JavaFoldableBlockParser();
1719
}
20+
21+
if (mode == yaml) {
22+
return IndentFoldableBlockParser();
23+
}
24+
1825
return HighlightFoldableBlockParser();
1926
}
2027
}

lib/src/util/string_util.dart

+14
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

+9-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class Popup extends StatefulWidget {
4545

4646
class PopupState extends State<Popup> {
4747
final pageStorageBucket = PageStorageBucket();
48+
4849
@override
4950
void initState() {
5051
widget.controller.addListener(rebuild);
@@ -68,6 +69,13 @@ class PopupState extends State<Popup> {
6869
(widget.editorOffset?.dx ?? 0) -
6970
100;
7071

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+
7179
return PageStorage(
7280
bucket: pageStorageBucket,
7381
child: Positioned(
@@ -97,7 +105,7 @@ class PopupState extends State<Popup> {
97105
child: ScrollablePositionedList.builder(
98106
shrinkWrap: true,
99107
physics: const ClampingScrollPhysics(),
100-
itemScrollController: widget.controller.itemScrollController,
108+
itemScrollController: isc,
101109
itemPositionsListener: widget.controller.itemPositionsListener,
102110
itemCount: widget.controller.suggestions.length,
103111
itemBuilder: (context, index) {

lib/src/wip/autocomplete/popup_controller.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class PopupController extends ChangeNotifier {
2424
int get selectedIndex => _selectedIndex;
2525

2626
void show(List<String> suggestions) {
27-
if (enabled == false) {
27+
if (!enabled) {
2828
return;
2929
}
3030

0 commit comments

Comments
 (0)