11# This file is a part of Julia. License is MIT: https://julialang.org/license
22
3+ # Since this code is in the startup-path, we go to some effort to
4+ # be easier on the compiler, such as using `map` over broadcasting.
5+
36include (" terminfo_data.jl" )
47
58"""
@@ -15,7 +18,7 @@ particular capabilities, solely based on `term(5)`.
1518
1619- `names::Vector{String}`: The names this terminal is known by.
1720- `flags::BitVector`: A list of 0–$(length (TERM_FLAGS)) flag values.
18- - `numbers::Union{Vector{UInt16 }, Vector{UInt32 }}`: A list of 0–$(length (TERM_NUMBERS))
21+ - `numbers::Union{Vector{Int16 }, Vector{Int32 }}`: A list of 0–$(length (TERM_NUMBERS))
1922 number values. A value of `typemax(eltype(numbers))` is used to skip over
2023 unspecified capabilities while ensuring value indices are correct.
2124- `strings::Vector{Union{String, Nothing}}`: A list of 0–$(length (TERM_STRINGS))
@@ -30,9 +33,9 @@ See also: `TermInfo` and `TermCapability`.
3033struct TermInfoRaw
3134 names:: Vector{String}
3235 flags:: BitVector
33- numbers:: Union{ Vector{UInt16}, Vector{UInt32} }
36+ numbers:: Vector{Int }
3437 strings:: Vector{Union{String, Nothing}}
35- extended:: Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}}
38+ extended:: Union{Nothing, Dict{Symbol, Union{Bool, Int, String, Nothing }}}
3639end
3740
3841"""
@@ -59,94 +62,105 @@ See also: `TermInfoRaw` and `TermCapability`.
5962"""
6063struct TermInfo
6164 names:: Vector{String}
62- flags:: Int
63- numbers:: BitVector
64- strings:: BitVector
65- extensions:: Vector{Symbol}
66- capabilities:: Dict{Symbol, Union{Bool, Int, String}}
65+ flags:: Dict{Symbol, Bool}
66+ numbers:: Dict{Symbol, Int}
67+ strings:: Dict{Symbol, String}
68+ extensions:: Union{Nothing, Set{Symbol}}
6769end
6870
69- TermInfo () = TermInfo ([], 0 , [], [], [], Dict ())
71+ TermInfo () = TermInfo ([], Dict (), Dict (), Dict (), nothing )
7072
7173function read (data:: IO , :: Type{TermInfoRaw} )
7274 # Parse according to `term(5)`
7375 # Header
7476 magic = read (data, UInt16) |> ltoh
7577 NumInt = if magic == 0o0432
76- UInt16
78+ Int16
7779 elseif magic == 0o01036
78- UInt32
80+ Int32
7981 else
8082 throw (ArgumentError (" Terminfo data did not start with the magic number 0o0432 or 0o01036" ))
8183 end
82- name_bytes = read (data, UInt16) |> ltoh
83- flag_bytes = read (data, UInt16) |> ltoh
84- numbers_count = read (data, UInt16) |> ltoh
85- string_count = read (data, UInt16) |> ltoh
86- table_bytes = read (data, UInt16) |> ltoh
84+ name_bytes, flag_bytes, numbers_count, string_count, table_bytes =
85+ @ntuple 5 _-> read (data, Int16) |> ltoh
8786 # Terminal Names
88- term_names = split (String (read (data, name_bytes - 1 )), ' |' ) .| > String
87+ term_names = map (String, split (String (read (data, name_bytes - 1 )), ' |' ))
8988 0x00 == read (data, UInt8) ||
9089 throw (ArgumentError (" Terminfo data did not contain a null byte after the terminal names section" ))
9190 # Boolean Flags
92- flags = read (data, flag_bytes) .== 0x01
91+ flags = map ( == ( 0x01 ), read (data, flag_bytes))
9392 if position (data) % 2 != 0
9493 0x00 == read (data, UInt8) ||
9594 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" ))
9695 end
9796 # Numbers, Strings, Table
98- numbers = map (ltoh, reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))))
99- string_indices = map (ltoh, reinterpret (UInt16 , read (data, string_count * sizeof (UInt16 ))))
97+ numbers = map (Int ∘ ltoh, reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))))
98+ string_indices = map (ltoh, reinterpret (Int16 , read (data, string_count * sizeof (Int16 ))))
10099 strings_table = read (data, table_bytes)
101- strings = map (string_indices) do idx
102- if idx ∉ (0xffff , 0xfffe )
103- len = findfirst (== (0x00 ), view (strings_table, 1 + idx: length (strings_table)))
104- ! isnothing (len) ||
105- throw (ArgumentError (" Terminfo string table entry does not terminate with a null byte" ))
106- String (strings_table[1 + idx: idx+ len- 1 ])
107- end
108- end
100+ strings = _terminfo_read_strings (strings_table, string_indices)
109101 TermInfoRaw (term_names, flags, numbers, strings,
110102 if ! eof (data) extendedterminfo (data, NumInt) end )
111103end
112104
113105"""
114- extendedterminfo(data::IO; NumInt::Union{Type{UInt16 }, Type{UInt32 }})
106+ extendedterminfo(data::IO; NumInt::Union{Type{Int16 }, Type{Int32 }})
115107
116108Read an extended terminfo section from `data`, with `NumInt` as the numbers type.
117109
118110This will accept any terminfo content that conforms with `term(5)`.
119111
120112See also: `read(::IO, ::Type{TermInfoRaw})`
121113"""
122- function extendedterminfo (data:: IO , NumInt:: Union{Type{UInt16 }, Type{UInt32 }} )
114+ function extendedterminfo (data:: IO , NumInt:: Union{Type{Int16 }, Type{Int32 }} )
123115 # Extended info
124116 if position (data) % 2 != 0
125117 0x00 == read (data, UInt8) ||
126- throw (ArgumentError (" Terminfo did not contain a null byte before the extended section, expected to position the start on an even byte" ))
118+ throw (ArgumentError (" Terminfo did not contain a null byte before the extended section; expected to position the start on an even byte" ))
127119 end
128120 # Extended header
129- flag_bytes = read (data, UInt16) |> ltoh
130- numbers_count = read (data, UInt16) |> ltoh
131- string_count = read (data, UInt16) |> ltoh
132- table_count = read (data, UInt16) |> ltoh
133- table_bytes = read (data, UInt16) |> ltoh
121+ flag_bytes, numbers_count, string_count, table_count, table_bytes =
122+ @ntuple 5 _-> read (data, Int16) |> ltoh
134123 # Extended flags/numbers/strings
135- flags = read (data, flag_bytes) .== 0x01
124+ flags = map ( == ( 0x01 ), read (data, flag_bytes))
136125 if flag_bytes % 2 != 0
137126 0x00 == read (data, UInt8) ||
138- 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" ))
127+ 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" ))
128+ end
129+ numbers = map (Int ∘ ltoh, reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))))
130+ table_indices = map (ltoh, reinterpret (Int16, read (data, table_count * sizeof (Int16))))
131+ table_data = read (data, table_bytes)
132+ strings = _terminfo_read_strings (table_data, table_indices[1 : string_count])
133+ table_halfoffset = Int16 (get (table_indices, string_count, 0 ) +
134+ ncodeunits (something (get (strings, length (strings), " " ), " " )) + 1 )
135+ for index in string_count+ 1 : lastindex (tables_indices)
136+ tables_indices[index] += table_halfoffset
139137 end
140- numbers = map (n -> Int (ltoh (n)), reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))))
141- table_indices = map (ltoh, reinterpret (UInt16, read (data, table_count * sizeof (UInt16))))
142- table_strings = [String (readuntil (data, 0x00 )) for _ in 1 : length (table_indices)]
143- info = Dict {Symbol, Union{Bool, Int, String}} ()
144- strings = table_strings[1 : string_count]
145- labels = table_strings[string_count+ 1 : end ]
146- for (label, val) in zip (labels, vcat (flags, numbers, strings))
147- info[Symbol (label)] = val
138+ labels = map (Symbol, _terminfo_read_strings (table_data, table_indices[string_count+ 1 : end ]))
139+ Dict {Symbol, Union{Bool, Int, String, Nothing}} (
140+ zip (labels, Iterators. flatten (flags, numbers, strings)))
141+ end
142+
143+ """
144+ _terminfo_read_strings(table::Vector{UInt8}, indices::Vector{Int16})
145+
146+ From `table`, read a string starting at each position in `indices`. Each string
147+ must be null-terminated. Should an index be -1 or -2, `nothing` is given instead
148+ of a string.
149+ """
150+ function _terminfo_read_strings (table:: Vector{UInt8} , indices:: Vector{Int16} )
151+ strings = Vector {Union{Nothing, String}} (undef, length (indices))
152+ map! (strings, indices) do idx
153+ if idx >= 0
154+ len = findfirst (== (0x00 ), view (table, 1 + idx: length (table)))
155+ ! isnothing (len) ||
156+ throw (ArgumentError (" Terminfo table entry @$idx does not terminate with a null byte" ))
157+ String (table[1 + idx: idx+ len- 1 ])
158+ elseif idx ∈ (- 1 , - 2 )
159+ else
160+ throw (ArgumentError (" Terminfo table index is invalid: -2 ≰ $idx " ))
161+ end
148162 end
149- return info
163+ strings
150164end
151165
152166"""
@@ -158,45 +172,60 @@ NCurses 6.3, see `TERM_FLAGS`, `TERM_NUMBERS`, and `TERM_STRINGS`).
158172function TermInfo (raw:: TermInfoRaw )
159173 capabilities = Dict {Symbol, Union{Bool, Int, String}} ()
160174 sizehint! (capabilities, 2 * (length (raw. flags) + length (raw. numbers) + length (raw. strings)))
175+ flags = Dict {Symbol, Bool} ()
176+ numbers = Dict {Symbol, Int} ()
177+ strings = Dict {Symbol, String} ()
178+ extensions = nothing
161179 for (flag, value) in zip (TERM_FLAGS, raw. flags)
162- capabilities [flag. short] = value
163- capabilities [flag. long] = value
180+ flags [flag. short] = value
181+ flags [flag. long] = value
164182 end
165183 for (num, value) in zip (TERM_NUMBERS, raw. numbers)
166- if value != typemax (eltype (raw. numbers))
167- capabilities[num. short] = Int (value)
168- capabilities[num. long] = Int (value)
169- end
184+ numbers[num. short] = Int (value)
185+ numbers[num. long] = Int (value)
170186 end
171187 for (str, value) in zip (TERM_STRINGS, raw. strings)
172188 if ! isnothing (value)
173- capabilities [str. short] = value
174- capabilities [str. long] = value
189+ strings [str. short] = value
190+ strings [str. long] = value
175191 end
176192 end
177- extensions = if ! isnothing (raw. extended)
178- capabilities = merge (capabilities, raw. extended)
179- keys (raw. extended) |> collect
180- else
181- Symbol[]
193+ if ! isnothing (raw. extended)
194+ extensions = Set {Symbol} ()
195+ for (key, value) in raw. extended
196+ push! (extensions, key)
197+ if value isa Bool
198+ flags[key] = value
199+ elseif value isa Int
200+ numbers[key] = value
201+ elseif value isa String
202+ strings[key] = value
203+ end
204+ end
182205 end
183- TermInfo (raw. names, length (raw. flags),
184- map (n-> n != typemax (typeof (n)), raw. numbers),
185- map (! isnothing, raw. strings),
186- extensions, capabilities)
206+ TermInfo (raw. names, flags, numbers, strings, extensions)
207+ end
208+
209+ get (ti:: TermInfo , key:: Symbol , default:: Bool ) = get (ti. flags, key, default)
210+ get (ti:: TermInfo , key:: Symbol , default:: Int ) = get (ti. numbers, key, default)
211+ get (ti:: TermInfo , key:: Symbol , default:: String ) = get (ti. strings, key, default)
212+
213+ haskey (ti:: TermInfo , key:: Symbol ) =
214+ haskey (ti. flags, key) || haskey (ti. numbers, key) || haskey (ti. strings, key)
215+
216+ function getindex (ti:: TermInfo , key:: Symbol )
217+ haskey (ti. flags, key) && return ti. flags[key]
218+ haskey (ti. numbers, key) && return ti. numbers[key]
219+ haskey (ti. strings, key) && return ti. strings[key]
220+ throw (KeyError (key))
187221end
188222
189- getindex (ti:: TermInfo , key:: Symbol ) = ti. capabilities[key]
190- get (ti:: TermInfo , key:: Symbol , default:: D ) where D<: Union{Bool, Int, String} =
191- get (ti. capabilities, key, default):: D
192- get (ti:: TermInfo , key:: Symbol , default) = get (ti. capabilities, key, default)
193- keys (ti:: TermInfo ) = keys (ti. capabilities)
194- haskey (ti:: TermInfo , key:: Symbol ) = haskey (ti. capabilities, key)
223+ keys (ti:: TermInfo ) = keys (ti. flags) ∪ keys (ti. numbers) ∪ keys (ti. strings)
195224
196225function show (io:: IO , :: MIME"text/plain" , ti:: TermInfo )
197- print (io, " TermInfo(" , ti. names, " ; " , ti. flags, " flags, " ,
198- sum (ti. numbers), " numbers, " , sum (ti. strings), " strings" )
199- ! isempty (ti. extensions) > 0 &&
226+ print (io, " TermInfo(" , ti. names, " ; " , length ( ti. flags) , " flags, " ,
227+ length (ti. numbers), " numbers, " , length (ti. strings), " strings" )
228+ ! isnothing (ti. extensions) &&
200229 print (io, " , " , length (ti. extensions), " extended capabilities" )
201230 print (io, ' )' )
202231end
@@ -216,13 +245,15 @@ function find_terminfo_file(term::String)
216245 [ENV [" TERMINFO" ]]
217246 elseif isdir (joinpath (homedir (), " .terminfo" ))
218247 [joinpath (homedir (), " .terminfo" )]
219- elseif haskey (ENV , " TERMINFO_DIRS" )
220- split (ENV [" TERMINFO_DIRS" ], ' :' )
221- elseif Sys. isunix ()
222- [" /usr/share/terminfo" ]
223248 else
224249 String[]
225250 end
251+ haskey (ENV , " TERMINFO_DIRS" ) &&
252+ append! (terminfo_dirs,
253+ replace (split (ENV [" TERMINFO_DIRS" ], ' :' ),
254+ " " => " /usr/share/terminfo" ))
255+ Sys. isunix () &&
256+ push! (terminfo_dirs, " /etc/terminfo" , " /usr/share/terminfo" )
226257 for dir in terminfo_dirs
227258 if isfile (joinpath (dir, chr, term))
228259 return joinpath (dir, chr, term)
0 commit comments