https://github.com/JuliaLang/julia
Tip revision: b7498550cbabafab972bfcae3c662107d1f974de authored by K Pamnany on 29 November 2023, 21:07:14 UTC
Improve `ReentrantLock`
Improve `ReentrantLock`
Tip revision: b749855
terminfo.jl
# This file is a part of Julia. License is MIT: https://julialang.org/license
include("terminfo_data.jl")
"""
struct TermInfoRaw
A structured representation of a terminfo file, without any knowledge of
particular capabilities, solely based on `term(5)`.
!!! warning
This is not part of the public API, and thus subject to change without notice.
# Fields
- `names::Vector{String}`: The names this terminal is known by.
- `flags::BitVector`: A list of 0–$(length(TERM_FLAGS)) flag values.
- `numbers::Union{Vector{UInt16}, Vector{UInt32}}`: A list of 0–$(length(TERM_NUMBERS))
number values. A value of `typemax(eltype(numbers))` is used to skip over
unspecified capabilities while ensuring value indices are correct.
- `strings::Vector{Union{String, Nothing}}`: A list of 0–$(length(TERM_STRINGS))
string values. A value of `nothing` is used to skip over unspecified
capabilities while ensuring value indices are correct.
- `extended::Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}}`: Should an
extended info section exist, this gives the entire extended info as a
dictionary. Otherwise `nothing`.
See also: `TermInfo` and `TermCapability`.
"""
struct TermInfoRaw
names::Vector{String}
flags::BitVector
numbers::Union{Vector{UInt16}, Vector{UInt32}}
strings::Vector{Union{String, Nothing}}
extended::Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}}
end
"""
struct TermInfo
A parsed terminfo paired with capability information.
!!! warning
This is not part of the public API, and thus subject to change without notice.
# Fields
- `names::Vector{String}`: The names this terminal is known by.
- `flags::Int`: The number of flags specified.
- `numbers::BitVector`: A mask indicating which of `TERM_NUMBERS` have been
specified.
- `strings::BitVector`: A mask indicating which of `TERM_STRINGS` have been
specified.
- `extensions::Vector{Symbol}`: A list of extended capability variable names.
- `capabilities::Dict{Symbol, Union{Bool, Int, String}}`: The capability values
themselves.
See also: `TermInfoRaw` and `TermCapability`.
"""
struct TermInfo
names::Vector{String}
flags::Int
numbers::BitVector
strings::BitVector
extensions::Vector{Symbol}
capabilities::Dict{Symbol, Union{Bool, Int, String}}
end
TermInfo() = TermInfo([], 0, [], [], [], Dict())
function read(data::IO, ::Type{TermInfoRaw})
# Parse according to `term(5)`
# Header
magic = read(data, UInt16) |> ltoh
NumInt = if magic == 0o0432
UInt16
elseif magic == 0o01036
UInt32
else
throw(ArgumentError("Terminfo data did not start with the magic number 0o0432 or 0o01036"))
end
name_bytes = read(data, UInt16) |> ltoh
flag_bytes = read(data, UInt16) |> ltoh
numbers_count = read(data, UInt16) |> ltoh
string_count = read(data, UInt16) |> ltoh
table_bytes = read(data, UInt16) |> ltoh
# Terminal Names
term_names = split(String(read(data, name_bytes - 1)), '|') .|> String
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo data did not contain a null byte after the terminal names section"))
# Boolean Flags
flags = read(data, flag_bytes) .== 0x01
if position(data) % 2 != 0
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo did not contain a null byte after the flag section, expected to position the start of the numbers section on an even byte"))
end
# Numbers, Strings, Table
numbers = map(ltoh, reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt))))
string_indices = map(ltoh, reinterpret(UInt16, read(data, string_count * sizeof(UInt16))))
strings_table = read(data, table_bytes)
strings = map(string_indices) do idx
if idx ∉ (0xffff, 0xfffe)
len = findfirst(==(0x00), view(strings_table, 1+idx:length(strings_table)))
!isnothing(len) ||
throw(ArgumentError("Terminfo string table entry does not terminate with a null byte"))
String(strings_table[1+idx:idx+len-1])
end
end
TermInfoRaw(term_names, flags, numbers, strings,
if !eof(data) extendedterminfo(data, NumInt) end)
end
"""
extendedterminfo(data::IO; NumInt::Union{Type{UInt16}, Type{UInt32}})
Read an extended terminfo section from `data`, with `NumInt` as the numbers type.
This will accept any terminfo content that conforms with `term(5)`.
See also: `read(::IO, ::Type{TermInfoRaw})`
"""
function extendedterminfo(data::IO, NumInt::Union{Type{UInt16}, Type{UInt32}})
# Extended info
if position(data) % 2 != 0
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo did not contain a null byte before the extended section, expected to position the start on an even byte"))
end
# Extended header
flag_bytes = read(data, UInt16) |> ltoh
numbers_count = read(data, UInt16) |> ltoh
string_count = read(data, UInt16) |> ltoh
table_count = read(data, UInt16) |> ltoh
table_bytes = read(data, UInt16) |> ltoh
# Extended flags/numbers/strings
flags = read(data, flag_bytes) .== 0x01
if flag_bytes % 2 != 0
0x00 == read(data, UInt8) ||
throw(ArgumentError("Terminfo did not contain a null byte after the extended flag section, expected to position the start of the numbers section on an even byte"))
end
numbers = map(n -> Int(ltoh(n)), reinterpret(NumInt, read(data, numbers_count * sizeof(NumInt))))
table_indices = map(ltoh, reinterpret(UInt16, read(data, table_count * sizeof(UInt16))))
table_strings = [String(readuntil(data, 0x00)) for _ in 1:length(table_indices)]
info = Dict{Symbol, Union{Bool, Int, String}}()
strings = table_strings[1:string_count]
labels = table_strings[string_count+1:end]
for (label, val) in zip(labels, vcat(flags, numbers, strings))
info[Symbol(label)] = val
end
return info
end
"""
TermInfo(raw::TermInfoRaw)
Construct a `TermInfo` from `raw`, using known terminal capabilities (as of
NCurses 6.3, see `TERM_FLAGS`, `TERM_NUMBERS`, and `TERM_STRINGS`).
"""
function TermInfo(raw::TermInfoRaw)
capabilities = Dict{Symbol, Union{Bool, Int, String}}()
sizehint!(capabilities, 2 * (length(raw.flags) + length(raw.numbers) + length(raw.strings)))
for (flag, value) in zip(TERM_FLAGS, raw.flags)
capabilities[flag.short] = value
capabilities[flag.long] = value
end
for (num, value) in zip(TERM_NUMBERS, raw.numbers)
if value != typemax(eltype(raw.numbers))
capabilities[num.short] = Int(value)
capabilities[num.long] = Int(value)
end
end
for (str, value) in zip(TERM_STRINGS, raw.strings)
if !isnothing(value)
capabilities[str.short] = value
capabilities[str.long] = value
end
end
extensions = if !isnothing(raw.extended)
capabilities = merge(capabilities, raw.extended)
keys(raw.extended) |> collect
else
Symbol[]
end
TermInfo(raw.names, length(raw.flags),
map(n-> n != typemax(typeof(n)), raw.numbers),
map(!isnothing, raw.strings),
extensions, capabilities)
end
getindex(ti::TermInfo, key::Symbol) = ti.capabilities[key]
get(ti::TermInfo, key::Symbol, default::D) where D<:Union{Bool, Int, String} =
get(ti.capabilities, key, default)::D
get(ti::TermInfo, key::Symbol, default) = get(ti.capabilities, key, default)
keys(ti::TermInfo) = keys(ti.capabilities)
haskey(ti::TermInfo, key::Symbol) = haskey(ti.capabilities, key)
function show(io::IO, ::MIME"text/plain", ti::TermInfo)
print(io, "TermInfo(", ti.names, "; ", ti.flags, " flags, ",
sum(ti.numbers), " numbers, ", sum(ti.strings), " strings")
!isempty(ti.extensions) > 0 &&
print(io, ", ", length(ti.extensions), " extended capabilities")
print(io, ')')
end
"""
find_terminfo_file(term::String)
Locate the terminfo file for `term`, return `nothing` if none could be found.
The lookup policy is described in `terminfo(5)` "Fetching Compiled
Descriptions".
"""
function find_terminfo_file(term::String)
isempty(term) && return
chr, chrcode = string(first(term)), string(Int(first(term)), base=16)
terminfo_dirs = if haskey(ENV, "TERMINFO")
[ENV["TERMINFO"]]
elseif isdir(joinpath(homedir(), ".terminfo"))
[joinpath(homedir(), ".terminfo")]
elseif haskey(ENV, "TERMINFO_DIRS")
split(ENV["TERMINFO_DIRS"], ':')
elseif Sys.isunix()
["/usr/share/terminfo"]
else
String[]
end
for dir in terminfo_dirs
if isfile(joinpath(dir, chr, term))
return joinpath(dir, chr, term)
elseif isfile(joinpath(dir, chrcode, term))
return joinpath(dir, chrcode, term)
end
end
end
"""
load_terminfo(term::String)
Load the `TermInfo` for `term`, falling back on a blank `TermInfo`.
"""
function load_terminfo(term::String)
file = find_terminfo_file(term)
isnothing(file) && return TermInfo()
try
TermInfo(read(file, TermInfoRaw))
catch err
if err isa ArgumentError || err isa IOError
TermInfo()
else
rethrow()
end
end
end
"""
The terminfo of the current terminal.
"""
current_terminfo::TermInfo = TermInfo()
# Legacy/TTY methods and the `:color` parameter
if Sys.iswindows()
ttyhascolor(term_type = nothing) = true
else
function ttyhascolor(term_type = get(ENV, "TERM", ""))
startswith(term_type, "xterm") ||
haskey(current_terminfo, :setaf)
end
end
"""
ttyhastruecolor()
Return a boolean signifying whether the current terminal supports 24-bit colors.
This uses the `COLORTERM` environment variable if possible, returning true if it
is set to either `"truecolor"` or `"24bit"`.
As a fallback, first on unix systems the `colors` terminal capability is checked
— should more than 256 colors be reported, this is taken to signify 24-bit
support.
"""
function ttyhastruecolor()
get(ENV, "COLORTERM", "") ∈ ("truecolor", "24bit") ||
@static if Sys.isunix()
get(current_terminfo, :colors, 0) > 256
else
false
end
end
function get_have_color()
global have_color
have_color === nothing && (have_color = ttyhascolor())
return have_color::Bool
end
function get_have_truecolor()
global have_truecolor
have_truecolor === nothing && (have_truecolor = ttyhastruecolor())
return have_truecolor::Bool
end
in(key_value::Pair{Symbol,Bool}, ::TTY) = key_value.first === :color && key_value.second === get_have_color()
haskey(::TTY, key::Symbol) = key === :color
getindex(::TTY, key::Symbol) = key === :color ? get_have_color() : throw(KeyError(key))
get(::TTY, key::Symbol, default) = key === :color ? get_have_color() : default