1
1
module JuliaSyntaxHighlighting
2
2
3
3
import Base: JuliaSyntax, AnnotatedString, annotate!
4
- import Base. JuliaSyntax: var"@K_str" , Kind, Tokenize, tokenize
5
- import . Tokenize: kind, untokenize
4
+ import Base. JuliaSyntax: var"@K_str" , Kind, GreenNode, parseall, kind, flags
6
5
using StyledStrings: Face, addface!
7
6
8
7
public highlight, highlight!
@@ -59,96 +58,148 @@ const HIGHLIGHT_FACES = [
59
58
60
59
__init__ () = foreach (addface!, HIGHLIGHT_FACES)
61
60
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
61
+ function paren_type (k:: Kind )
62
+ if k == K " (" ; 1 , :paren
63
+ elseif k == K " )" ; - 1 , :paren
64
+ elseif k == K " [" ; 1 , :bracket
65
+ elseif k == K " ]" ; - 1 , :bracket
66
+ elseif k == K " {" ; 1 , :curly
67
+ elseif k == K " }" ; - 1 , :curly
68
+ else 0 , :none
75
69
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
70
+ end
71
+
72
+ struct ParenDepthCounter
73
+ paren:: Ref{UInt}
74
+ bracket:: Ref{UInt}
75
+ curly:: Ref{UInt}
76
+ end
77
+
78
+ ParenDepthCounter () =
79
+ ParenDepthCounter (Ref (zero (UInt)), Ref (zero (UInt)), Ref (zero (UInt)))
80
+
81
+ struct GreenLineage
82
+ node:: GreenNode
83
+ parent:: Union{Nothing, GreenLineage}
84
+ end
85
+
86
+ struct HighlightContext{S <: AbstractString }
87
+ content:: S
88
+ offset:: Int
89
+ lnode:: GreenNode
90
+ llnode:: GreenNode
91
+ pdepths:: ParenDepthCounter
92
+ end
93
+
94
+ function _hl_annotations (content:: AbstractString , ast:: GreenNode )
95
+ highlights = Vector {Tuple{UnitRange{Int}, Pair{Symbol, Any}}} ()
96
+ ctx = HighlightContext (content, 0 , ast, ast, ParenDepthCounter ())
97
+ _hl_annotations! (highlights, GreenLineage (ast, nothing ), ctx)
98
+ highlights
99
+ end
100
+
101
+ function _hl_annotations! (highlights:: Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}} ,
102
+ lineage:: GreenLineage , ctx:: HighlightContext )
103
+ (; node, parent) = lineage
104
+ (; content, offset, lnode, llnode, pdepths) = ctx
105
+ region = firstindex (content)+ offset: node. span+ offset
106
+ nkind = node. head. kind
107
+ pnode = if ! isnothing (parent) parent. node end
108
+ pkind = if ! isnothing (parent) kind (parent. node) end
109
+ face = if nkind == K " Identifier"
110
+ if pkind == K " ::" && JuliaSyntax. is_trivia (pnode)
111
+ :julia_type
112
+ elseif pkind == K " curly" && kind (lnode) == K " curly" && ! isnothing (parent. parent) && kind (parent. parent. node) == K " call"
113
+ :julia_identifier
114
+ elseif pkind == K " curly"
115
+ :julia_type
116
+ elseif pkind == K " braces" && lnode != pnode
117
+ :julia_type
118
+ elseif kind (lnode) == K " ::" && JuliaSyntax. is_trivia (lnode)
119
+ :julia_type
120
+ elseif kind (lnode) == K " :" && ! JuliaSyntax. is_number (llnode) &&
121
+ kind (llnode) ∉ (K " Identifier" , K " )" , K " ]" , K " end" , K " '" )
122
+ highlights[end ] = (highlights[end ][1 ], :face => :julia_symbol )
123
+ :julia_symbol
124
+ elseif view (content, region) in SINGLETON_IDENTIFIERS
125
+ :julia_singleton_identifier
126
+ elseif view (content, region) == " NaN"
127
+ :julia_number
128
+ else
129
+ :julia_identifier
130
+ end
131
+ elseif nkind == K " @" ; :julia_macro
132
+ elseif nkind == K " MacroName" ; :julia_macro
133
+ elseif nkind == K " StringMacroName" ; :julia_macro
134
+ elseif nkind == K " CmdMacroName" ; :julia_macro
135
+ elseif nkind == K " ::" ; :julia_type
136
+ elseif nkind == K " Comment" ; :julia_comment
137
+ elseif nkind == K " String" ; :julia_string
138
+ elseif JuliaSyntax. is_string_delim (node); :julia_string_delim
139
+ elseif nkind == K " CmdString" ; :julia_cmdstring
140
+ elseif nkind == K " `" || nkind == K " ```" ; :julia_cmdstring
141
+ elseif nkind == K " Char"
142
+ kind (lnode) == K " '" && ! isempty (highlights) &&
143
+ (highlights[end ] = (highlights[end ][1 ], :face => :julia_char_delim ))
144
+ :julia_char
145
+ elseif nkind == K " '" && kind (lnode) == K " Char" ; :julia_char_delim
146
+ elseif nkind == K " true" || nkind == K " false" ; :julia_bool
147
+ elseif JuliaSyntax. is_number (nkind); :julia_number
148
+ elseif JuliaSyntax. is_prec_assignment (nkind) && JuliaSyntax. is_trivia (node);
149
+ :julia_assignment
150
+ elseif JuliaSyntax. is_word_operator (nkind) && JuliaSyntax. is_trivia (node);
151
+ :julia_assignment
152
+ elseif nkind == K " ;" && pkind == K " parameters" && pnode == lnode
153
+ :julia_assignment
154
+ elseif JuliaSyntax. is_prec_comparison (nkind); :julia_comparator
155
+ elseif JuliaSyntax. is_operator (nkind) && ! JuliaSyntax. is_prec_assignment (nkind) &&
156
+ ! JuliaSyntax. is_word_operator (nkind) && nkind != K " ." &&
157
+ (JuliaSyntax. is_trivia (node) || iszero (flags (node)));
158
+ :julia_operator
159
+ elseif JuliaSyntax. is_keyword (nkind) && JuliaSyntax. is_trivia (node); :julia_keyword
160
+ elseif JuliaSyntax. is_error (nkind); :julia_error
161
+ elseif ((depthchange, ptype) = paren_type (nkind)) |> last != :none
162
+ if nkind == K " (" && ! isempty (highlights) && kind (lnode) == K " Identifier" && last (last (highlights[end ])) == :julia_identifier
163
+ highlights[end ] = (highlights[end ][1 ], :face => :julia_funcall )
164
+ end
165
+ depthref = getfield (pdepths, ptype)[]
166
+ pdepth = if depthchange > 0
167
+ getfield (pdepths, ptype)[] += depthchange
168
+ else
169
+ depth0 = getfield (pdepths, ptype)[]
170
+ getfield (pdepths, ptype)[] += depthchange
171
+ depth0
138
172
end
139
- isnothing (face) || push! (highlighted, (range, :face => face))
140
- last2k, lastk = lastk, kind
141
- last2f, lastf = lastf, face
173
+ if pdepth <= 0 && UNMATCHED_DELIMITERS_ENABLED[]
174
+ :julia_unpaired_parenthetical
175
+ elseif ! RAINBOW_DELIMITERS_ENABLED[]
176
+ :julia_parenthetical
177
+ else
178
+ displaydepth = mod1 (pdepth, MAX_PAREN_HIGHLIGHT_DEPTH)
179
+ Symbol (" julia_rainbow_$(ptype) _$(displaydepth) " )
180
+ end
181
+ end
182
+ ! isnothing (face) &&
183
+ push! (highlights, (region, :face => face))
184
+ isempty (node. args) && return
185
+ llnode, lnode = node, node
186
+ for child in node. args
187
+ cctx = HighlightContext (content, offset, lnode, llnode, pdepths)
188
+ _hl_annotations! (highlights, GreenLineage (child, lineage), cctx)
189
+ llnode, lnode = lnode, child
190
+ offset += child. span
142
191
end
143
- highlighted
144
192
end
145
193
146
194
"""
147
- highlight(content::Union{AbstractString, IOBuffer, IOContext{IOBuffer}})
195
+ highlight(content::Union{AbstractString, IO},
196
+ ast::JuliaSyntax.GreenNode = <parsed content>) -> AnnotatedString{String}
148
197
149
198
Apply syntax highlighting to `content` using `JuliaSyntax`.
150
199
151
- Returns an `AnnotatedString{String}`.
200
+ By default, `JuliaSyntax.parseall` is used to generate to `ast` with the
201
+ `ignore_errors` keyword argument set to `true`. Alternatively, one may provide a
202
+ pre-generated `ast`.
152
203
153
204
# Examples
154
205
@@ -166,24 +217,29 @@ julia> JuliaSyntaxHighlighting.highlight("sum(1:8)") |> Base.annotations
166
217
(8:8, :face => :julia_rainbow_paren_1)
167
218
```
168
219
"""
220
+ function highlight end
221
+
169
222
highlight (str:: AbstractString ) =
170
- AnnotatedString (str, _hl_annotations (str, tokenize (str)))
171
-
172
- function highlight (buf:: IOBuffer )
173
- pos = position (buf)
174
- eof (buf) && seekstart (buf)
175
- str = read (buf, String)
176
- seek (buf, pos)
177
- highlight (str)
178
- end
223
+ highlight (str, parseall (GreenNode, str, ignore_errors= true ))
224
+
225
+ highlight (io:: IO ) = highlight (read (io, String))
179
226
180
- highlight (buf:: IOContext{IOBuffer} ) = highlight (buf. io)
227
+ highlight (io:: IO , ast:: GreenNode ) =
228
+ highlight (read (io, String), ast)
229
+
230
+ highlight (str:: AbstractString , ast:: GreenNode ) =
231
+ AnnotatedString (str, _hl_annotations (str, ast))
181
232
182
233
"""
183
- highlight!(content::Union{AnnotatedString, SubString{AnnotatedString}})
234
+ highlight!(content::Union{AnnotatedString, SubString{AnnotatedString}},
235
+ ast::JuliaSyntax.GreenNode = <parsed content>)
184
236
185
237
Modify `content` by applying syntax highlighting using `JuliaSyntax`.
186
238
239
+ By default, `JuliaSyntax.parseall` is used to generate to `ast` with the
240
+ `ignore_errors` keyword argument set to `true`. Alternatively, one may provide a
241
+ pre-generated `ast`.
242
+
187
243
# Examples
188
244
189
245
```jldoctest
@@ -204,15 +260,15 @@ julia> Base.annotations(str)
204
260
```
205
261
"""
206
262
function highlight! (str:: AnnotatedString )
207
- for (range, annot) in _hl_annotations (str. string, tokenize ( str. string))
263
+ for (range, annot) in _hl_annotations (str. string, parseall (GreenNode, str. string, ignore_errors = true ))
208
264
annotate! (str, range, annot)
209
265
end
210
266
str
211
267
end
212
268
213
269
function highlight! (str:: SubString{AnnotatedString{S}} ) where {S}
214
270
plainstr = SubString {S} (str. string. string, str. offset, str. ncodeunits, Val (:noshift ))
215
- for (range, annot) in _hl_annotations (plainstr, tokenize ( plainstr))
271
+ for (range, annot) in _hl_annotations (plainstr, parseall (GreenNode, plainstr, ignore_errors = true ))
216
272
annotate! (str, range, annot)
217
273
end
218
274
str
0 commit comments