blob: d81430e5f99a50505e523a99c90abd11237ce2f9 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:string_scanner/string_scanner.dart';
class SyntaxHighlighterStyle {
SyntaxHighlighterStyle({
this.baseStyle,
this.numberStyle,
this.commentStyle,
this.keywordStyle,
this.stringStyle,
this.punctuationStyle,
this.classStyle,
this.constantStyle,
});
static SyntaxHighlighterStyle lightThemeStyle() {
return SyntaxHighlighterStyle(
baseStyle: const TextStyle(color: Color(0xFF000000)),
numberStyle: const TextStyle(color: Color(0xFF1565C0)),
commentStyle: const TextStyle(color: Color(0xFF9E9E9E)),
keywordStyle: const TextStyle(color: Color(0xFF9C27B0)),
stringStyle: const TextStyle(color: Color(0xFF43A047)),
punctuationStyle: const TextStyle(color: Color(0xFF000000)),
classStyle: const TextStyle(color: Color(0xFF512DA8)),
constantStyle: const TextStyle(color: Color(0xFF795548)),
);
}
static SyntaxHighlighterStyle darkThemeStyle() {
return SyntaxHighlighterStyle(
baseStyle: const TextStyle(color: Color(0xFFFFFFFF)),
numberStyle: const TextStyle(color: Color(0xFF1565C0)),
commentStyle: const TextStyle(color: Color(0xFF9E9E9E)),
keywordStyle: const TextStyle(color: Color(0xFF80CBC4)),
stringStyle: const TextStyle(color: Color(0xFF009688)),
punctuationStyle: const TextStyle(color: Color(0xFFFFFFFF)),
classStyle: const TextStyle(color: Color(0xFF009688)),
constantStyle: const TextStyle(color: Color(0xFF795548)),
);
}
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 SyntaxHighlighter {
TextSpan format(String src);
}
class DartSyntaxHighlighter extends SyntaxHighlighter {
DartSyntaxHighlighter([this._style]) {
_spans = <_HighlightSpan>[];
_style ??= SyntaxHighlighterStyle.darkThemeStyle();
}
SyntaxHighlighterStyle? _style;
static const List<String> _keywords = <String>[
'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',
'new', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static',
'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var',
'void', 'while', 'with', 'yield',
];
static const List<String> _builtInTypes = <String>[
'int', 'double', 'num', 'bool',
];
String? _src;
late StringScanner _scanner;
late List<_HighlightSpan> _spans;
@override
TextSpan format(String? src) {
_src = src;
_scanner = StringScanner(_src!);
if (_generateSpans()) {
// Successfully parsed the code
final List<TextSpan> formattedText = <TextSpan>[];
int currentPosition = 0;
for (final _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;
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,
_scanner.lastMatch!.end,
));
continue;
}
// Line comments
if (_scanner.scan('//')) {
final int startComment = _scanner.lastMatch!.start;
bool eof = false;
int endComment;
if (_scanner.scan(RegExp(r'.*\n'))) {
endComment = _scanner.lastMatch!.end - 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,
_scanner.lastMatch!.end,
));
continue;
}
// Raw r'String'
if (_scanner.scan(RegExp(r"r'.*'"))) {
_spans.add(_HighlightSpan(
_HighlightType.string,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// Multiline """String"""
if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
_spans.add(_HighlightSpan(
_HighlightType.string,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// Multiline '''String'''
if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
_spans.add(_HighlightSpan(
_HighlightType.string,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// "String"
if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) {
_spans.add(_HighlightSpan(
_HighlightType.string,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// 'String'
if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) {
_spans.add(_HighlightSpan(
_HighlightType.string,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// Double
if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
_spans.add(_HighlightSpan(
_HighlightType.number,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// Integer
if (_scanner.scan(RegExp(r'\d+'))) {
_spans.add(_HighlightSpan(
_HighlightType.number,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end)
);
continue;
}
// Punctuation
if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) {
_spans.add(_HighlightSpan(
_HighlightType.punctuation,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// Meta data
if (_scanner.scan(RegExp(r'@\w+'))) {
_spans.add(_HighlightSpan(
_HighlightType.keyword,
_scanner.lastMatch!.start,
_scanner.lastMatch!.end,
));
continue;
}
// Words
if (_scanner.scan(RegExp(r'\w+'))) {
_HighlightType? type;
String word = _scanner.lastMatch![0]!;
if (word.startsWith('_')) {
word = word.substring(1);
}
if (_keywords.contains(word)) {
type = _HighlightType.keyword;
} else if (_builtInTypes.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,
_scanner.lastMatch!.end,
));
}
}
// Check if this loop did anything
if (lastLoopPosition == _scanner.position) {
// Failed to parse this file, abort gracefully
return false;
}
lastLoopPosition = _scanner.position;
}
_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.isNotEmpty) {
final 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;
}
}
}