diff --git a/flutter_highlight/lib/flutter_highlight.dart b/flutter_highlight/lib/flutter_highlight.dart index 43b7fa1..518dec3 100644 --- a/flutter_highlight/lib/flutter_highlight.dart +++ b/flutter_highlight/lib/flutter_highlight.dart @@ -36,38 +36,6 @@ class HighlightView extends StatelessWidget { 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; - } - static const _rootKey = 'root'; static const _defaultFontColor = Color(0xff000000); static const _defaultBackgroundColor = Color(0xffffffff); @@ -93,9 +61,105 @@ class HighlightView extends StatelessWidget { child: RichText( text: TextSpan( style: _textStyle, - children: _convert(highlight.parse(source, language: language).nodes), + children: getHighlightTextSpan(source, language, 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; +}