Skip to content
Open
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
19 changes: 14 additions & 5 deletions lib/src/code_field/code_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ 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';
Expand Down Expand Up @@ -136,6 +137,18 @@ class CodeController extends TextEditingController {
EnterKeyIntent: EnterKeyAction(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 @@ -150,11 +163,7 @@ class CodeController extends TextEditingController {
this.readOnly = false,
this.stringMap,
this.params = const EditorParams(),
this.modifiers = const [
IndentModifier(),
CloseBlockModifier(),
TabModifier(),
],
this.modifiers = defaultCodeModifiers,
}) : _analyzer = analyzer,
_readOnlySectionNames = readOnlySectionNames,
_code = Code.empty,
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);

const InsertionCodeModifier.backticks()
: this(openChar: '`', closeString: '`');

const InsertionCodeModifier.braces() : this(openChar: '{', closeString: '}');

const InsertionCodeModifier.brackets()
: this(openChar: '[', closeString: ']');

const InsertionCodeModifier.doubleQuotes()
: this(openChar: '"', closeString: '"');

const InsertionCodeModifier.parentheses()
: this(openChar: '(', closeString: ')');

const InsertionCodeModifier.singleQuotes()
: this(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();
}
}
178 changes: 178 additions & 0 deletions test/src/code_modifiers/insertion_test.dart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need two tests:

  1. A unit test to InsertionCodeModifer with 1 and 23 strings.
  2. A test on CodeController that if created with the default parameters it handles all 6 pairs of characters.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import 'package:flutter/material.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:flutter_code_editor/src/code_modifiers/insertion.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test('Insertion modifier test', () {
const examples = [
//
_Example(
'Add close char at the start of the string',
initialValue: TextEditingValue(
text: 'dict',
// \ cursor
selection: TextSelection.collapsed(offset: 0),
),
expected: TextEditingValue(
text: '{}dict',
// \ cursor
selection: TextSelection.collapsed(offset: 1),
),
inputChar: '{',
),

_Example(
'Add close char in the middle of the string',
initialValue: TextEditingValue(
text: 'print',
// \ cursor
selection: TextSelection.collapsed(offset: 3),
),
expected: TextEditingValue(
text: 'pri()nt',
// \ cursor
selection: TextSelection.collapsed(offset: 4),
),
inputChar: '(',
),

_Example(
'Add close char at the end of the string',
initialValue: TextEditingValue(
text: 'print',
// \ cursor
selection: TextSelection.collapsed(offset: 5),
),
expected: TextEditingValue(
text: 'print[]',
// \ cursor
selection: TextSelection.collapsed(offset: 6),
),
inputChar: '[',
),

_Example(
'Add close with several close chars',
initialValue: TextEditingValue(
text: 'string',
// \ cursor
selection: TextSelection.collapsed(offset: 6),
),
expected: TextEditingValue(
text: 'string123',
// \ cursor
selection: TextSelection.collapsed(offset: 7),
),
inputChar: '1',
insertedString: '23',
),

_Example(
'Add close char before same close char',
initialValue: TextEditingValue(
text: 'string)',
// \ cursor
selection: TextSelection.collapsed(offset: 6),
),
expected: TextEditingValue(
text: 'string())',
// \ cursor
selection: TextSelection.collapsed(offset: 7),
),
inputChar: '(',
),

_Example(
'Empty initial string',
initialValue: TextEditingValue(
// ignore: avoid_redundant_argument_values
text: '',
// \ cursor
selection: TextSelection.collapsed(offset: 0),
),
expected: TextEditingValue(
text: '()',
// \ cursor
selection: TextSelection.collapsed(offset: 1),
),
inputChar: '(',
),
];

for (final example in examples) {
final additionalModifier = example.insertedString != null
? InsertionCodeModifier(
openChar: example.inputChar,
closeString: example.insertedString!,
)
: null;

if (additionalModifier != null &&
CodeController.defaultCodeModifiers.any(
(e) =>
e is InsertionCodeModifier && e.openChar == example.inputChar,
)) {
fail('Modifier for ${example.inputChar} already exists');
}

late CodeController controller;

if (additionalModifier == null) {
controller = CodeController();
} else {
controller = CodeController(
modifiers: CodeController.defaultCodeModifiers + [additionalModifier],
);
}

controller.value = example.initialValue;

controller.value = _addCharToSelectedPosition(
controller.value,
example.inputChar,
);

expect(
controller.value,
example.expected,
reason: example.name,
);
}
});
}

TextEditingValue _addCharToSelectedPosition(
TextEditingValue value,
String char,
) {
final selection = value.selection;
final text = value.text;

final newText = text.substring(0, selection.start) +
char +
text.substring(selection.start);

return TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
offset: selection.start + char.length,
),
);
}

class _Example {
final String name;
final TextEditingValue initialValue;
final TextEditingValue expected;
final String inputChar;
final String? insertedString;

const _Example(
this.name, {
required this.initialValue,
required this.expected,
required this.inputChar,
this.insertedString,
});
}