Skip to content
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

Issue192 paired symbols, issue199 yaml support #231

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
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(),
alexeyinkin marked this conversation as resolved.
Show resolved Hide resolved
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
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
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();
}
}
1 change: 1 addition & 0 deletions lib/src/wip/autocomplete/popup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class PopupState extends State<Popup> {
final pageStorageBucket = PageStorageBucket();
@override
void initState() {
widget.controller.reset();
widget.controller.addListener(rebuild);
super.initState();
}
Expand Down
6 changes: 5 additions & 1 deletion lib/src/wip/autocomplete/popup_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class PopupController extends ChangeNotifier {
bool shouldShow = false;
bool enabled = true;

final ItemScrollController itemScrollController = ItemScrollController();
ItemScrollController itemScrollController = ItemScrollController();
final ItemPositionsListener itemPositionsListener =
ItemPositionsListener.create();

Expand All @@ -23,6 +23,10 @@ class PopupController extends ChangeNotifier {

int get selectedIndex => _selectedIndex;

void reset() {
itemScrollController = ItemScrollController();
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the desired effect of this? Scroll to the top? If so, can you manually scroll to the top?

Replacing the controller has the drawbacks:

  • Need to dispose the old one.
  • Need to make sure the disposed one will not show in a widget and break it.

}

void show(List<String> suggestions) {
if (enabled == false) {
return;
Expand Down
149 changes: 149 additions & 0 deletions test/src/code_modifiers/controller_insertion_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
import 'package:flutter_code_editor/flutter_code_editor.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test('Insertion modifier test', () {
const examples = [
//
_Example(
'Add backticks',
initialValue: TextEditingValue(
text: 'dict',
// \ cursor
selection: TextSelection.collapsed(offset: 0),
),
expected: TextEditingValue(
text: '``dict',
// \ cursor
selection: TextSelection.collapsed(offset: 1),
),
inputChar: '`',
),

_Example(
'Add char at the start of the string (braces)',
initialValue: TextEditingValue(
text: 'dict',
// \ cursor
selection: TextSelection.collapsed(offset: 0),
),
expected: TextEditingValue(
text: '{}dict',
// \ cursor
selection: TextSelection.collapsed(offset: 1),
),
inputChar: '{',
),

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

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

_Example(
'Add close char before same close char (double quotes)',
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 (single quotes)',
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 controller = CodeController();
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;

const _Example(
this.name, {
required this.initialValue,
required this.expected,
required this.inputChar,
});
}
75 changes: 75 additions & 0 deletions test/src/code_modifiers/insertion_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import 'package:flutter/services.dart';
import 'package:flutter_code_editor/src/code_field/editor_params.dart';
import 'package:flutter_code_editor/src/code_modifiers/insertion.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
test('inserts at the start of string correctly', () {
Copy link
Contributor

Choose a reason for hiding this comment

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

group('InsertionCodeModifier', ...

const modifier = InsertionCodeModifier(openChar: '1', closeString: '23');
Copy link
Contributor

Choose a reason for hiding this comment

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

Make global?

const text = 'Hello World';
final selection = TextSelection.fromPosition(const TextPosition(offset: 0));
const editorParams = EditorParams();
Copy link
Contributor

Choose a reason for hiding this comment

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

Make global?


final result = modifier.updateString(text, selection, editorParams);

expect(result!.text, '123Hello World');
expect(result.selection.baseOffset, 1);
expect(result.selection.extentOffset, 1);
});

test('inserts in the middle of string correctly', () {
const modifier = InsertionCodeModifier(openChar: '1', closeString: '23');
const text = 'Hello World';
final selection = TextSelection.fromPosition(const TextPosition(offset: 5));
const editorParams = EditorParams();

final result = modifier.updateString(text, selection, editorParams);

expect(result!.text, 'Hello123 World');
expect(result.selection.baseOffset, 6);
expect(result.selection.extentOffset, 6);
});

test('inserts at the end of string correctly', () {
const modifier = InsertionCodeModifier(openChar: '1', closeString: '23');
const text = 'Hello World';
final selection =
TextSelection.fromPosition(const TextPosition(offset: text.length));
const editorParams = EditorParams();

final result = modifier.updateString(text, selection, editorParams);

expect(result!.text, 'Hello World123');
expect(result.selection.baseOffset, text.length + 1);
expect(result.selection.extentOffset, text.length + 1);
});

test('inserts in the middle of string with selection correctly', () {
const modifier = InsertionCodeModifier(openChar: '1', closeString: '23');
const text = 'Hello World';
const selection = TextSelection(
baseOffset: 5,
extentOffset: 7,
);
const editorParams = EditorParams();

final result = modifier.updateString(text, selection, editorParams);

expect(result!.text, 'Hello123orld');
expect(result.selection.baseOffset, 6);
expect(result.selection.extentOffset, 6);
});

test('inserts at empty string correctly', () {
const modifier = InsertionCodeModifier(openChar: '1', closeString: '23');
const text = '';
final selection = TextSelection.fromPosition(const TextPosition(offset: 0));
const editorParams = EditorParams();

final result = modifier.updateString(text, selection, editorParams);

expect(result!.text, '123');
expect(result.selection.baseOffset, 1);
expect(result.selection.extentOffset, 1);
});
}