Skip to content

Commit 1cfbbd0

Browse files
committed
Make compatible with Julia 1.0
1 parent 57619c2 commit 1cfbbd0

17 files changed

+1793
-97
lines changed

.github/workflows/CI.yml

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ on:
33
push:
44
branches:
55
- main
6+
- julia1-compat
67
tags: ['*']
78
pull_request:
89
concurrency:
@@ -18,9 +19,17 @@ jobs:
1819
fail-fast: false
1920
matrix:
2021
version:
21-
# - '1.0'
22-
# - '1.6'
23-
# - '1'
22+
- '1.0'
23+
- '1.1'
24+
- '1.2'
25+
- '1.3'
26+
- '1.4'
27+
- '1.5'
28+
- '1.6'
29+
- '1.7'
30+
- '1.8'
31+
- '1.9'
32+
- '1.10'
2433
- 'nightly'
2534
os:
2635
- ubuntu-latest

Project.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
name = "StyledStrings"
22
uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b"
33
authors = ["TEC <[email protected]>"]
4-
version = "1.11.0"
4+
version = "1.0.0"
5+
6+
[deps]
7+
TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
58

69
[compat]
7-
julia = "1.11"
10+
TOML = "1"
11+
julia = "1"
812

913
[extras]
1014
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

src/StyledStrings.jl

+11-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22

33
module StyledStrings
44

5-
import Base: AnnotatedString, AnnotatedChar, annotations, annotate!,
6-
annotatedstring, convert, merge, show, print, write,
7-
ScopedValue, with, @with
5+
import Base: convert, merge, show, print, write
86

97
export @styled_str
10-
public Face, addface!, SimpleColor
8+
9+
include("strings/strings.jl")
10+
include("compat.jl")
11+
include("terminfo.jl")
12+
13+
import .AnnotatedStrings: AnnotatedString, AnnotatedChar, annotations,
14+
annotate!, annotatedstring, annotatedstring_optimize!
1115

1216
include("faces.jl")
1317
include("regioniterator.jl")
@@ -16,12 +20,14 @@ include("stylemacro.jl")
1620
include("legacy.jl")
1721

1822
function __init__()
23+
term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
24+
global current_terminfo = load_terminfo(term_env)
1925
userfaces = joinpath(first(DEPOT_PATH), "config", "faces.toml")
2026
isfile(userfaces) && loaduserfaces!(userfaces)
2127
Legacy.load_env_colors!()
2228
end
2329

24-
if Base.generating_output()
30+
if generating_output()
2531
include("precompile.jl")
2632
end
2733

