Skip to content

Commit 122a882

Browse files
committed
Improve parse-tree based highlighting
1 parent 5331c52 commit 122a882

File tree

1 file changed

+147
-56
lines changed

1 file changed

+147
-56
lines changed

src/JuliaSyntaxHighlighting.jl

Lines changed: 147 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,46 +10,57 @@ const MAX_PAREN_HIGHLIGHT_DEPTH = 6
1010
const RAINBOW_DELIMITERS_ENABLED = Ref(true)
1111
const UNMATCHED_DELIMITERS_ENABLED = Ref(true)
1212

13-
const SINGLETON_IDENTIFIERS = ("nothing", "missing")
13+
const SINGLETON_IDENTIFIERS = (:nothing, :missing)
14+
15+
const BASE_TYPE_IDENTIFIERS =
16+
Set([n for n in names(Base, imported=true) if getglobal(Base, n) isa Type])
17+
Set([n for n in names(Core, imported=true) if getglobal(Core, n) isa Type])
18+
19+
const BUILTIN_FUNCTIONS =
20+
Set([n for n in names(Core) if getglobal(Base, n) isa Core.Builtin])
1421

1522
const HIGHLIGHT_FACES = [
1623
# Julia syntax highlighting faces
17-
:julia_identifier => Face(foreground=:bright_white),
18-
:julia_singleton_identifier => Face(inherit=:julia_symbol),
1924
:julia_macro => Face(foreground=:magenta),
2025
:julia_symbol => Face(foreground=:magenta),
26+
:julia_singleton_identifier => Face(inherit=:julia_symbol),
2127
:julia_type => Face(foreground=:yellow),
28+
:julia_typedec => Face(foreground=:bright_blue),
2229
:julia_comment => Face(foreground=:grey),
2330
:julia_string => Face(foreground=:green),
31+
:julia_regex => Face(inherit=:julia_string),
32+
:julia_backslash_literal => Face(foreground=:magenta, inherit=:julia_string),
2433
:julia_string_delim => Face(foreground=:bright_green),
2534
:julia_cmdstring => Face(inherit=:julia_string),
2635
:julia_char => Face(inherit=:julia_string),
2736
:julia_char_delim => Face(inherit=:julia_string_delim),
28-
:julia_number => Face(foreground=:bright_red),
29-
:julia_bool => Face(foreground=:bright_red),
37+
:julia_number => Face(foreground=:bright_magenta),
38+
:julia_bool => Face(inherit=:julia_number),
3039
:julia_funcall => Face(foreground=:cyan),
31-
:julia_operator => Face(foreground=:cyan),
32-
:julia_comparator => Face(foreground=:yellow),
33-
:julia_assignment => Face(foreground=:bright_blue),
40+
:julia_broadcast => Face(foreground=:bright_blue, weight=:bold),
41+
:julia_builtin => Face(foreground=:bright_blue),
42+
:julia_operator => Face(foreground=:blue),
43+
:julia_comparator => Face(inherit = :julia_operator),
44+
:julia_assignment => Face(foreground=:bright_red),
3445
:julia_keyword => Face(foreground=:red),
35-
:julia_error => Face(background=:red),
3646
:julia_parenthetical => Face(),
37-
:julia_unpaired_parenthetical => Face(inherit=:julia_error),
47+
:julia_unpaired_parenthetical => Face(inherit=[:julia_error, :julia_parenthetical]),
48+
:julia_error => Face(background=:red),
3849
# Rainbow delimitors (1-6, (), [], and {})
39-
:julia_rainbow_paren_1 => Face(foreground=:bright_green),
40-
:julia_rainbow_paren_2 => Face(foreground=:bright_blue),
41-
:julia_rainbow_paren_3 => Face(foreground=:bright_red),
50+
:julia_rainbow_paren_1 => Face(foreground=:bright_green, inherit=:julia_parenthetical),
51+
:julia_rainbow_paren_2 => Face(foreground=:bright_blue, inherit=:julia_parenthetical),
52+
:julia_rainbow_paren_3 => Face(foreground=:bright_red, inherit=:julia_parenthetical),
4253
:julia_rainbow_paren_4 => Face(inherit=:julia_rainbow_paren_1),
4354
:julia_rainbow_paren_5 => Face(inherit=:julia_rainbow_paren_2),
4455
:julia_rainbow_paren_6 => Face(inherit=:julia_rainbow_paren_3),
45-
:julia_rainbow_bracket_1 => Face(foreground=:blue),
46-
:julia_rainbow_bracket_2 => Face(foreground=:bright_magenta),
56+
:julia_rainbow_bracket_1 => Face(foreground=:blue, inherit=:julia_parenthetical),
57+
:julia_rainbow_bracket_2 => Face(foreground=:bright_magenta, inherit=:julia_parenthetical),
4758
:julia_rainbow_bracket_3 => Face(inherit=:julia_rainbow_bracket_1),
4859
:julia_rainbow_bracket_4 => Face(inherit=:julia_rainbow_bracket_2),
4960
:julia_rainbow_bracket_5 => Face(inherit=:julia_rainbow_bracket_1),
5061
:julia_rainbow_bracket_6 => Face(inherit=:julia_rainbow_bracket_2),
51-
:julia_rainbow_curly_1 => Face(foreground=:bright_yellow),
52-
:julia_rainbow_curly_2 => Face(foreground=:yellow),
62+
:julia_rainbow_curly_1 => Face(foreground=:bright_yellow, inherit=:julia_parenthetical),
63+
:julia_rainbow_curly_2 => Face(foreground=:yellow, inherit=:julia_parenthetical),
5364
:julia_rainbow_curly_3 => Face(inherit=:julia_rainbow_curly_1),
5465
:julia_rainbow_curly_4 => Face(inherit=:julia_rainbow_curly_2),
5566
:julia_rainbow_curly_5 => Face(inherit=:julia_rainbow_curly_1),
@@ -87,52 +98,75 @@ struct HighlightContext{S <: AbstractString}
8798
content::S
8899
offset::Int
89100
lnode::GreenNode
90-
llnode::GreenNode
91101
pdepths::ParenDepthCounter
92102
end
93103

94104
function _hl_annotations(content::AbstractString, ast::GreenNode)
95105
highlights = Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}()
96-
ctx = HighlightContext(content, 0, ast, ast, ParenDepthCounter())
106+
ctx = HighlightContext(content, 0, ast, ParenDepthCounter())
97107
_hl_annotations!(highlights, GreenLineage(ast, nothing), ctx)
98108
highlights
99109
end
100110

