/* * @Description: Code highlight controller * @Author: ekibun * @Date: 2020-08-01 17:42:06 * @LastEditors: ekibun * @LastEditTime: 2020-08-02 12:39:26 */ import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_highlight/themes/a11y-light.dart'; import 'package:highlight/highlight.dart'; Map _theme = a11yLightTheme; List _convert(String code) { var nodes = highlight.parse(code, language: 'javascript').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; } class CodeInputController extends TextEditingController { CodeInputController({required String text}) : super(text: text); TextSpan oldSpan = TextSpan(); Future? spanCall; @override TextSpan buildTextSpan( {required BuildContext context, TextStyle? style, bool? withComposing}) { String oldText = oldSpan.toPlainText(); String newText = value.text; if (oldText == newText) return oldSpan; spanCall?.timeout(Duration.zero) .then((_) => spanCall = compute(_convert, value.text).then((lsSpan) { TextSpan newSpan = TextSpan(style: style, children: lsSpan); if (newSpan.toPlainText() == value.text) oldSpan = newSpan; notifyListeners(); })) .catchError((_) => {}); List beforeSpans = []; int splitAt = value.selection.start; if (splitAt < 0) splitAt = newText.length ~/ 2; int start = 0; InlineSpan? leftSpan; oldSpan.children?.indexWhere((element) { String elementText = element.toPlainText(); if (start + elementText.length > splitAt || !newText.startsWith(elementText, start)) { leftSpan = element; return true; } beforeSpans.add(element as TextSpan); start += elementText.length; return false; }); List endSpans = []; int end = 0; InlineSpan? rightSpan; oldSpan.children?.sublist(beforeSpans.length).lastIndexWhere((element) { String elementText = element.toPlainText(); if (splitAt + end + elementText.length >= newText.length || !newText .substring(start, newText.length - end) .endsWith(elementText)) { rightSpan = element; return true; } endSpans.add(element as TextSpan); end += elementText.length; return false; }); return TextSpan(style: style, children: [ ...beforeSpans, TextSpan( style: leftSpan != null && leftSpan == rightSpan! ? leftSpan!.style : style, text: newText.substring(start, max(start, newText.length - end))), ...endSpans.reversed ]); } }