preface

After the rich text introduction of the previous two articles. Now that we have a general understanding of the use of StringScanner, we looked at the highlighting function of code blocks in Flutter /gallery and used it in FlutterUnit. Currently, flutter/gallery generates all the code corresponding to the TextSpan directly via codeViewer_cli, a 45,295-line, 2.6MB file, and provides the required TextSpan via static methods. Both generate the corresponding TextSpan. The previous code highlighting logic can be viewed in the package syntax_highlighter.


Effect:

This article steps through a simple code highlighting display:

Not highlight Has highlighted

Other articles in this series
  • The Flutter Text reading 1 | know from source Text component”
  • 2 | the Flutter Text reading Text is how to draw out”
  • The 3 | Flutter Text reading Text component using introduction
  • 4 the Flutter text reading | TextStyle text style interpretation”
  • 5 the Flutter text reading | RichText the use of the rich text (on)”
  • 6 the Flutter text reading | RichText rich text use (in)”

1. Highlight keywords

1. Resource introduction

The test code string here is placed in assets. And configure it in pubspec.yaml.

Read the string with rootBundle#loadString.

void _loadData() async {
  content = await rootBundle.loadString("assets/code.dart");
}
Copy the code

2. Highlight the word

For example, we now want the final word to be highlighted. How do we do that? The implementation needs to find the starting and ending positions of each final in the text, and then record both positions. The information is stored via SpanBean.

class SpanBean {
  SpanBean(this.start, this.end, {this.recognizer});

  final int start;
  final int end;

  String text(String src){
    return src.substring(start,end);
  }

  TextStyle get style {
    return TextStyle(
      color: Colors.green,
      fontWeight: FontWeight.bold
    );
  }

  final GestureRecognizer recognizer;
}
Copy the code

Corresponds to a CodeParser class that parses code strings. The implementation uses StringScanner to scan the text through the _parseContent method. You can match words with the regular expression RegExp(r’\w+’), and if the word is final, you go to _SPANS. After the scan is complete, InlineSpan is generated by _formInlineSpanByBean.

class CodeParser {
  StringScanner _scanner;

  InlineSpan parser(String content) {
    _scanner = StringScanner(content);
    _parseContent();

    return _formInlineSpanByBean(content);
  }

  List<SpanBean> _spans = [];

  void _parseContent() {
    while(! _scanner.isDone) {if (_scanner.scan(RegExp(r'\w+'))) {
        int startIndex = _scanner.lastMatch.start;
        int endIndex = _scanner.lastMatch.end;
        String word = _scanner.lastMatch[0];
        if (word == 'final'){ _spans.add(SpanBean(startIndex, endIndex)); }}if(! _scanner.isDone) { _scanner.position++; }}}voiddispose() { _spans.forEach((element) { element.recognizer? .dispose(); }); } InlineSpan _formInlineSpanByBean(String content) {
    final List<TextSpan> spans = <TextSpan>[];
    int currentPosition = 0;

    for (SpanBean span in _spans) {
      if(currentPosition ! = span.start) { spans.add( TextSpan(text: content.substring(currentPosition, span.start))); } spans.add(TextSpan( style: span.style, text: span.text(content), recognizer: span.recognizer)); currentPosition = span.end; }if(currentPosition ! = content.length) spans.add( TextSpan(text: content.substring(currentPosition, content.length)));returnTextSpan(style: TextStyleSupport.defaultStyle, children: spans); }}Copy the code

3. Keyword highlighting

Now that I’ve made the qualitative change from zero to one, it’s easier from there. Considering that different languages may have different keywords, an interface Language can be defined to facilitate expansion.

abstract class Language {
  final String name;

  const Language(this.name);

  bool containsKeywords(String word);
}
Copy the code

This allows Dart syntax keywords to be highlighted through DartLanguage, making it easier to add and change keywords if they are added or removed from Dart.

class DartLanguage extends Language{

  const DartLanguage() : super('Dart');

  static const List<String> _kDartKeywords = [
  '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'
  ];

  @override
  bool containsKeywords(String word)=>_kDartInTypes.contains(word);
  
}
Copy the code

