• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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