forked from lxm_flutter/FlutterUnit
409 lines
11 KiB
Dart
409 lines
11 KiB
Dart
import 'package:string_scanner/string_scanner.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class SyntaxHighlighterStyle {
|
|
SyntaxHighlighterStyle(
|
|
{this.baseStyle,
|
|
this.numberStyle,
|
|
this.commentStyle,
|
|
this.keywordStyle,
|
|
this.stringStyle,
|
|
this.punctuationStyle,
|
|
this.classStyle,
|
|
this.constantStyle});
|
|
|
|
//123
|
|
static SyntaxHighlighterStyle defaultStyle() {
|
|
return SyntaxHighlighterStyle(
|
|
baseStyle: TextStyle(color: Color.fromRGBO(212, 212, 212, 1.0)),
|
|
numberStyle: TextStyle(color: Colors.blue[800]),
|
|
commentStyle: TextStyle(color: Color.fromRGBO(124, 126, 120, 1.0)),
|
|
keywordStyle: TextStyle(color: Color.fromRGBO(228, 125, 246, 1.0)),
|
|
stringStyle: TextStyle(color: Color.fromRGBO(150, 190, 118, 1.0)),
|
|
punctuationStyle:
|
|
TextStyle(color: Color.fromRGBO(212, 212, 212, 1.0)),
|
|
classStyle: TextStyle(color: Color.fromRGBO(150, 190, 118, 1.0)),
|
|
constantStyle: TextStyle(color: Colors.brown[500]));
|
|
}
|
|
|
|
final TextStyle? baseStyle;
|
|
final TextStyle? numberStyle;
|
|
final TextStyle? commentStyle;
|
|
final TextStyle? keywordStyle;
|
|
final TextStyle? stringStyle;
|
|
final TextStyle? punctuationStyle;
|
|
final TextStyle? classStyle;
|
|
final TextStyle? constantStyle;
|
|
}
|
|
|
|
abstract class SyntaxCostomHighlighter {
|
|
TextSpan format(String src);
|
|
}
|
|
|
|
class DartSyntaxHighlighter extends SyntaxCostomHighlighter {
|
|
DartSyntaxHighlighter([this._style]) {
|
|
_spans = <_HighlightSpan>[];
|
|
|
|
if (_style == null) _style = SyntaxHighlighterStyle.defaultStyle();
|
|
}
|
|
|
|
SyntaxHighlighterStyle? _style;
|
|
|
|
static const List<String> _kKeywords = [
|
|
'abstract',
|
|
'as',
|
|
'assert',
|
|
'async',
|
|
'await',
|
|
'break',
|
|
'case',
|
|
'catch',
|
|
'class',
|
|
'const',
|
|
'continue',
|
|
'default',
|
|
'deferred',
|
|
'do',
|
|
'dynamic',
|
|
'else',
|
|
'enum',
|
|
'export',
|
|
'external',
|
|
'extends',
|
|
'factory',
|
|
'false',
|
|
'final',
|
|
'finally',
|
|
'for',
|
|
'get',
|
|
'if',
|
|
'implements',
|
|
'import',
|
|
'in',
|
|
'is',
|
|
'library',
|
|
'',
|
|
'null',
|
|
'operator',
|
|
'part',
|
|
'rethrow',
|
|
'return',
|
|
'set',
|
|
'static',
|
|
'super',
|
|
'switch',
|
|
'sync',
|
|
'this',
|
|
'throw',
|
|
'true',
|
|
'try',
|
|
'typedef',
|
|
'var',
|
|
'void',
|
|
'while',
|
|
'with',
|
|
'yield',
|
|
'print',
|
|
'function',
|
|
'public',
|
|
'protected',
|
|
'private',
|
|
'namespace',
|
|
'using',
|
|
'extends',
|
|
'let',
|
|
'export',
|
|
'default',
|
|
'import',
|
|
'from',
|
|
'PureCommponent',
|
|
'constructor',
|
|
'render',
|
|
'\$sudo',
|
|
'console',
|
|
'instanceof'
|
|
];
|
|
|
|
static const List<String> _kBuiltInTypes = [
|
|
'int',
|
|
'double',
|
|
'num',
|
|
'bool'
|
|
];
|
|
|
|
String _src='';
|
|
late StringScanner _scanner;
|
|
|
|
List<_HighlightSpan> _spans =[];
|
|
|
|
TextSpan format(String src) {
|
|
_src = src;
|
|
_scanner = StringScanner(_src);
|
|
|
|
if (_generateSpans()) {
|
|
// Successfully parsed the code
|
|
List<TextSpan> formattedText = <TextSpan>[];
|
|
int currentPosition = 0;
|
|
|
|
for (_HighlightSpan span in _spans) {
|
|
if (currentPosition != span.start)
|
|
formattedText.add(
|
|
TextSpan(text: _src.substring(currentPosition, span.start)));
|
|
|
|
formattedText.add( TextSpan(
|
|
style: span.textStyle(_style), text: span.textForSpan(_src)));
|
|
|
|
currentPosition = span.end;
|
|
}
|
|
|
|
if (currentPosition != _src.length)
|
|
formattedText.add(
|
|
TextSpan(text: _src.substring(currentPosition, _src.length)));
|
|
|
|
return TextSpan(style: _style?.baseStyle, children: formattedText);
|
|
} else {
|
|
// Parsing failed, return with only basic formatting
|
|
return TextSpan(style: _style?.baseStyle, text: src);
|
|
}
|
|
}
|
|
|
|
bool _generateSpans() {
|
|
int lastLoopPosition = _scanner.position;
|
|
|
|
try {
|
|
while (!_scanner.isDone) {
|
|
// Skip White space
|
|
_scanner.scan( RegExp(r"\s+"));
|
|
|
|
// Block comments
|
|
if (_scanner.scan( RegExp(r"/\*(.|\n)*\*/"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.comment,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Line comments
|
|
if (_scanner.scan("//")) {
|
|
int startComment = _scanner.lastMatch?.start??0;
|
|
|
|
bool eof = false;
|
|
int endComment;
|
|
if (_scanner.scan( RegExp(r".*\n"))) {
|
|
endComment = _scanner.lastMatch?.end??0 - 1;
|
|
} else {
|
|
eof = true;
|
|
endComment = _src.length;
|
|
}
|
|
|
|
_spans.add( _HighlightSpan(
|
|
_HighlightType.comment, startComment, endComment));
|
|
|
|
if (eof) break;
|
|
|
|
continue;
|
|
}
|
|
|
|
if (_scanner.scan("#")) {
|
|
int startComment = _scanner.lastMatch?.start??0;
|
|
|
|
bool eof = false;
|
|
int endComment;
|
|
|
|
if (_scanner.scan( RegExp(r".*\n"))) {
|
|
endComment = _scanner.lastMatch?.end??0 - 1;
|
|
} else {
|
|
eof = true;
|
|
endComment = _src.length;
|
|
}
|
|
|
|
_spans.add( _HighlightSpan(
|
|
_HighlightType.comment, startComment, endComment));
|
|
|
|
if (eof) break;
|
|
|
|
continue;
|
|
}
|
|
// Raw r"String"
|
|
if (_scanner.scan( RegExp(r'r".*"'))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.string,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Raw r'String'
|
|
if (_scanner.scan( RegExp(r"r'.*'"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.string,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Multiline """String"""
|
|
if (_scanner.scan( RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.string,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Multiline '''String'''
|
|
if (_scanner.scan( RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.string,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// "String"
|
|
if (_scanner.scan( RegExp(r'"(?:[^"\\]|\\.)*"'))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.string,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// 'String'
|
|
if (_scanner.scan( RegExp(r"'(?:[^'\\]|\\.)*'"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.string,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Double
|
|
if (_scanner.scan( RegExp(r"\d+\.\d+"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.number,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Integer
|
|
if (_scanner.scan( RegExp(r"\d+"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.number,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Punctuation
|
|
if (_scanner.scan( RegExp(r"[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.punctuation,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
//中文
|
|
if (_scanner.scan( RegExp(r"[\u4e00-\u9fa5]"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.punctuation,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Metadata
|
|
if (_scanner.scan( RegExp(r"@\w+"))) {
|
|
_spans.add( _HighlightSpan(_HighlightType.keyword,
|
|
_scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
continue;
|
|
}
|
|
|
|
// Words
|
|
if (_scanner.scan( RegExp(r"\w+"))) {
|
|
_HighlightType? type;
|
|
|
|
String word = _scanner.lastMatch![0]!;
|
|
if (word.startsWith("_")) word = word.substring(1);
|
|
|
|
if (_kKeywords.contains(word))
|
|
type = _HighlightType.keyword;
|
|
else if (_kBuiltInTypes.contains(word))
|
|
type = _HighlightType.keyword;
|
|
else if (_firstLetterIsUpperCase(word))
|
|
type = _HighlightType.klass;
|
|
else if (word.length >= 2 &&
|
|
word.startsWith("k") &&
|
|
_firstLetterIsUpperCase(word.substring(1)))
|
|
type = _HighlightType.constant;
|
|
|
|
if (type != null) {
|
|
_spans.add( _HighlightSpan(
|
|
type, _scanner.lastMatch?.start??0, _scanner.lastMatch?.end??0));
|
|
}
|
|
}
|
|
|
|
// Check if this loop did anything
|
|
if (lastLoopPosition == _scanner.position) {
|
|
// Failed to parse this file, abort gracefully
|
|
if (_spans.length > 0) {
|
|
_spans.add( _HighlightSpan(_HighlightType.punctuation,
|
|
lastLoopPosition, _scanner.string.length - 1));
|
|
_simplify();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
lastLoopPosition = _scanner.position;
|
|
}
|
|
} catch (e) {
|
|
print(e.toString());
|
|
}
|
|
|
|
_simplify();
|
|
return true;
|
|
}
|
|
|
|
void _simplify() {
|
|
for (int i = _spans.length - 2; i >= 0; i -= 1) {
|
|
if (_spans[i].type == _spans[i + 1].type &&
|
|
_spans[i].end == _spans[i + 1].start) {
|
|
_spans[i] = _HighlightSpan(
|
|
_spans[i].type, _spans[i].start, _spans[i + 1].end);
|
|
_spans.removeAt(i + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _firstLetterIsUpperCase(String str) {
|
|
if (str.length > 0) {
|
|
String first = str.substring(0, 1);
|
|
return first == first.toUpperCase();
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
enum _HighlightType {
|
|
number,
|
|
comment,
|
|
keyword,
|
|
string,
|
|
punctuation,
|
|
klass,
|
|
constant
|
|
}
|
|
|
|
class _HighlightSpan {
|
|
_HighlightSpan(this.type, this.start, this.end);
|
|
|
|
final _HighlightType type;
|
|
final int start;
|
|
final int end;
|
|
|
|
String textForSpan(String src) {
|
|
return src.substring(start, end);
|
|
}
|
|
|
|
TextStyle? textStyle(SyntaxHighlighterStyle? style) {
|
|
if (type == _HighlightType.number)
|
|
return style?.numberStyle;
|
|
else if (type == _HighlightType.comment)
|
|
return style?.commentStyle;
|
|
else if (type == _HighlightType.keyword)
|
|
return style?.keywordStyle;
|
|
else if (type == _HighlightType.string)
|
|
return style?.stringStyle;
|
|
else if (type == _HighlightType.punctuation)
|
|
return style?.punctuationStyle;
|
|
else if (type == _HighlightType.klass)
|
|
return style?.classStyle;
|
|
else if (type == _HighlightType.constant)
|
|
return style?.constantStyle;
|
|
else
|
|
return style?.baseStyle;
|
|
}
|
|
}
|