Can be introduced into Language in CodeParser object, when parsing through Language. Whether containsKeywords judgment for the keywords of the Language.


class CodeParser {
  StringScanner _scanner;
  final Language language;
  CodeParser({this.language = const DartLanguage()});

---->[CodeParser#_parseContent]----
if (_scanner.scan(RegExp(r'\w+'))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  String word = _scanner.lastMatch[0];
  if(language.containsKeywords(word)){ _spans.add(SpanBean(startIndex, endIndex)); }}Copy the code

This gives the effect of changing the highlighting style using the Style in the SpanBean.


Class name and comment highlighting

1. Highlight type definitions

Now we need to extend the type of highlighting maintained by SpanType. And through the StyleSupport kGithubLight maintain a, types and writing style of the mapping. Passing SpanType in the SpanBean eliminates the need to branch out the textStyles corresponding to the highlighted types.

enum SpanType { keyword, clazz }

class StyleSupport {
  static const Map<SpanType, TextStyle> kGithubLight = {
    SpanType.keyword: TextStyle(fontWeight: FontWeight.bold, color: const Color(0xFF009999)),
    SpanType.clazz: TextStyle(color: const Color(0xFF6F42C1)),}; }class SpanBean {
  SpanBean(this.start, this.end, this.type, {this.recognizer});

  final int start;
  final int end;
  final SpanType type;

  String text(String src) {
    return src.substring(start, end);
  }
  
  TextStyle get style => StyleSupport.kGithubLight[type];

  final GestureRecognizer recognizer;
}

Copy the code

2. Class name resolution

Class names are easy to determine by capitalizing the first letter.

if (_scanner.scan(RegExp(r'\w+'))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  String word = _scanner.lastMatch[0];
  if (language.containsKeywords(word)){
    _spans.add(SpanBean(startIndex, endIndex,SpanType.keyword));
  }else if (_firstLetterIsUpperCase(word)){
    // The type is the class name_spans.add(SpanBean(startIndex, endIndex,SpanType.clazz)); }}bool _firstLetterIsUpperCase(String str) {
  if (str.isNotEmpty) {
    final String first = str.substring(0.1);
    return first == first.toUpperCase();
  }
  return false;
}
Copy the code

The result is as follows, but you can see that the class name in the comment is also highlighted. As long as it is processed before the class name is parsed, StringScanner scans annotations and has no effect on subsequent processing.


Third, comment highlighting

1. Add a type

Add comment to the type as follows and provide the corresponding style:

enum SpanType { keyword, clazz, comment }

class StyleSupport {
  static const Map<SpanType, TextStyle> kGithubLight = {
    SpanType.keyword: TextStyle(fontWeight: FontWeight.bold, color: const Color(0xFF009999)),
    SpanType.clazz: TextStyle(color: const Color(0xFF6F42C1)),
    SpanType.comment: TextStyle(color: Color(0xFF9D9D8D), fontStyle: FontStyle.italic),
  };
}
Copy the code

2. Analytical processing

Comments are divided into block comments and line comments. Since line comments end with \n, if the last line is a comment, it needs to be treated separately.

/ / comment
if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) {
  int startIndex = _scanner.lastMatch.start;
  int endIndex = _scanner.lastMatch.end;
  _spans.add(SpanBean( startIndex, endIndex,SpanType.comment));
}

/ / comment line
if (_scanner.scan('/ /')) {
  final int startIndex = _scanner.lastMatch.start;
  int endIndex;
  if (_scanner.scan(RegExp(r'.*\n'))) {
    endIndex = _scanner.lastMatch.end - 1;
  } else {
    endIndex = content.length;
  }
  _spans.add(SpanBean(startIndex, endIndex ,SpanType.comment));
}
Copy the code

The comment effect is as follows:


String parsing

1. Add a type

Add string to the type and provide the corresponding style as follows:

enum SpanType { keyword, clazz, comment, string }

class StyleSupport {
  static const Map<SpanType, TextStyle> kGithubLight = {
    SpanType.keyword: TextStyle(fontWeight: FontWeight.bold, color: const Color(0xFF009999)),
    SpanType.clazz: TextStyle(color: const Color(0xFF6F42C1)),
    SpanType.comment: TextStyle(color: Color(0xFF9D9D8D), fontStyle: FontStyle.italic),
    SpanType.string: TextStyle(color: Color(0xFFDD1045)),}; }Copy the code

2. Analytical processing

There are six cases of a string, as follows:

// r"String"
if (_scanner.scan(RegExp(r'r".*"'))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.string));
}

