Skip to content

Commit 48aa5cb

Browse files
committed
Initial commit
0 parents  commit 48aa5cb

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 The Julia Programming Language
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Project.toml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name = "JuliaSyntaxHighlighting"
2+
uuid = "dc6e5ff7-fb65-4e79-a425-ec3bc9c03011"
3+
authors = ["TEC <[email protected]>"]
4+
version = "1.11.0"
5+
6+
[deps]
7+
StyledStrings = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
8+
9+
[compat]
10+
julia = "1.11"
11+
12+
[extras]
13+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
14+
15+
[targets]
16+
test = ["Test"]

src/JuliaSyntaxHighlighting.jl

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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

Comments
 (0)