diff --git a/README.md b/README.md index b7906c6..218dd13 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +> NOTE: this fork has been archived in favour of hosting my own [flutter_highlight](https://github.com/tusharsadhwani/flutter_highlight) repo for ease of use. + # highlight Syntax highlighting for Dart and Flutter, which supports lots of languages and themes. diff --git a/flutter_highlight/example/android/gradle.properties b/flutter_highlight/example/android/gradle.properties index 2bd6f4f..1441b1d 100644 --- a/flutter_highlight/example/android/gradle.properties +++ b/flutter_highlight/example/android/gradle.properties @@ -1,2 +1,3 @@ org.gradle.jvmargs=-Xmx1536M +android.enableR8=true diff --git a/flutter_highlight/example/lib/main.dart b/flutter_highlight/example/lib/main.dart index 74ada48..989bc5b 100644 --- a/flutter_highlight/example/lib/main.dart +++ b/flutter_highlight/example/lib/main.dart @@ -92,17 +92,20 @@ class _MyHomePageState extends State { ], ), body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, + child: ListView( + primary: false, + shrinkWrap: true, children: [ HighlightView( - exampleMap[language], + text: exampleMap[language], + editable: true, language: language, theme: themeMap[theme], padding: EdgeInsets.all(12), textStyle: TextStyle( - fontFamily: - 'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace'), + fontFamily: + 'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace', + ), ) ], ), diff --git a/flutter_highlight/example/pubspec.yaml b/flutter_highlight/example/pubspec.yaml index 417fdba..7f96469 100644 --- a/flutter_highlight/example/pubspec.yaml +++ b/flutter_highlight/example/pubspec.yaml @@ -1,6 +1,8 @@ name: flutter_highlight_gallery description: A new Flutter project. +publish_to: none + # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. diff --git a/flutter_highlight/lib/flutter_highlight.dart b/flutter_highlight/lib/flutter_highlight.dart index 43b7fa1..19c0b13 100644 --- a/flutter_highlight/lib/flutter_highlight.dart +++ b/flutter_highlight/lib/flutter_highlight.dart @@ -3,9 +3,12 @@ import 'package:flutter/widgets.dart'; import 'package:highlight/highlight.dart' show highlight, Node; /// Highlight Flutter Widget -class HighlightView extends StatelessWidget { +class HighlightView extends StatefulWidget { /// The original code to be highlighted - final String source; + final String text; + + /// The controller for the highlighted text + final HighlightEditingController controller; /// Highlight language /// @@ -19,6 +22,12 @@ class HighlightView extends StatelessWidget { /// [All available themes](https://github.com/pd4d10/highlight/blob/master/flutter_highlight/lib/themes) final Map theme; + /// Editable + /// + /// If set to true, this will render a TextField instead. + /// Defaults to false. + final bool editable; + /// Padding final EdgeInsetsGeometry padding; @@ -27,46 +36,17 @@ class HighlightView extends StatelessWidget { /// Specify text styles such as font family and font size final TextStyle textStyle; - HighlightView( - String input, { + HighlightView({ + this.text, + this.controller, this.language, this.theme = const {}, + this.editable = false, this.padding, this.textStyle, int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087 - }) : source = input.replaceAll('\t', ' ' * tabSize); - - List _convert(List nodes) { - List spans = []; - var currentSpans = spans; - List> stack = []; - - _traverse(Node node) { - if (node.value != null) { - currentSpans.add(node.className == null - ? TextSpan(text: node.value) - : TextSpan(text: node.value, style: theme[node.className])); - } else if (node.children != null) { - List tmp = []; - currentSpans.add(TextSpan(children: tmp, style: theme[node.className])); - stack.add(currentSpans); - currentSpans = tmp; - - node.children.forEach((n) { - _traverse(n); - if (n == node.children.last) { - currentSpans = stack.isEmpty ? spans : stack.removeLast(); - } - }); - } - } - - for (var node in nodes) { - _traverse(node); - } - - return spans; - } + }) : assert(text != null || controller != null, + 'One of text or controller properties must be provided'); static const _rootKey = 'root'; static const _defaultFontColor = Color(0xff000000); @@ -77,25 +57,157 @@ class HighlightView extends StatelessWidget { // So we just use monospace here for now static const _defaultFontFamily = 'monospace'; + @override + _HighlightViewState createState() => _HighlightViewState(); +} + +class _HighlightViewState extends State { + String source; + HighlightEditingController controller; + + @override + void initState() { + super.initState(); + if (widget.controller == null) { + controller = HighlightEditingController(widget.language, widget.theme); + controller.text = widget.text; + } else { + controller = widget.controller; + } + source = controller.text; + } + @override Widget build(BuildContext context) { var _textStyle = TextStyle( - fontFamily: _defaultFontFamily, - color: theme[_rootKey]?.color ?? _defaultFontColor, + fontFamily: HighlightView._defaultFontFamily, + color: widget.theme[HighlightView._rootKey]?.color ?? + HighlightView._defaultFontColor, ); - if (textStyle != null) { - _textStyle = _textStyle.merge(textStyle); + if (widget.textStyle != null) { + _textStyle = _textStyle.merge(widget.textStyle); } return Container( - color: theme[_rootKey]?.backgroundColor ?? _defaultBackgroundColor, - padding: padding, - child: RichText( - text: TextSpan( - style: _textStyle, - children: _convert(highlight.parse(source, language: language).nodes), - ), - ), + color: widget.theme[HighlightView._rootKey]?.backgroundColor ?? + HighlightView._defaultBackgroundColor, + padding: widget.padding, + child: widget.editable + ? TextField( + controller: controller, + decoration: InputDecoration(border: InputBorder.none), + maxLines: null, + style: widget.theme[HighlightView._rootKey], + ) + : RichText( + text: TextSpan( + style: _textStyle, + children: getHighlightTextSpan( + source, + widget.language, + widget.theme, + ), + ), + ), + ); + } +} + +List getHighlightTextSpan(source, language, theme) { + return _convert(highlight.parse(source, language: language).nodes, theme); +} + +class HighlightEditingController extends TextEditingController { + String language; + Map theme; + + HighlightEditingController(this.language, this.theme); + + @override + TextSpan buildTextSpan({TextStyle style, bool withComposing}) { + final result = highlight.parse(value.text, language: language); + final spans = TextSpan( + style: style, + children: _convert(result.nodes, theme), ); + if (value.composing.isValid && withComposing) { + underlineComposing(spans); + } + return spans; + } + + underlineComposing(TextSpan nodes) { + var pos = 0; + final TextStyle composingStyle = + TextStyle(decoration: TextDecoration.underline); + + TextSpan _traverse(TextSpan node) { + if (node.text != null && + pos <= value.composing.start && + value.composing.end <= pos + node.text.length) { + var relativeComposing = TextRange( + start: value.composing.start - pos, + end: value.composing.end - pos, + ); + return TextSpan( + children: [ + TextSpan(text: relativeComposing.textBefore(node.text)), + TextSpan( + style: composingStyle, + text: relativeComposing.textInside(node.text), + ), + TextSpan(text: relativeComposing.textAfter(node.text)), + ], + ); + } + + pos += node.text?.length ?? 0; + if (node.children != null) { + for (var i = 0; + i < node.children.length && pos <= value.composing.start; + i++) { + var update = _traverse(node.children[i]); + if (update != null) { + node.children[i] = update; + return null; + } + } + } + return null; + } + + _traverse(nodes); + } +} + +List _convert(List nodes, theme) { + List spans = []; + var currentSpans = spans; + List> stack = []; + + _traverse(Node node) { + if (node.value != null) { + currentSpans.add(node.className == null + ? TextSpan(text: node.value) + : TextSpan(text: node.value, style: theme[node.className])); + } else if (node.children != null) { + List tmp = []; + currentSpans.add(TextSpan(children: tmp, style: theme[node.className])); + stack.add(currentSpans); + currentSpans = tmp; + + node.children.forEach((n) { + _traverse(n); + if (n == node.children.last) { + currentSpans = stack.isEmpty ? spans : stack.removeLast(); + } + }); + } } + + for (var node in nodes) { + _traverse(node); + } + + return spans; } diff --git a/flutter_highlight/pubspec.yaml b/flutter_highlight/pubspec.yaml index ea30628..151a685 100644 --- a/flutter_highlight/pubspec.yaml +++ b/flutter_highlight/pubspec.yaml @@ -4,13 +4,16 @@ version: 0.6.0+1 author: Rongjian Zhang homepage: https://github.com/git-touch/highlight +publish_to: none + environment: sdk: ">=2.3.0 <3.0.0" dependencies: flutter: sdk: flutter - highlight: ^0.6.0 + highlight: + path: ../highlight dev_dependencies: flutter_test: