-
-
Save X-Wei/7370ec7823f9be40a91feb127627586d to your computer and use it in GitHub Desktop.
| // 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; | |
| } | |
| } |
════════ 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
...
════════════════════════════════════════════════════════════════════════════════

This file is adopted from the syntax_highlighter in flutter_garrery: https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/gallery/syntax_highlighter.dart