101111
function _hl_annotations!(highlights::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}},
102112
lineage::GreenLineage, ctx::HighlightContext)
103113
(; node, parent) = lineage
104-
(; content, offset, lnode, llnode, pdepths) = ctx
114+
(; content, offset, lnode, pdepths) = ctx
105115
region = firstindex(content)+offset:node.span+offset
106116
nkind = node.head.kind
107117
pnode = if !isnothing(parent) parent.node end
108118
pkind = if !isnothing(parent) kind(parent.node) end
119+
ppkind = if !isnothing(parent) && !isnothing(parent.parent)
120+
kind(parent.parent.node) end
121+
# We need to check for infix binary operators
122+
# of the form [lhs] [ws] [op] [ws]? [rhs]
123+
isbinaryinfix(node) = 3 <= length(node.args) <= 5 &&
124+
(JuliaSyntax.is_operator(kind(node.args[2])) ||
125+
(kind(node.args[2]) == K"Whitespace" &&
126+
JuliaSyntax.is_operator(kind(node.args[3]))))
127+
isplainoperator(node) =
128+
JuliaSyntax.is_operator(node) &&
129+
!JuliaSyntax.is_trivia(node) &&
130+
!JuliaSyntax.is_prec_assignment(node) &&
131+
!JuliaSyntax.is_word_operator(node) &&
132+
nkind != K"." && nkind != K"..." &&
133+
(JuliaSyntax.is_trivia(node) ||
134+
iszero(flags(node)) && !isbinaryinfix(node))
109135
face = if nkind == K"Identifier"
110-
if pkind == K"::" && JuliaSyntax.is_trivia(pnode)
136+
if pkind == K"curly"
111137
: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
128138
else
129-
:julia_identifier
139+
name = Symbol(view(content, region))
140+
if name in SINGLETON_IDENTIFIERS
141+
:julia_singleton_identifier
142+
elseif name == :NaN
143+
:julia_number
144+
elseif name in BASE_TYPE_IDENTIFIERS
145+
:julia_type
146+
end
130147
end
131-
elseif nkind == K"@"; :julia_macro
132-
elseif nkind == K"MacroName"; :julia_macro
148+
elseif nkind == K"macrocall" && length(node.args) >= 2 &&
149+
kind(node.args[1]) == K"@" && kind(node.args[2]) == K"MacroName"
150+
region = first(region):first(region)+node.args[2].span
151+
:julia_macro
133152
elseif nkind == K"StringMacroName"; :julia_macro
134153
elseif nkind == K"CmdMacroName"; :julia_macro
135-
elseif nkind == K"::"; :julia_type
154+
elseif nkind == K"::";
155+
if JuliaSyntax.is_trivia(node)
156+
:julia_typedec
157+
else
158+
literal_typedecl = findfirst(
159+
c ->kind(c) == K"::" && JuliaSyntax.is_trivia(c),
160+
node.args)
161+
if !isnothing(literal_typedecl)
162+
shift = sum(c ->Int(c.span), node.args[1:literal_typedecl])
163+
region = first(region)+shift:last(region)
164+
:julia_type
165+
end
166+
end
167+
elseif nkind == K"quote" && length(node.args) == 2 &&
168+
kind(node.args[1]) == K":" && kind(node.args[2]) == K"Identifier"
169+
:julia_symbol
136170
elseif nkind == K"Comment"; :julia_comment
137171
elseif nkind == K"String"; :julia_string
138172
elseif JuliaSyntax.is_string_delim(node); :julia_string_delim
@@ -146,22 +180,65 @@ function _hl_annotations!(highlights::Vector{Tuple{UnitRange{Int}, Pair{Symbol,
146180
elseif nkind == K"true" || nkind == K"false"; :julia_bool
147181
elseif JuliaSyntax.is_number(nkind); :julia_number
148182
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
183+
if nkind == K"="
184+
ifelse(ppkind == K"for", :julia_keyword, :julia_assignment)
185+
else # updating for <op>=
186+
push!(highlights, (firstindex(content)+offset:node.span+offset-1, :face => :julia_operator))
187+
push!(highlights, (node.span+offset:node.span+offset, :face => :julia_assignment))
188+
nothing
189+
end
152190
elseif nkind == K";" && pkind == K"parameters" && pnode == lnode
153191
: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
192+
elseif (JuliaSyntax.is_keyword(nkind) ||nkind == K"->" ) && JuliaSyntax.is_trivia(node)
193+
:julia_keyword
194+
elseif nkind == K"where"
195+
if JuliaSyntax.is_trivia(node)
196+
:julia_keyword
197+
else
198+
literal_where = findfirst(
199+
c ->kind(c) == K"where" && JuliaSyntax.is_trivia(c),
200+
node.args)
201+
if !isnothing(literal_where)
202+
shift = sum(c ->Int(c.span), node.args[1:literal_where])
203+
region = first(region)+shift:last(region)
204+
:julia_type
205+
end
206+
end
207+
elseif nkind == K"in"
208+
ifelse(ppkind == K"for", :julia_keyword, :julia_comparator)
209+
elseif nkind == K"isa"; :julia_builtin
210+
elseif nkind in (K"&&", K"||", K"<:", K"===") && JuliaSyntax.is_trivia(node)
211+
:julia_builtin
212+
elseif JuliaSyntax.is_prec_comparison(nkind) && JuliaSyntax.is_trivia(node);
213+
:julia_comparator
214+
elseif isplainoperator(node); :julia_operator
215+
elseif nkind == K"..." && JuliaSyntax.is_trivia(node); :julia_operator
216+
elseif nkind == K"." && JuliaSyntax.is_trivia(node) && kind(pnode) == K"dotcall";
217+
:julia_broadcast
218+
elseif nkind in (K"call", K"dotcall") && JuliaSyntax.is_prefix_call(node)
219+
argoffset, arg1 = 0, nothing
220+
for arg in node.args
221+
argoffset += arg.span
222+
if !JuliaSyntax.is_trivia(arg)
223+
arg1 = arg
224+
break
225+
end
226+
end
227+
if isnothing(arg1)
228+
elseif kind(arg1) == K"Identifier"
229+
region = first(region):first(region)+argoffset-1
230+
name = Symbol(view(content, region))
231+
ifelse(name in BUILTIN_FUNCTIONS, :julia_builtin, :julia_funcall)
232+
elseif kind(arg1) == K"." && length(arg1.args) == 3 &&
233+
kind(arg1.args[end]) == K"quote" &&
234+
length(arg1.args[end].args) == 1 &&
235+
kind(arg1.args[end].args[1]) == K"Identifier"
236+
region = first(region)+argoffset-arg1.args[end].args[1].span:first(region)+argoffset-1
237+
name = Symbol(view(content, region))
238+
ifelse(name in BUILTIN_FUNCTIONS, :julia_builtin, :julia_funcall)
239+
end
160240
elseif JuliaSyntax.is_error(nkind); :julia_error
161241
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
165242
depthref = getfield(pdepths, ptype)[]
166243
pdepth = if depthchange > 0
167244
getfield(pdepths, ptype)[] += depthchange
@@ -181,12 +258,26 @@ function _hl_annotations!(highlights::Vector{Tuple{UnitRange{Int}, Pair{Symbol,
181258
end
182259
!isnothing(face) &&
183260
push!(highlights, (region, :face => face))
261+
if nkind == K"Comment"
262+
for match in eachmatch(
263+
r"(?:^|[(\[{[:space:]-])`([^[:space:]](?:.*?[^[:space:]])?)`(?:$|[!,\-.:;?\[\][:space:]])",
264+
view(content, region))
265+
code = first(match.captures)
266+
push!(highlights, (firstindex(content)+offset+code.offset:firstindex(content)+offset+code.offset+code.ncodeunits-1,
267+
:face => :code))
268+
end
269+
elseif nkind == K"String"
270+
for match in eachmatch(r"\\.", view(content, region))
271+
push!(highlights, (firstindex(content)+offset+match.offset-1:firstindex(content)+offset+match.offset+ncodeunits(match.match)-2,
272+
:face => :julia_backslash_literal))
273+
end
274+
end
184275
isempty(node.args) && return
185-
llnode, lnode = node, node
276+
lnode = node
186277
for child in node.args
187-
cctx = HighlightContext(content, offset, lnode, llnode, pdepths)
278+
cctx = HighlightContext(content, offset, lnode, pdepths)
188279
_hl_annotations!(highlights, GreenLineage(child, lineage), cctx)
189-
llnode, lnode = lnode, child
280+
lnode = child
190281
offset += child.span
191282
end
192283
end

0 commit comments

Comments
 (0)