// r'String'
if (_scanner.scan(RegExp(r"r'.*'"))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.string));
}

// """String"""
if (_scanner.scan(RegExp(r'"""(? :[^"\\]|\\(.|\n))*"""'))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.string));
}

// '''String'''
if (_scanner.scan(RegExp(r"'''(? :[^'\\]|\\(.|\n))*'''"))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.string));
}

// "String"
if (_scanner.scan(RegExp(r'"(? : [^ | \ \. \ \] ") * ' '))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.string));
}

// 'String'
if (_scanner.scan(RegExp(r"'(? : [^ '| \ \. \ \]) * '"))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.string));
}
Copy the code

Numbers and punctuation

1. Add a type

Add number and punctuation to the type as follows, and provide the corresponding styles:

enum SpanType { keyword, clazz, comment, string, number, punctuation,}
class StyleSupport {
  static const Map<SpanType, TextStyle> kGithubLight = {
    SpanType.keyword: TextStyle(fontWeight: FontWeight.bold, color: const Color(0xFF009999)),
    
    SpanType.clazz: TextStyle(color: const Color(0xFF6F42C1)),
    SpanType.comment: TextStyle(color: Color(0xFF9D9D8D), fontStyle: FontStyle.italic),
    SpanType.string: TextStyle(color: Color(0xFFDD1045),),
    SpanType.number: TextStyle(color: Color(0xFF008081),),
    SpanType.punctuation: TextStyle(color: Color(0xFF333333,),),}; }Copy the code

2. Analytical processing

Now that you have the basic code highlighting types, you can extend them by parsing them yourself if you need more. In general, the most important thing is how to resolve through re.

// Double
if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.number));
}

// Integer
if (_scanner.scan(RegExp(r'\d+'))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.number));
}

// Punctuation
if (_scanner.scan(RegExp(r'[\[\]\{\}\(\).!=<>&\|\?\+\-\*/%\^~;:,]'))) {
  _spans.add(SpanBean(_scanner.lastMatch.start,
      _scanner.lastMatch.end,SpanType.punctuation));
}
Copy the code


Six, code style switch

You can define other styles in StyleSupport for switching. Styles can also be exposed as members of CodeParser to facilitate custom styles.

Style 1 Styles 2,
class StyleSupport {
  static const Map<SpanType, TextStyle> kGithubLight = {
    SpanType.keyword: TextStyle(fontWeight: FontWeight.bold, color: const Color(0xFF009999)),
    SpanType.clazz: TextStyle(color: const Color(0xFF6F42C1)),
    SpanType.comment: TextStyle(color: Color(0xFF9D9D8D), fontStyle: FontStyle.italic),
    SpanType.string: TextStyle(color: Color(0xFFDD1045),),
    SpanType.number: TextStyle(color: Color(0xFF008081),),
    SpanType.punctuation: TextStyle(color: Color(0xFF333333,),),};static const Map<SpanType, TextStyle> kTolyDark = {
    SpanType.keyword: TextStyle(fontWeight: FontWeight.bold, color: const Color(0xFF80CBC4)),
    SpanType.clazz: TextStyle(color: const Color(0xFF7AA6DA)),
    SpanType.comment: TextStyle(color: Color(0xFF9E9E9E), fontStyle: FontStyle.italic),
    SpanType.string: TextStyle(color: Color(0xFFB9CA4A),),
    SpanType.number: TextStyle(color: Color(0xFFDF935F),),
    SpanType.punctuation: TextStyle(color: Color(0xFF333333,),),}; }Copy the code

That’s the core stuff, and if you have other highlighting needs, you can resolve them yourself. You can also tidy it up and provide a component for easy use. So that’s it. Thanks for watching


@Zhang Fengjietele 2021.01.22 not allowed to transfer my public number: the king of programming contact me – email :[email protected] – wechat :zdl1994328 ~ END ~