Last active
July 15, 2021 19:52
-
-
Save X-Wei/7370ec7823f9be40a91feb127627586d to your computer and use it in GitHub Desktop.
syntax-highlighter for flutter_markdown (adopted from flutter_gallery)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2016 The Chromium 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:flutter_markdown/flutter_markdown.dart' show SyntaxHighlighter; | |
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), | |
fontFamily: 'monospace', | |
fontSize: 14.0, | |
), | |
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; | |
} | |
class DartSyntaxHighlighter extends SyntaxHighlighter { | |
DartSyntaxHighlighter([this._style]) { | |
_spans = <_HighlightSpan>[]; | |
_style ??= SyntaxHighlighterStyle.lightThemeStyle(); | |
} | |
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; | |
StringScanner _scanner; | |
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 (_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))); | |
_spans.clear(); | |
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
════════ Exception caught by widgets library ═══════════════════════════════════
The following RangeError was thrown building _BodyBuilder:
Value not in range: 66
The relevant error-causing widget was
Scaffold
lib\main_viewer.dart:42
When the exception was thrown, this was the stack
#0 _StringBase.substring (dart:core-patch/string_patch.dart:388:7)
#1 _HighlightSpan.textForSpan
package:coder_side/syntax_highlighter.dart:348
#2 DartSyntaxHighlighter.format
package:coder_side/syntax_highlighter.dart:153
#3 _MarkdownWidgetState.formatText
package:flutter_markdown/src/widget.dart:199
#4 MarkdownBuilder.visitText
package:flutter_markdown/src/builder.dart:212
...
════════════════════════════════════════════════════════════════════════════════