Skip to content

Issue192 paired symbols, issue199 yaml support [origin: @Malarg] #290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions example/lib/03.change_language_theme/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:highlight/languages/java.dart';
import 'package:highlight/languages/php.dart';
import 'package:highlight/languages/python.dart';
import 'package:highlight/languages/scala.dart';
import 'package:highlight/languages/yaml.dart';

final builtinLanguages = {
'dart': dart,
Expand All @@ -12,6 +13,7 @@ final builtinLanguages = {
'php': php,
'python': python,
'scala': scala,
'yaml': yaml,
};

const languageList = <String>[
Expand All @@ -21,6 +23,7 @@ const languageList = <String>[
'php',
'python',
'scala',
'yaml',
];

const themeList = <String>[
Expand Down
21 changes: 21 additions & 0 deletions lib/src/code_field/actions/tab.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';

import '../code_controller.dart';

class TabKeyIntent extends Intent {
const TabKeyIntent();
}

class TabKeyAction extends Action<TabKeyIntent> {
final CodeController controller;

TabKeyAction({
required this.controller,
});

@override
Object? invoke(TabKeyIntent intent) {
controller.onTabKeyAction();
return null;
}
}
82 changes: 63 additions & 19 deletions lib/src/code_field/code_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import '../../flutter_code_editor.dart';
import '../autocomplete/autocompleter.dart';
import '../code/code_edit_result.dart';
import '../code/key_event.dart';
import '../code_modifiers/insertion.dart';
import '../history/code_history_controller.dart';
import '../history/code_history_record.dart';
import '../search/controller.dart';
import '../search/result.dart';
import '../search/search_navigation_controller.dart';
import '../search/settings_controller.dart';
import '../single_line_comments/parser/single_line_comments.dart';
import '../util/string_util.dart';
import '../wip/autocomplete/popup_controller.dart';
import 'actions/comment_uncomment.dart';
import 'actions/copy.dart';
Expand All @@ -28,6 +30,7 @@ import 'actions/indent.dart';
import 'actions/outdent.dart';
import 'actions/redo.dart';
import 'actions/search.dart';
import 'actions/tab.dart';
import 'actions/undo.dart';
import 'search_result_highlighted_builder.dart';
import 'span_builder.dart';
Expand All @@ -50,6 +53,7 @@ class CodeController extends TextEditingController {
/// Calls [AbstractAnalyzer.analyze] after change with 500ms debounce.
AbstractAnalyzer get analyzer => _analyzer;
AbstractAnalyzer _analyzer;

set analyzer(AbstractAnalyzer analyzer) {
if (_analyzer == analyzer) {
return;
Expand Down Expand Up @@ -107,6 +111,7 @@ class CodeController extends TextEditingController {

SearchSettingsController get _searchSettingsController =>
searchController.settingsController;

SearchNavigationController get _searchNavigationController =>
searchController.navigationController;

Expand All @@ -130,8 +135,21 @@ class CodeController extends TextEditingController {
SearchIntent: SearchAction(controller: this),
DismissIntent: CustomDismissAction(controller: this),
EnterKeyIntent: EnterKeyAction(controller: this),
TabKeyIntent: TabKeyAction(controller: this),
};

static const defaultCodeModifiers = [
IndentModifier(),
CloseBlockModifier(),
TabModifier(),
InsertionCodeModifier.backticks,
InsertionCodeModifier.braces,
InsertionCodeModifier.brackets,
InsertionCodeModifier.doubleQuotes,
InsertionCodeModifier.parentheses,
InsertionCodeModifier.singleQuotes,
];

CodeController({
String? text,
Mode? language,
Expand All @@ -143,11 +161,7 @@ class CodeController extends TextEditingController {
this.patternMap,
this.readOnly = false,
this.params = const EditorParams(),
this.modifiers = const [
IndentModifier(),
CloseBlockModifier(),
TabModifier(),
],
this.modifiers = defaultCodeModifiers,
}) : _analyzer = analyzer,
_readOnlySectionNames = readOnlySectionNames,
_code = Code.empty,
Expand Down Expand Up @@ -347,30 +361,60 @@ class CodeController extends TextEditingController {
insertStr('\n');
}

void onTabKeyAction() {
if (popupController.shouldShow) {
insertSelectedWord();
return;
}

insertStr(' ' * params.tabSpaces);
}

/// Inserts the word selected from the list of completions
void insertSelectedWord() {
final previousSelection = selection;
final selectedWord = popupController.getSelectedWord();
final startPosition = value.wordAtCursorStart;
final currentWord = value.wordAtCursor;

if (startPosition != null) {
final replacedText = text.replaceRange(
startPosition,
selection.baseOffset,
selectedWord,
);
if (startPosition == null || currentWord == null) {
popupController.hide();
return;
}

final adjustedSelection = previousSelection.copyWith(
baseOffset: startPosition + selectedWord.length,
extentOffset: startPosition + selectedWord.length,
);
final endReplacingPosition = startPosition + currentWord.length;
final endSelectionPosition = startPosition + selectedWord.length;

value = TextEditingValue(
text: replacedText,
selection: adjustedSelection,
);
var additionalSpaceIfEnd = '';
var offsetIfEndsWithSpace = 1;
if (text.length < endReplacingPosition + 1) {
additionalSpaceIfEnd = ' ';
} else {
final charAfterText = text[endReplacingPosition];
if (charAfterText != ' ' &&
!StringUtil.isDigit(charAfterText) &&
!StringUtil.isLetterEng(charAfterText)) {
// ex. case ';' or other finalizer, or symbol
offsetIfEndsWithSpace = 0;
}
}

final replacedText = text.replaceRange(
startPosition,
endReplacingPosition,
'$selectedWord$additionalSpaceIfEnd',
);

final adjustedSelection = previousSelection.copyWith(
baseOffset: endSelectionPosition + offsetIfEndsWithSpace,
extentOffset: endSelectionPosition + offsetIfEndsWithSpace,
);

value = TextEditingValue(
text: replacedText,
selection: adjustedSelection,
);

popupController.hide();
}

Expand Down
6 changes: 6 additions & 0 deletions lib/src/code_field/code_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'actions/enter_key.dart';
import 'actions/indent.dart';
import 'actions/outdent.dart';
import 'actions/search.dart';
import 'actions/tab.dart';
import 'code_controller.dart';
import 'default_styles.dart';
import 'js_workarounds/js_workarounds.dart';
Expand Down Expand Up @@ -110,6 +111,11 @@ final _shortcuts = <ShortcutActivator, Intent>{
LogicalKeySet(
LogicalKeyboardKey.enter,
): const EnterKeyIntent(),

// TabKey
LogicalKeySet(
LogicalKeyboardKey.tab,
): const TabKeyIntent(),
};

class CodeField extends StatefulWidget {
Expand Down
2 changes: 2 additions & 0 deletions lib/src/code_modifiers/close_block_code_modifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:flutter/widgets.dart';
import '../code_field/editor_params.dart';
import 'code_modifier.dart';

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

Expand Down
2 changes: 1 addition & 1 deletion lib/src/code_modifiers/code_modifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ abstract class CodeModifier {

const CodeModifier(this.char);

// Helper to insert [str] in [text] between [start] and [end]
/// Helper to insert [str] in [text] between [start] and [end]
TextEditingValue replace(String text, int start, int end, String str) {
final len = str.length;
return TextEditingValue(
Expand Down
47 changes: 47 additions & 0 deletions lib/src/code_modifiers/insertion.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:flutter/services.dart';

import '../code_field/editor_params.dart';
import 'code_modifier.dart';

class InsertionCodeModifier extends CodeModifier {
final String openChar;
final String closeString;

const InsertionCodeModifier({
required this.openChar,
required this.closeString,
}) : super(openChar);

static const backticks =
InsertionCodeModifier(openChar: '`', closeString: '`');

static const braces = InsertionCodeModifier(openChar: '{', closeString: '}');

static const brackets =
InsertionCodeModifier(openChar: '[', closeString: ']');

static const doubleQuotes =
InsertionCodeModifier(openChar: '"', closeString: '"');

static const parentheses =
InsertionCodeModifier(openChar: '(', closeString: ')');

static const singleQuotes =
InsertionCodeModifier(openChar: '\'', closeString: '\'');

@override
TextEditingValue? updateString(
String text,
TextSelection sel,
EditorParams params,
) {
final replaced = replace(text, sel.start, sel.end, '$openChar$closeString');

return replaced.copyWith(
selection: TextSelection(
baseOffset: replaced.selection.baseOffset - closeString.length,
extentOffset: replaced.selection.extentOffset - closeString.length,
),
);
}
}
7 changes: 7 additions & 0 deletions lib/src/folding/parsers/parser_factory.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:highlight/highlight_core.dart';
import 'package:highlight/languages/java.dart';
import 'package:highlight/languages/python.dart';
import 'package:highlight/languages/yaml.dart';

import 'abstract.dart';
import 'highlight.dart';
import 'indent.dart';
import 'java.dart';
import 'python.dart';

Expand All @@ -15,6 +17,11 @@ class FoldableBlockParserFactory {
if (mode == java) {
return JavaFoldableBlockParser();
}

if (mode == yaml) {
return IndentFoldableBlockParser();
}

return HighlightFoldableBlockParser();
}
}
14 changes: 14 additions & 0 deletions lib/src/util/string_util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class StringUtil {
static bool isDigit(String char) {
if (char.isEmpty || char.length > 1) return false;
final codeUnit = char.codeUnitAt(0);
return codeUnit >= 48 && codeUnit <= 57; // Unicode range for '0' to '9'
}

static bool isLetterEng(String char) {
if (char.isEmpty || char.length > 1) return false;
final codeUnit = char.codeUnitAt(0);
return (codeUnit >= 65 && codeUnit <= 90) || // A-Z
(codeUnit >= 97 && codeUnit <= 122); // a-z
}
}
10 changes: 9 additions & 1 deletion lib/src/wip/autocomplete/popup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class Popup extends StatefulWidget {

class PopupState extends State<Popup> {
final pageStorageBucket = PageStorageBucket();

@override
void initState() {
widget.controller.addListener(rebuild);
Expand All @@ -68,6 +69,13 @@ class PopupState extends State<Popup> {
(widget.editorOffset?.dx ?? 0) -
100;

// Fixes assertion error when ISC isn't attached but _attach method
// of ISC instance are being called
ItemScrollController? isc;
if (widget.controller.itemScrollController.isAttached) {
isc = widget.controller.itemScrollController;
}

return PageStorage(
bucket: pageStorageBucket,
child: Positioned(
Expand Down Expand Up @@ -97,7 +105,7 @@ class PopupState extends State<Popup> {
child: ScrollablePositionedList.builder(
shrinkWrap: true,
physics: const ClampingScrollPhysics(),
itemScrollController: widget.controller.itemScrollController,
itemScrollController: isc,
itemPositionsListener: widget.controller.itemPositionsListener,
itemCount: widget.controller.suggestions.length,
itemBuilder: (context, index) {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/wip/autocomplete/popup_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class PopupController extends ChangeNotifier {
int get selectedIndex => _selectedIndex;

void show(List<String> suggestions) {
if (enabled == false) {
if (!enabled) {
return;
}

Expand Down
Loading