src/compat.jl

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
@static if VERSION < v"1.1"
4+
isnothing(x) = x === nothing
5+
end
6+
7+
@static if VERSION < v"1.2"
8+
ncodeunits(s::AbstractString) = Base.ncodeunits(s)
9+
ncodeunits(c::AbstractChar) = Base.ncodeunits(string(c))
10+
elseif AnnotatedStrings != @__MODULE__
11+
const ncodeunits = Base.ncodeunits
12+
end
13+
14+
@static if VERSION < v"1.3"
15+
function _str_sizehint(x)
16+
if x isa Float64
17+
return 20
18+
elseif x isa Float32
19+
return 12
20+
elseif x isa String || x isa SubString{String}
21+
return sizeof(x)
22+
elseif x isa Char
23+
return ncodeunits(x)
24+
else
25+
return 8
26+
end
27+
end
28+
else
29+
import Base._str_sizehint
30+
end
31+
32+
@static if VERSION < v"1.3"
33+
function unescape_string(io::IO, s::AbstractString, keep = ())
34+
a = Iterators.Stateful(s)
35+
for c in a
36+
if !isempty(a) && c == '\\'
37+
c = popfirst!(a)
38+
if c in keep
39+
print(io, '\\', c)
40+
elseif c == 'x' || c == 'u' || c == 'U'
41+
n = k = 0
42+
m = c == 'x' ? 2 :
43+
c == 'u' ? 4 : 8
44+
while (k += 1) <= m && !isempty(a)
45+
nc = peek(a)
46+
n = '0' <= nc <= '9' ? n<<4 + (nc-'0') :
47+
'a' <= nc <= 'f' ? n<<4 + (nc-'a'+10) :
48+
'A' <= nc <= 'F' ? n<<4 + (nc-'A'+10) : break
49+
popfirst!(a)
50+
end
51+
if k == 1 || n > 0x10ffff
52+
u = m == 4 ? 'u' : 'U'
53+
throw(ArgumentError("invalid $(m == 2 ? "hex (\\x)" :
54+
"unicode (\\$u)") escape sequence"))
55+
end
56+
if m == 2 # \x escape sequence
57+
write(io, UInt8(n))
58+
else
59+
print(io, Char(n))
60+
end
61+
elseif '0' <= c <= '7'
62+
k = 1
63+
n = c-'0'
64+
while (k += 1) <= 3 && !isempty(a)
65+
c = peek(a)
66+
n = ('0' <= c <= '7') ? n<<3 + c-'0' : break
67+
popfirst!(a)
68+
end
69+
if n > 255
70+
throw(ArgumentError("octal escape sequence out of range"))
71+
end
72+
write(io, UInt8(n))
73+
else
74+
print(io, c == 'a' ? '\a' :
75+
c == 'b' ? '\b' :
76+
c == 't' ? '\t' :
77+
c == 'n' ? '\n' :
78+
c == 'v' ? '\v' :
79+
c == 'f' ? '\f' :
80+
c == 'r' ? '\r' :
81+
c == 'e' ? '\e' :
82+
(c == '\\' || c == '"') ? c :
83+
throw(ArgumentError("invalid escape sequence \\$c")))
84+
end
85+
else
86+
print(io, c)
87+
end
88+
end
89+
end
90+
unescape_string(s::AbstractString, keep = ()) =
91+
sprint(unescape_string, s, keep; sizehint=lastindex(s))
92+
end
93+
94+
@static if VERSION < v"1.4"
95+
function takewhile(f::Function, itr)
96+
taken = Vector{eltype(itr)}()
97+
next = iterate(itr)
98+
while !isnothing(next) && ((item, state) = next) |> first |> f
99+
push!(taken, item)
100+
next = iterate(itr, state)
101+
end
102+
taken
103+
end
104+
else
105+
const takewhile = Iterators.takewhile
106+
end
107+
108+
@static if VERSION < v"1.5"
109+
function peek(s::Iterators.Stateful, sentinel=nothing)
110+
ns = s.nextvalstate
111+
return ns !== nothing ? ns[1] : sentinel
112+
end
113+
end
114+
115+
@static if VERSION < v"1.6"
116+
function parseatom(text::AbstractString, pos::Integer)
117+
Meta.parse(text, pos, greedy = false)
118+
end
119+
else
120+
const parseatom = Meta.parseatom
121+
end
122+
123+
@static if VERSION < v"1.11"
124+
function generating_output(incremental::Union{Bool,Nothing}=nothing)
125+
ccall(:jl_generating_output, Cint, ()) == 0 && return false
126+
if incremental !== nothing
127+
Base.JLOptions().incremental == incremental || return false
128+
end
129+
return true
130+
end
131+
else
132+
const generating_output = Base.generating_output
133+
end

src/faces.jl

+57-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# This file is a part of Julia. License is MIT: https://julialang.org/license
22

3+
using TOML
4+
35
const RGBTuple = NamedTuple{(:r, :g, :b), NTuple{3, UInt8}}
46

57
"""
@@ -27,7 +29,7 @@ end
2729
SimpleColor(r::Integer, g::Integer, b::Integer) = SimpleColor((; r=UInt8(r), g=UInt8(g), b=UInt8(b)))
2830
SimpleColor(rgb::UInt32) = SimpleColor(reverse(reinterpret(UInt8, [rgb]))[2:end]...)
2931

30-
convert(::Type{SimpleColor}, (; r, g, b)::RGBTuple) = SimpleColor((; r, g, b))
32+
convert(::Type{SimpleColor}, rgb::RGBTuple) = SimpleColor(rgb)
3133
convert(::Type{SimpleColor}, namedcolor::Symbol) = SimpleColor(namedcolor)
3234
convert(::Type{SimpleColor}, rgb::UInt32) = SimpleColor(rgb)
3335

@@ -339,7 +341,7 @@ const FACES = let default = Dict{Symbol, Face}(
339341
:repl_prompt_pkg => Face(inherit=[:blue, :repl_prompt]),
340342
:repl_prompt_beep => Face(inherit=[:shadow, :repl_prompt]),
341343
)
342-
(; default, current=ScopedValue(copy(default)), lock=ReentrantLock())
344+
(; default=default, current=Ref(copy(default)), lock=ReentrantLock())
343345
end
344346

345347
## Adding and resetting faces ##
@@ -363,12 +365,14 @@ Face (sample)
363365
```
364366
"""
365367
function addface!((name, default)::Pair{Symbol, Face})
366-
@lock FACES.lock if !haskey(FACES.default, name)
367-
FACES.default[name] = default
368-
FACES.current[][name] = if haskey(FACES.current[], name)
369-
merge(deepcopy(default), FACES.current[][name])
370-
else
371-
deepcopy(default)
368+
lock(FACES.lock) do
369+
if !haskey(FACES.default, name)
370+
FACES.default[name] = default
371+
FACES.current[][name] = if haskey(FACES.current[], name)
372+
merge(deepcopy(default), FACES.current[][name])
373+
else
374+
deepcopy(default)
375+
end
372376
end
373377
end
374378
end
@@ -379,7 +383,7 @@ end
379383
Reset the current global face dictionary to the default value.
380384
"""
381385
function resetfaces!()
382-
@lock FACES.lock begin
386+
lock(FACES.lock) do
383387
current = FACES.current[]
384388
empty!(current)
385389
for (key, val) in FACES.default
@@ -399,13 +403,15 @@ In the unlikely event that the face `name` does not have a default value,
399403
it is deleted, a warning message is printed, and `nothing` returned.
400404
"""
401405
function resetfaces!(name::Symbol)
402-
@lock FACES.lock if !haskey(FACES.current[], name)
403-
elseif haskey(FACES.default, name)
404-
FACES.current[][name] = deepcopy(FACES.default[name])
405-
else # This shouldn't happen
406-
delete!(FACES.current[], name)
407-
@warn """The face $name was reset, but it had no default value, and so has been deleted instead!,
408-
This should not have happened, perhaps the face was added without using `addface!`?"""
406+
lock(FACES.lock) do
407+
if !haskey(FACES.current[], name)
408+
elseif haskey(FACES.default, name)
409+
FACES.current[][name] = deepcopy(FACES.default[name])
410+
else # This shouldn't happen
411+
delete!(FACES.current[], name)
412+
@warn """The face $name was reset, but it had no default value, and so has been deleted instead!,
413+
This should not have happened, perhaps the face was added without using `addface!`?"""
414+
end
409415
end
410416
end
411417

@@ -418,6 +424,9 @@ Execute `f` with `FACES``.current` temporarily modified by zero or more
418424
temporarily unset an face (if if has been set). When `withfaces` returns, the
419425
original `FACES``.current` has been restored.
420426
427+
!!! warning
428+
Changing faces is not thread-safe.
429+
421430
# Examples
422431
423432
```jldoctest; setup = :(import StyledStrings: Face, withfaces)
@@ -428,27 +437,45 @@ red and blue mixed make purple
428437
```
429438
"""
430439
function withfaces(f, keyvals::Pair{Symbol, <:Union{Face, Symbol, Nothing}}...)
431-
newfaces = copy(FACES.current[])
440+
old = Dict{Symbol, Union{Face, Nothing}}()
432441
for (name, face) in keyvals
442+
old[name] = get(FACES.current[], name, nothing)
433443
if face isa Face
434-
newfaces[name] = face
444+
FACES.current[][name] = face
435445
elseif face isa Symbol
436-
newfaces[name] =get(FACES.current[], face, Face())
437-
elseif haskey(newfaces, name)
438-
delete!(newfaces, name)
446+
FACES.current[][name] =
447+
something(get(old, face, nothing), get(FACES.current[], face, Face()))
448+
elseif haskey(FACES.current[], name)
449+
delete!(FACES.current[], name)
450+
end
451+
end
452+
try f()
453+
finally
454+
for (name, face) in old
455+
if isnothing(face)
456+
delete!(FACES.current[], name)
457+
else
458+
FACES.current[][name] = face
459+
end
439460
end
440461
end
441-
@with(FACES.current => newfaces, f())
442462
end
443463

444464
"""
445465
withfaces(f, altfaces::Dict{Symbol, Face})
446466
447467
Execute `f` with `FACES``.current` temporarily swapped out with `altfaces`
448468
When `withfaces` returns, the original `FACES``.current` has been restored.
469+
470+
!!! warning
471+
Changing faces is not thread-safe.
449472
"""
450473
function withfaces(f, altfaces::Dict{Symbol, Face})
451-
@with(FACES.current => altfaces, f())
474+
oldfaces, FACES.current[] = FACES.current[], altfaces
475+
try f()
476+
finally
477+
FACES.current[] = oldfaces
478+
end
452479
end
453480

454481
withfaces(f) = f()
@@ -578,10 +605,12 @@ Face (sample)
578605
```
579606
"""
580607
function loadface!((name, update)::Pair{Symbol, Face})
581-
@lock FACES.lock if haskey(FACES.current[], name)
582-
FACES.current[][name] = merge(FACES.current[][name], update)
583-
else
584-
FACES.current[][name] = update
608+
lock(FACES.lock) do
609+
if haskey(FACES.current[], name)
610+
FACES.current[][name] = merge(FACES.current[][name], update)
611+
else
612+
FACES.current[][name] = update
613+
end
585614
end
586615
end
587616

@@ -617,7 +646,7 @@ end
617646
618647
Load all faces declared in the Faces.toml file `tomlfile`.
619648
"""
620-
loaduserfaces!(tomlfile::String) = loaduserfaces!(Base.parsed_toml(tomlfile))
649+
loaduserfaces!(tomlfile::String) = loaduserfaces!(open(TOML.parse, tomlfile))
621650

622651
function convert(::Type{Face}, spec::Dict)
623652
Face(if haskey(spec, "font") && spec["font"] isa String

0 commit comments

Comments
 (0)