package syntax // The inline half of the Markdown scanner: what can appear inside a line of // prose, once the line's own shape has been decided by markdown.go. // scanMarkdownInline colours what can appear inside a line of prose. func scanMarkdownInline(s *LineScanner) { for !s.AtEnd() { switch { case s.Peek(0) == '`': takeCodeSpan(s) case s.Peek(0) == '!' && s.Peek(1) == '[': takeLink(s, 1) case s.Peek(0) == '[': takeLink(s, 0) case isEmphasisMarker(s.Peek(0)): takeEmphasis(s) default: s.pos++ } } } // isEmphasisMarker reports whether a rune can begin bold or italic text. func isEmphasisMarker(r rune) bool { return r == '*' || r == '_' } // takeCodeSpan colours `code`, or the lone backtick when nothing closes it. func takeCodeSpan(s *LineScanner) { start := s.pos for at := s.pos + 1; at < len(s.line); at++ { if s.line[at] == '`' { s.pos = at + 1 s.Emit(start, s.pos, ClassString) return } } s.pos++ } // takeEmphasis colours *italic*, **bold** and their underscore spellings. // // The run of markers at the start has to be matched by the same run at the // end, so that **bold** is one span rather than an empty italic followed by // stray text. func takeEmphasis(s *LineScanner) { marker := s.Peek(0) run := 0 for s.Peek(run) == marker { run++ } if end := findRun(s.line, s.pos+run, marker, run); end > 0 { start := s.pos s.pos = end + run s.Emit(start, s.pos, ClassEmphasis) return } s.pos += run } // findRun returns where a run of exactly n markers starts at or after from, or // -1 when the line holds none. func findRun(line []rune, from int, marker rune, n int) int { for at := from; at+n <= len(line); at++ { if line[at] != marker { continue } count := 0 for at+count < len(line) && line[at+count] == marker { count++ } if count == n { return at } at += count - 1 } return -1 } // takeLink colours [text](target) and its image form ![alt](src). // // Both halves are one span: a link is one thing to the eye, and splitting the // text from the target would put two colours on something read as a unit. func takeLink(s *LineScanner, offset int) { closeBracket := indexFrom(s.line, s.pos+offset+1, ']') if closeBracket < 0 || closeBracket+1 >= len(s.line) || s.line[closeBracket+1] != '(' { s.pos += offset + 1 return } closeParen := indexFrom(s.line, closeBracket+2, ')') if closeParen < 0 { s.pos += offset + 1 return } start := s.pos s.pos = closeParen + 1 s.Emit(start, s.pos, ClassLink) } // indexFrom returns where a rune first appears at or after an offset, or -1. func indexFrom(line []rune, from int, want rune) int { for at := max(from, 0); at < len(line); at++ { if line[at] == want { return at } } return -1 }