|
| 1 | +module JuliaSyntaxHighlighting |
| 2 | + |
| 3 | +import Base: JuliaSyntax, AnnotatedString, annotate! |
| 4 | +import Base.JuliaSyntax: var"@K_str", Kind, Tokenize, tokenize |
| 5 | +import .Tokenize: kind, untokenize |
| 6 | +using StyledStrings: Face, addface! |
| 7 | + |
| 8 | +public highlight, highlight! |
| 9 | + |
| 10 | +const MAX_PAREN_HIGHLIGHT_DEPTH = 6 |
| 11 | +const RAINBOW_DELIMITERS_ENABLED = Ref(true) |
| 12 | +const UNMATCHED_DELIMITERS_ENABLED = Ref(true) |
| 13 | + |
| 14 | +const SINGLETON_IDENTIFIERS = ("nothing", "missing") |
| 15 | + |
| 16 | +const HIGHLIGHT_FACES = [ |
| 17 | + # Julia syntax highlighting faces |
| 18 | + :julia_identifier => Face(foreground=:bright_white), |
| 19 | + :julia_singleton_identifier => Face(inherit=:julia_symbol), |
| 20 | + :julia_macro => Face(foreground=:magenta), |
| 21 | + :julia_symbol => Face(foreground=:magenta), |
| 22 | + :julia_type => Face(foreground=:yellow), |
| 23 | + :julia_comment => Face(foreground=:grey), |
| 24 | + :julia_string => Face(foreground=:green), |
| 25 | + :julia_string_delim => Face(foreground=:bright_green), |
| 26 | + :julia_cmdstring => Face(inherit=:julia_string), |
| 27 | + :julia_char => Face(inherit=:julia_string), |
| 28 | + :julia_char_delim => Face(inherit=:julia_string_delim), |
| 29 | + :julia_number => Face(foreground=:bright_red), |
| 30 | + :julia_bool => Face(foreground=:bright_red), |
| 31 | + :julia_funcall => Face(foreground=:cyan), |
| 32 | + :julia_operator => Face(foreground=:cyan), |
| 33 | + :julia_comparator => Face(foreground=:yellow), |
| 34 | + :julia_assignment => Face(foreground=:bright_blue), |
| 35 | + :julia_keyword => Face(foreground=:red), |
| 36 | + :julia_error => Face(background=:red), |
| 37 | + :julia_parenthetical => Face(), |
| 38 | + :julia_unpaired_parenthetical => Face(inherit=:julia_error), |
| 39 | + # Rainbow delimitors (1-6, (), [], and {}) |
| 40 | + :julia_rainbow_paren_1 => Face(foreground=:bright_green), |
| 41 | + :julia_rainbow_paren_2 => Face(foreground=:bright_blue), |
| 42 | + :julia_rainbow_paren_3 => Face(foreground=:bright_red), |
| 43 | + :julia_rainbow_paren_4 => Face(inherit=:julia_rainbow_paren_1), |
| 44 | + :julia_rainbow_paren_5 => Face(inherit=:julia_rainbow_paren_2), |
| 45 | + :julia_rainbow_paren_6 => Face(inherit=:julia_rainbow_paren_3), |
| 46 | + :julia_rainbow_bracket_1 => Face(foreground=:blue), |
| 47 | + :julia_rainbow_bracket_2 => Face(foreground=:bright_magenta), |
| 48 | + :julia_rainbow_bracket_3 => Face(inherit=:julia_rainbow_bracket_1), |
| 49 | + :julia_rainbow_bracket_4 => Face(inherit=:julia_rainbow_bracket_2), |
| 50 | + :julia_rainbow_bracket_5 => Face(inherit=:julia_rainbow_bracket_1), |
| 51 | + :julia_rainbow_bracket_6 => Face(inherit=:julia_rainbow_bracket_2), |
| 52 | + :julia_rainbow_curly_1 => Face(foreground=:bright_yellow), |
| 53 | + :julia_rainbow_curly_2 => Face(foreground=:yellow), |
| 54 | + :julia_rainbow_curly_3 => Face(inherit=:julia_rainbow_curly_1), |
| 55 | + :julia_rainbow_curly_4 => Face(inherit=:julia_rainbow_curly_2), |
| 56 | + :julia_rainbow_curly_5 => Face(inherit=:julia_rainbow_curly_1), |
| 57 | + :julia_rainbow_curly_6 => Face(inherit=:julia_rainbow_curly_2), |
| 58 | +] |
| 59 | + |
| 60 | +__init__() = foreach(addface!, HIGHLIGHT_FACES) |
| 61 | + |
| 62 | +function _hl_annotations(content::AbstractString, tokens) |
| 63 | + highlighted = Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}() |
| 64 | + lastk, last2k = K"None", K"None" |
| 65 | + lastf, last2f = :none, :none |
| 66 | + function paren_type(k) |
| 67 | + if k == K"("; 1, :paren |
| 68 | + elseif k == K")"; -1, :paren |
| 69 | + elseif k == K"["; 1, :bracket |
| 70 | + elseif k == K"]"; -1, :bracket |
| 71 | + elseif k == K"{"; 1, :curly |
| 72 | + elseif k == K"}"; -1, :curly |
| 73 | + else 0, :none |
| 74 | + end |
| 75 | + end |
| 76 | + depthcounters = (paren = Ref(0), bracket = Ref(0), curly = Ref(0)) |
| 77 | + for (; head::JuliaSyntax.SyntaxHead, range::UnitRange{UInt32}) in tokens |
| 78 | + range = first(range):thisind(content, last(range)) |
| 79 | + kind = head.kind |
| 80 | + face = if kind == K"Identifier" |
| 81 | + if lastk == K":" && !JuliaSyntax.is_number(last2k) && |
| 82 | + last2k ∉ (K"Identifier", K")", K"]", K"end", K"'") |
| 83 | + highlighted[end] = (highlighted[end][1], :face => :julia_symbol) |
| 84 | + :julia_symbol |
| 85 | + elseif lastk == K"::" |
| 86 | + :julia_type |
| 87 | + elseif lastk ∈ (K".", K"{") && last2f == :julia_type |
| 88 | + :julia_type |
| 89 | + elseif view(content, range) in SINGLETON_IDENTIFIERS |
| 90 | + :julia_singleton_identifier |
| 91 | + elseif view(content, range) == "NaN" |
| 92 | + :julia_number |
| 93 | + else |
| 94 | + :julia_identifier |
| 95 | + end |
| 96 | + elseif kind == K"@"; :julia_macro |
| 97 | + elseif kind == K"MacroName"; :julia_macro |
| 98 | + elseif kind == K"StringMacroName"; :julia_macro |
| 99 | + elseif kind == K"CmdMacroName"; :julia_macro |
| 100 | + elseif kind == K"::"; :julia_type |
| 101 | + elseif kind == K"Comment"; :julia_comment |
| 102 | + elseif kind == K"String"; :julia_string |
| 103 | + elseif JuliaSyntax.is_string_delim(kind); :julia_string_delim |
| 104 | + elseif kind == K"CmdString"; :julia_cmdstring |
| 105 | + elseif kind == K"`" || kind == K"```"; :julia_cmdstring |
| 106 | + elseif kind == K"Char" |
| 107 | + lastk == K"'" && |
| 108 | + (highlighted[end] = (highlighted[end][1], :face => :julia_char_delim)) |
| 109 | + :julia_char |
| 110 | + elseif kind == K"'" && lastk == K"Char"; :julia_char_delim |
| 111 | + elseif kind == K"true" || kind == K"false"; :julia_bool |
| 112 | + elseif JuliaSyntax.is_number(kind); :julia_number |
| 113 | + elseif JuliaSyntax.is_prec_assignment(kind); :julia_assignment |
| 114 | + elseif JuliaSyntax.is_prec_comparison(kind); :julia_comparator |
| 115 | + elseif JuliaSyntax.is_operator(kind); :julia_operator |
| 116 | + elseif JuliaSyntax.is_keyword(kind); :julia_keyword |
| 117 | + elseif JuliaSyntax.is_error(kind); :julia_error |
| 118 | + elseif ((depthchange, ptype) = paren_type(kind)) |> last != :none |
| 119 | + if kind == K"(" && lastk == K"Identifier" |
| 120 | + highlighted[end] = (highlighted[end][1], :face => :julia_funcall) |
| 121 | + end |
| 122 | + depthref = getfield(depthcounters, ptype)[] |
| 123 | + pdepth = if depthchange > 0 |
| 124 | + getfield(depthcounters, ptype)[] += depthchange |
| 125 | + else |
| 126 | + depth0 = getfield(depthcounters, ptype)[] |
| 127 | + getfield(depthcounters, ptype)[] += depthchange |
| 128 | + depth0 |
| 129 | + end |
| 130 | + if pdepth <= 0 && UNMATCHED_DELIMITERS_ENABLED[] |
| 131 | + :julia_unpaired_parenthetical |
| 132 | + elseif !RAINBOW_DELIMITERS_ENABLED[] |
| 133 | + :julia_parenthetical |
| 134 | + else |
| 135 | + displaydepth = mod1(pdepth, MAX_PAREN_HIGHLIGHT_DEPTH) |
| 136 | + Symbol("julia_rainbow_$(ptype)_$(displaydepth)") |
| 137 | + end |
| 138 | + end |
| 139 | + isnothing(face) || push!(highlighted, (range, :face => face)) |
| 140 | + last2k, lastk = lastk, kind |
| 141 | + last2f, lastf = lastf, face |
| 142 | + end |
| 143 | + highlighted |
| 144 | +end |
| 145 | + |
| 146 | +""" |
| 147 | + highlight(content::Union{AbstractString, IOBuffer, IOContext{IOBuffer}}) |
| 148 | +
|
| 149 | +Apply syntax highlighting to `content` using `JuliaSyntax`. |
| 150 | +
|
| 151 | +Returns an `AnnotatedString{String}`. |
| 152 | +""" |
| 153 | +highlight(str::AbstractString) = |
| 154 | + AnnotatedString(str, _hl_annotations(str, tokenize(str))) |
| 155 | + |
| 156 | +function highlight(buf::IOBuffer) |
| 157 | + pos = position(buf) |
| 158 | + eof(buf) && seekstart(buf) |
| 159 | + str = read(buf, String) |
| 160 | + seek(buf, pos) |
| 161 | + highlight(str) |
| 162 | +end |
| 163 | + |
| 164 | +highlight(buf::IOContext{IOBuffer}) = highlight(buf.io) |
| 165 | + |
| 166 | +""" |
| 167 | + highlight!(content::Union{AnnotatedString, SubString{AnnotatedString}}) |
| 168 | +
|
| 169 | +Modify `content` by applying syntax highlighting using `JuliaSyntax`. |
| 170 | +""" |
| 171 | +function highlight!(str::AnnotatedString) |
| 172 | + for (range, annot) in _hl_annotations(str.string, tokenize(str.string)) |
| 173 | + annotate!(str, range, annot) |
| 174 | + end |
| 175 | + str |
| 176 | +end |
| 177 | + |
| 178 | +function highlight!(str::SubString{AnnotatedString{S}}) where {S} |
| 179 | + plainstr = SubString{S}(str.string.string, str.offset, str.ncodeunits, Val(:noshift)) |
| 180 | + for (range, annot) in _hl_annotations(plainstr, tokenize(plainstr)) |
| 181 | + annotate!(str, range, annot) |
| 182 | + end |
| 183 | + str |
| 184 | +end |
| 185 | + |
| 186 | +end |
0 commit comments