1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:flutter/material.dart'; 6import 'package:string_scanner/string_scanner.dart'; 7 8class SyntaxHighlighterStyle { 9 SyntaxHighlighterStyle({ 10 this.baseStyle, 11 this.numberStyle, 12 this.commentStyle, 13 this.keywordStyle, 14 this.stringStyle, 15 this.punctuationStyle, 16 this.classStyle, 17 this.constantStyle, 18 }); 19 20 static SyntaxHighlighterStyle lightThemeStyle() { 21 return SyntaxHighlighterStyle( 22 baseStyle: const TextStyle(color: Color(0xFF000000)), 23 numberStyle: const TextStyle(color: Color(0xFF1565C0)), 24 commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), 25 keywordStyle: const TextStyle(color: Color(0xFF9C27B0)), 26 stringStyle: const TextStyle(color: Color(0xFF43A047)), 27 punctuationStyle: const TextStyle(color: Color(0xFF000000)), 28 classStyle: const TextStyle(color: Color(0xFF512DA8)), 29 constantStyle: const TextStyle(color: Color(0xFF795548)), 30 ); 31 } 32 33 static SyntaxHighlighterStyle darkThemeStyle() { 34 return SyntaxHighlighterStyle( 35 baseStyle: const TextStyle(color: Color(0xFFFFFFFF)), 36 numberStyle: const TextStyle(color: Color(0xFF1565C0)), 37 commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), 38 keywordStyle: const TextStyle(color: Color(0xFF80CBC4)), 39 stringStyle: const TextStyle(color: Color(0xFF009688)), 40 punctuationStyle: const TextStyle(color: Color(0xFFFFFFFF)), 41 classStyle: const TextStyle(color: Color(0xFF009688)), 42 constantStyle: const TextStyle(color: Color(0xFF795548)), 43 ); 44 } 45 46 final TextStyle baseStyle; 47 final TextStyle numberStyle; 48 final TextStyle commentStyle; 49 final TextStyle keywordStyle; 50 final TextStyle stringStyle; 51 final TextStyle punctuationStyle; 52 final TextStyle classStyle; 53 final TextStyle constantStyle; 54} 55 56abstract class SyntaxHighlighter { 57 TextSpan format(String src); 58} 59 60class DartSyntaxHighlighter extends SyntaxHighlighter { 61 DartSyntaxHighlighter([this._style]) { 62 _spans = <_HighlightSpan>[]; 63 _style ??= SyntaxHighlighterStyle.darkThemeStyle(); 64 } 65 66 SyntaxHighlighterStyle _style; 67 68 static const List<String> _keywords = <String>[ 69 'abstract', 'as', 'assert', 'async', 'await', 'break', 'case', 'catch', 70 'class', 'const', 'continue', 'default', 'deferred', 'do', 'dynamic', 'else', 71 'enum', 'export', 'external', 'extends', 'factory', 'false', 'final', 72 'finally', 'for', 'get', 'if', 'implements', 'import', 'in', 'is', 'library', 73 'new', 'null', 'operator', 'part', 'rethrow', 'return', 'set', 'static', 74 'super', 'switch', 'sync', 'this', 'throw', 'true', 'try', 'typedef', 'var', 75 'void', 'while', 'with', 'yield', 76 ]; 77 78 static const List<String> _builtInTypes = <String>[ 79 'int', 'double', 'num', 'bool', 80 ]; 81 82 String _src; 83 StringScanner _scanner; 84 85 List<_HighlightSpan> _spans; 86 87 @override 88 TextSpan format(String src) { 89 _src = src; 90 _scanner = StringScanner(_src); 91 92 if (_generateSpans()) { 93 // Successfully parsed the code 94 final List<TextSpan> formattedText = <TextSpan>[]; 95 int currentPosition = 0; 96 97 for (_HighlightSpan span in _spans) { 98 if (currentPosition != span.start) 99 formattedText.add(TextSpan(text: _src.substring(currentPosition, span.start))); 100 101 formattedText.add(TextSpan(style: span.textStyle(_style), text: span.textForSpan(_src))); 102 103 currentPosition = span.end; 104 } 105 106 if (currentPosition != _src.length) 107 formattedText.add(TextSpan(text: _src.substring(currentPosition, _src.length))); 108 109 return TextSpan(style: _style.baseStyle, children: formattedText); 110 } else { 111 // Parsing failed, return with only basic formatting 112 return TextSpan(style: _style.baseStyle, text: src); 113 } 114 } 115 116 bool _generateSpans() { 117 int lastLoopPosition = _scanner.position; 118 119 while (!_scanner.isDone) { 120 // Skip White space 121 _scanner.scan(RegExp(r'\s+')); 122 123 // Block comments 124 if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) { 125 _spans.add(_HighlightSpan( 126 _HighlightType.comment, 127 _scanner.lastMatch.start, 128 _scanner.lastMatch.end, 129 )); 130 continue; 131 } 132 133 // Line comments 134 if (_scanner.scan('//')) { 135 final int startComment = _scanner.lastMatch.start; 136 137 bool eof = false; 138 int endComment; 139 if (_scanner.scan(RegExp(r'.*\n'))) { 140 endComment = _scanner.lastMatch.end - 1; 141 } else { 142 eof = true; 143 endComment = _src.length; 144 } 145 146 _spans.add(_HighlightSpan( 147 _HighlightType.comment, 148 startComment, 149 endComment, 150 )); 151 152 if (eof) 153 break; 154 155 continue; 156 } 157 158 // Raw r"String" 159 if (_scanner.scan(RegExp(r'r".*"'))) { 160 _spans.add(_HighlightSpan( 161 _HighlightType.string, 162 _scanner.lastMatch.start, 163 _scanner.lastMatch.end, 164 )); 165 continue; 166 } 167 168 // Raw r'String' 169 if (_scanner.scan(RegExp(r"r'.*'"))) { 170 _spans.add(_HighlightSpan( 171 _HighlightType.string, 172 _scanner.lastMatch.start, 173 _scanner.lastMatch.end, 174 )); 175 continue; 176 } 177 178 // Multiline """String""" 179 if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { 180 _spans.add(_HighlightSpan( 181 _HighlightType.string, 182 _scanner.lastMatch.start, 183 _scanner.lastMatch.end, 184 )); 185 continue; 186 } 187 188 // Multiline '''String''' 189 if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { 190 _spans.add(_HighlightSpan( 191 _HighlightType.string, 192 _scanner.lastMatch.start, 193 _scanner.lastMatch.end, 194 )); 195 continue; 196 } 197 198 // "String" 199 if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) { 200 _spans.add(_HighlightSpan( 201 _HighlightType.string, 202 _scanner.lastMatch.start, 203 _scanner.lastMatch.end, 204 )); 205 continue; 206 } 207 208 // 'String' 209 if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) { 210 _spans.add(_HighlightSpan( 211 _HighlightType.string, 212 _scanner.lastMatch.start, 213 _scanner.lastMatch.end, 214 )); 215 continue; 216 } 217 218 // Double 219 if (_scanner.scan(RegExp(r'\d+\.\d+'))) { 220 _spans.add(_HighlightSpan( 221 _HighlightType.number, 222 _scanner.lastMatch.start, 223 _scanner.lastMatch.end, 224 )); 225 continue; 226 } 227 228 // Integer 229 if (_scanner.scan(RegExp(r'\d+'))) { 230 _spans.add(_HighlightSpan( 231 _HighlightType.number, 232 _scanner.lastMatch.start, 233 _scanner.lastMatch.end) 234 ); 235 continue; 236 } 237 238 // Punctuation 239 if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) { 240 _spans.add(_HighlightSpan( 241 _HighlightType.punctuation, 242 _scanner.lastMatch.start, 243 _scanner.lastMatch.end, 244 )); 245 continue; 246 } 247 248 // Meta data 249 if (_scanner.scan(RegExp(r'@\w+'))) { 250 _spans.add(_HighlightSpan( 251 _HighlightType.keyword, 252 _scanner.lastMatch.start, 253 _scanner.lastMatch.end, 254 )); 255 continue; 256 } 257 258 // Words 259 if (_scanner.scan(RegExp(r'\w+'))) { 260 _HighlightType type; 261 262 String word = _scanner.lastMatch[0]; 263 if (word.startsWith('_')) 264 word = word.substring(1); 265 266 if (_keywords.contains(word)) 267 type = _HighlightType.keyword; 268 else if (_builtInTypes.contains(word)) 269 type = _HighlightType.keyword; 270 else if (_firstLetterIsUpperCase(word)) 271 type = _HighlightType.klass; 272 else if (word.length >= 2 && word.startsWith('k') && _firstLetterIsUpperCase(word.substring(1))) 273 type = _HighlightType.constant; 274 275 if (type != null) { 276 _spans.add(_HighlightSpan( 277 type, 278 _scanner.lastMatch.start, 279 _scanner.lastMatch.end, 280 )); 281 } 282 } 283 284 // Check if this loop did anything 285 if (lastLoopPosition == _scanner.position) { 286 // Failed to parse this file, abort gracefully 287 return false; 288 } 289 lastLoopPosition = _scanner.position; 290 } 291 292 _simplify(); 293 return true; 294 } 295 296 void _simplify() { 297 for (int i = _spans.length - 2; i >= 0; i -= 1) { 298 if (_spans[i].type == _spans[i + 1].type && _spans[i].end == _spans[i + 1].start) { 299 _spans[i] = _HighlightSpan( 300 _spans[i].type, 301 _spans[i].start, 302 _spans[i + 1].end, 303 ); 304 _spans.removeAt(i + 1); 305 } 306 } 307 } 308 309 bool _firstLetterIsUpperCase(String str) { 310 if (str.isNotEmpty) { 311 final String first = str.substring(0, 1); 312 return first == first.toUpperCase(); 313 } 314 return false; 315 } 316} 317 318enum _HighlightType { 319 number, 320 comment, 321 keyword, 322 string, 323 punctuation, 324 klass, 325 constant 326} 327 328class _HighlightSpan { 329 _HighlightSpan(this.type, this.start, this.end); 330 final _HighlightType type; 331 final int start; 332 final int end; 333 334 String textForSpan(String src) { 335 return src.substring(start, end); 336 } 337 338 TextStyle textStyle(SyntaxHighlighterStyle style) { 339 if (type == _HighlightType.number) 340 return style.numberStyle; 341 else if (type == _HighlightType.comment) 342 return style.commentStyle; 343 else if (type == _HighlightType.keyword) 344 return style.keywordStyle; 345 else if (type == _HighlightType.string) 346 return style.stringStyle; 347 else if (type == _HighlightType.punctuation) 348 return style.punctuationStyle; 349 else if (type == _HighlightType.klass) 350 return style.classStyle; 351 else if (type == _HighlightType.constant) 352 return style.constantStyle; 353 else 354 return style.baseStyle; 355 } 356} 357