Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,10 @@
* removeChar & removeSelection methods added
* added onChange callback
* added enabled flag
* fixed middle dot issue
* fixed middle dot issue

## [1.0.3] - 2022-05-02

* added onTap to CodeField API
* fixed tab behavior in read-only mode
* added setCursor method to CodeController
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A customizable code text field supporting syntax highlighting

## Live demo

A [live demo](https://bertrandbev.github.io/code_field/#/) showcasing a few language / themes combinaisons
A [live demo](https://bertrandbev.github.io/code_field/#/) showcasing a few language / theme combinations

## Showcase

Expand Down Expand Up @@ -199,6 +199,7 @@ Key? key,
this.textSelectionTheme,
this.lineNumberBuilder,
this.focusNode,
this.onTap,
})
```

Expand Down
21 changes: 14 additions & 7 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.0"
version: "2.8.2"
boolean_selector:
dependency: transitive
description:
Expand All @@ -21,7 +21,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.2.0"
charcode:
dependency: transitive
description:
Expand Down Expand Up @@ -120,14 +120,21 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.0"
version: "1.7.0"
path:
dependency: transitive
description:
Expand Down Expand Up @@ -188,7 +195,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.1"
version: "0.4.8"
typed_data:
dependency: transitive
description:
Expand Down Expand Up @@ -244,7 +251,7 @@ packages:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.1"
sdks:
dart: ">=2.12.0 <3.0.0"
dart: ">=2.14.0 <3.0.0"
flutter: ">=1.26.0"
32 changes: 13 additions & 19 deletions lib/src/code_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class EditorParams {
}

class CodeController extends TextEditingController {
/// A highligh language to parse the text with
/// A highlight language to parse the text with
final Mode? language;

/// The theme to apply to the [language] parsing result
Expand Down Expand Up @@ -64,8 +64,7 @@ class CodeController extends TextEditingController {
this.onChange,
}) : super(text: text) {
// PatternMap
if (language != null && theme == null)
throw Exception("A theme must be provided for language parsing");
if (language != null && theme == null) throw Exception("A theme must be provided for language parsing");
// Register language
if (language != null) {
highlight.registerLanguage(languageId, language!);
Expand All @@ -76,6 +75,11 @@ class CodeController extends TextEditingController {
});
}

/// Sets a specific cursor position in the text
void setCursor(int offset) {
selection = TextSelection.collapsed(offset: offset);
}

/// Replaces the current [selection] by [str]
void insertStr(String str) {
final sel = selection;
Expand Down Expand Up @@ -148,8 +152,7 @@ class CodeController extends TextEditingController {
const _chars = 'abcdefghijklmnopqrstuvwxyz1234567890';
final _rnd = Random();
return String.fromCharCodes(
Iterable.generate(
10, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length))),
Iterable.generate(10, (_) => _chars.codeUnitAt(_rnd.nextInt(_chars.length))),
);
}

Expand All @@ -175,11 +178,8 @@ class CodeController extends TextEditingController {
}
}
// Now fix the textfield for web
if (_webSpaceFix)
newValue = newValue.copyWith(text: _spacesToMiddleDots(newValue.text));
if (onChange != null)
onChange!(
_webSpaceFix ? _middleDotsToSpaces(newValue.text) : newValue.text);
if (_webSpaceFix) newValue = newValue.copyWith(text: _spacesToMiddleDots(newValue.text));
if (onChange != null) onChange!(_webSpaceFix ? _middleDotsToSpaces(newValue.text) : newValue.text);
super.value = newValue;
}

Expand All @@ -190,11 +190,7 @@ class CodeController extends TextEditingController {
onMatch: (Match m) {
if (styleList.isEmpty) return '';
int idx;
for (idx = 1;
idx < m.groupCount &&
idx <= styleList.length &&
m.group(idx) == null;
idx++) {}
for (idx = 1; idx < m.groupCount && idx <= styleList.length && m.group(idx) == null; idx++) {}
children.add(TextSpan(
text: m[0],
style: styleList[idx - 1],
Expand Down Expand Up @@ -225,8 +221,7 @@ class CodeController extends TextEditingController {
if (val != null) {
if (_webSpaceFix) val = _spacesToMiddleDots(val);
var child = TextSpan(text: val, style: theme?[node.className]);
if (styleRegExp != null)
child = _processPatterns(val, theme?[node.className]);
if (styleRegExp != null) child = _processPatterns(val, theme?[node.className]);
currentSpans.add(child);
} else if (nodeChildren != null) {
List<TextSpan> tmp = [];
Expand All @@ -250,8 +245,7 @@ class CodeController extends TextEditingController {
}

@override
TextSpan buildTextSpan(
{required BuildContext context, TextStyle? style, bool? withComposing}) {
TextSpan buildTextSpan({required BuildContext context, TextStyle? style, bool? withComposing}) {
// Retrieve pattern regexp
final patternList = <String>[];
if (_webSpaceFix) {
Expand Down
31 changes: 14 additions & 17 deletions lib/src/code_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,14 @@ class LineNumberController extends TextEditingController {
LineNumberController(this.lineNumberBuilder);

@override
TextSpan buildTextSpan(
{required BuildContext context, TextStyle? style, bool? withComposing}) {
TextSpan buildTextSpan({required BuildContext context, TextStyle? style, bool? withComposing}) {
final children = <TextSpan>[];
final list = text.split("\n");
for (int k = 0; k < list.length; k++) {
final el = list[k];
final number = int.parse(el);
var textSpan = TextSpan(text: el, style: style);
if (lineNumberBuilder != null)
textSpan = lineNumberBuilder!(number, style);
if (lineNumberBuilder != null) textSpan = lineNumberBuilder!(number, style);
children.add(textSpan);
if (k < list.length - 1) children.add(TextSpan(text: "\n"));
}
Expand Down Expand Up @@ -97,6 +95,7 @@ class CodeField extends StatefulWidget {
final Decoration? decoration;
final TextSelectionThemeData? textSelectionTheme;
final FocusNode? focusNode;
final void Function()? onTap;

const CodeField({
Key? key,
Expand All @@ -111,6 +110,7 @@ class CodeField extends StatefulWidget {
this.padding = const EdgeInsets.symmetric(),
this.lineNumberStyle = const LineNumberStyle(),
this.enabled,
this.onTap,
this.readOnly = false,
this.cursorColor,
this.textSelectionTheme,
Expand All @@ -124,12 +124,12 @@ class CodeField extends StatefulWidget {
}

class CodeFieldState extends State<CodeField> {
// Add a controller
// Add a controller
LinkedScrollControllerGroup? _controllers;
ScrollController? _numberScroll;
ScrollController? _codeScroll;
LineNumberController? _numberController;
//

StreamSubscription<bool>? _keyboardVisibilitySubscription;
FocusNode? _focusNode;
String? lines;
Expand All @@ -144,12 +144,14 @@ class CodeFieldState extends State<CodeField> {
_numberController = LineNumberController(widget.lineNumberBuilder);
widget.controller.addListener(_onTextChanged);
_focusNode = widget.focusNode ?? FocusNode();
_focusNode!.onKey = _onKey;
_focusNode!.attach(context, onKey: _onKey);

_onTextChanged();
}

KeyEventResult _onKey(FocusNode node, RawKeyEvent event) {
if (widget.readOnly) return KeyEventResult.ignored;
return widget.controller.onKey(event);
}

Expand Down Expand Up @@ -184,8 +186,7 @@ class CodeFieldState extends State<CodeField> {
}

// Wrap the codeField in a horizontal scrollView
Widget _wrapInScrollView(
Widget codeField, TextStyle textStyle, double minWidth) {
Widget _wrapInScrollView(Widget codeField, TextStyle textStyle, double minWidth) {
final leftPad = widget.lineNumberStyle.margin / 2;
final intrinsic = IntrinsicWidth(
child: Column(
Expand Down Expand Up @@ -225,8 +226,7 @@ class CodeFieldState extends State<CodeField> {
final defaultText = Colors.grey.shade200;

final theme = widget.controller.theme;
Color? backgroundCol =
widget.background ?? theme?[ROOT_KEY]?.backgroundColor ?? defaultBg;
Color? backgroundCol = widget.background ?? theme?[ROOT_KEY]?.backgroundColor ?? defaultBg;
if (widget.decoration != null) {
backgroundCol = null;
}
Expand All @@ -236,16 +236,14 @@ class CodeFieldState extends State<CodeField> {
fontSize: textStyle.fontSize ?? 16.0,
);
TextStyle numberTextStyle = widget.lineNumberStyle.textStyle ?? TextStyle();
final numberColor =
(theme?[ROOT_KEY]?.color ?? defaultText).withOpacity(0.7);
final numberColor = (theme?[ROOT_KEY]?.color ?? defaultText).withOpacity(0.7);
// Copy important attributes
numberTextStyle = numberTextStyle.copyWith(
color: numberTextStyle.color ?? numberColor,
fontSize: textStyle.fontSize,
fontFamily: textStyle.fontFamily,
);
final cursorColor =
widget.cursorColor ?? theme?[ROOT_KEY]?.color ?? defaultText;
final cursorColor = widget.cursorColor ?? theme?[ROOT_KEY]?.color ?? defaultText;

final lineNumberCol = TextField(
scrollPadding: widget.padding,
Expand Down Expand Up @@ -274,6 +272,7 @@ class CodeFieldState extends State<CodeField> {

final codeField = TextField(
focusNode: _focusNode,
onTap: widget.onTap,
scrollPadding: widget.padding,
style: textStyle,
controller: widget.controller,
Expand Down Expand Up @@ -301,9 +300,7 @@ class CodeFieldState extends State<CodeField> {
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Control horizontal scrolling
return widget.wrap
? codeField
: _wrapInScrollView(codeField, textStyle, constraints.maxWidth);
return widget.wrap ? codeField : _wrapInScrollView(codeField, textStyle, constraints.maxWidth);
},
),
);
Expand Down
15 changes: 5 additions & 10 deletions lib/src/code_modifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ abstract class CodeModifier {
);
}

TextEditingValue? updateString(
String text, TextSelection sel, EditorParams params);
TextEditingValue? updateString(String text, TextSelection sel, EditorParams params);
}

class IntendModifier extends CodeModifier {
Expand All @@ -31,8 +30,7 @@ class IntendModifier extends CodeModifier {
}) : super('\n');

@override
TextEditingValue? updateString(
String text, TextSelection sel, EditorParams params) {
TextEditingValue? updateString(String text, TextSelection sel, EditorParams params) {
var spacesCount = 0;
var braceCount = 0;
for (var k = min(sel.start, text.length) - 1; k >= 0; k--) {
Expand All @@ -55,8 +53,7 @@ class CloseBlockModifier extends CodeModifier {
const CloseBlockModifier() : super('}');

@override
TextEditingValue? updateString(
String text, TextSelection sel, EditorParams params) {
TextEditingValue? updateString(String text, TextSelection sel, EditorParams params) {
int spaceCount = 0;
for (var k = min(sel.start, text.length) - 1; k >= 0; k--) {
if (text[k] == "\n") break;
Expand All @@ -66,8 +63,7 @@ class CloseBlockModifier extends CodeModifier {
}
spaceCount += 1;
}
if (spaceCount >= params.tabSpaces)
return replace(text, sel.start - params.tabSpaces, sel.end, "}");
if (spaceCount >= params.tabSpaces) return replace(text, sel.start - params.tabSpaces, sel.end, "}");
return null;
}
}
Expand All @@ -76,8 +72,7 @@ class TabModifier extends CodeModifier {
const TabModifier() : super('\t');

@override
TextEditingValue? updateString(
String text, TextSelection sel, EditorParams params) {
TextEditingValue? updateString(String text, TextSelection sel, EditorParams params) {
final tmp = replace(text, sel.start, sel.end, " " * params.tabSpaces);
return tmp;
}
Expand Down
Loading