@@ -15,7 +15,7 @@ particular capabilities, solely based on `term(5)`.
1515
1616- `names::Vector{String}`: The names this terminal is known by.
1717- `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))
18+ - `numbers::Union{Vector{Int16 }, Vector{Int32 }}`: A list of 0–$(length (TERM_NUMBERS))
1919 number values. A value of `typemax(eltype(numbers))` is used to skip over
2020 unspecified capabilities while ensuring value indices are correct.
2121- `strings::Vector{Union{String, Nothing}}`: A list of 0–$(length (TERM_STRINGS))
@@ -30,9 +30,9 @@ See also: `TermInfo` and `TermCapability`.
3030struct TermInfoRaw
3131 names:: Vector{String}
3232 flags:: BitVector
33- numbers:: Union{ Vector{UInt16}, Vector{UInt32} }
33+ numbers:: Vector{Int }
3434 strings:: Vector{Union{String, Nothing}}
35- extended:: Union{Nothing, Dict{Symbol, Union{Bool, Int, String}}}
35+ extended:: Union{Nothing, Dict{Symbol, Union{Bool, Int, String, Nothing }}}
3636end
3737
3838"""
@@ -59,31 +59,27 @@ See also: `TermInfoRaw` and `TermCapability`.
5959"""
6060struct TermInfo
6161 names:: Vector{String}
62- flags:: Int
63- numbers:: BitVector
64- strings:: BitVector
65- extensions:: Vector{Symbol}
66- capabilities:: Dict{Symbol, Union{Bool, Int, String}}
62+ flags:: Dict{Symbol, Bool}
63+ numbers:: Dict{Symbol, Int}
64+ strings:: Dict{Symbol, String}
65+ extensions:: Union{Nothing, Set{Symbol}}
6766end
6867
69- TermInfo () = TermInfo ([], 0 , [], [], [], Dict ())
68+ TermInfo () = TermInfo ([], Dict (), Dict (), Dict (), nothing )
7069
7170function read (data:: IO , :: Type{TermInfoRaw} )
7271 # Parse according to `term(5)`
7372 # Header
7473 magic = read (data, UInt16) |> ltoh
7574 NumInt = if magic == 0o0432
76- UInt16
75+ Int16
7776 elseif magic == 0o01036
78- UInt32
77+ Int32
7978 else
8079 throw (ArgumentError (" Terminfo data did not start with the magic number 0o0432 or 0o01036" ))
8180 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
81+ name_bytes, flag_bytes, numbers_count, string_count, table_bytes =
82+ @ntuple 5 _-> read (data, Int16) |> ltoh
8783 # Terminal Names
8884 term_names = split (String (read (data, name_bytes - 1 )), ' |' ) .| > String
8985 0x00 == read (data, UInt8) ||
@@ -95,57 +91,73 @@ function read(data::IO, ::Type{TermInfoRaw})
9591 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" ))
9692 end
9793 # Numbers, Strings, Table
98- numbers = reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))) .| > ltoh
99- string_indices = reinterpret (UInt16 , read (data, string_count * sizeof (UInt16 ))) .| > ltoh
94+ numbers = reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))) .| > ltoh .| > Int
95+ string_indices = reinterpret (Int16 , read (data, string_count * sizeof (Int16 ))) .| > ltoh
10096 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
97+ strings = _terminfo_read_strings (strings_table, string_indices)
10998 TermInfoRaw (term_names, flags, numbers, strings,
11099 if ! eof (data) extendedterminfo (data; NumInt) end )
111100end
112101
113102"""
114- extendedterminfo(data::IO; NumInt::Union{Type{UInt16 }, Type{UInt32 }})
103+ extendedterminfo(data::IO; NumInt::Union{Type{Int16 }, Type{Int32 }})
115104
116105Read an extended terminfo section from `data`, with `NumInt` as the numbers type.
117106
118107This will accept any terminfo content that conforms with `term(5)`.
119108
120109See also: `read(::IO, ::Type{TermInfoRaw})`
121110"""
122- function extendedterminfo (data:: IO ; NumInt:: Union{Type{UInt16 }, Type{UInt32 }} )
111+ function extendedterminfo (data:: IO ; NumInt:: Union{Type{Int16 }, Type{Int32 }} )
123112 # Extended info
124113 if position (data) % 2 != 0
125114 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" ))
115+ throw (ArgumentError (" Terminfo did not contain a null byte before the extended section; expected to position the start on an even byte" ))
127116 end
128117 # 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
118+ flag_bytes, numbers_count, string_count, table_count, table_bytes =
119+ @ntuple 5 _-> read (data, Int16) |> ltoh
134120 # Extended flags/numbers/strings
135121 flags = read (data, flag_bytes) .== 0x01
136122 if flag_bytes % 2 != 0
137123 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" ))
124+ 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" ))
139125 end
140- numbers = reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))) .| > ltoh
141- table_indices = reinterpret (UInt16, read (data, table_count * sizeof (UInt16))) .| > ltoh
142- table_strings = [String (readuntil (data, 0x00 )) for _ in 1 : length (table_indices)]
143- strings = table_strings[1 : string_count]
144- labels = Symbol .(table_strings[string_count+ 1 : end ])
145- Dict {Symbol, Union{Bool, Int, String}} (
126+ numbers = reinterpret (NumInt, read (data, numbers_count * sizeof (NumInt))) .| > ltoh .| > Int
127+ table_indices = reinterpret (Int16, read (data, table_count * sizeof (Int16))) .| > ltoh
128+ table_data = read (data, table_bytes)
129+ strings = _terminfo_read_strings (table_data, table_indices[1 : string_count])
130+ table_halfoffset = Int16 (get (table_indices, string_count, 0 ) +
131+ ncodeunits (something (get (strings, length (strings), " " ), " " )) + 1 )
132+ labels = _terminfo_read_strings (table_data, table_halfoffset .+ table_indices[string_count+ 1 : end ]) |>
133+ Vector{String} .| > Symbol
134+ Dict {Symbol, Union{Bool, Int, String, Nothing}} (
146135 labels .=> vcat (flags, numbers, strings))
147136end
148137
138+ """
139+ _terminfo_read_strings(table::Vector{UInt8}, indices::Vector{Int16})
140+
141+ From `table`, read a string starting at each position in `indices`. Each string
142+ must be null-terminated. Should an index be -1 or -2, `nothing` is given instead
143+ of a string.
144+ """
145+ function _terminfo_read_strings (table:: Vector{UInt8} , indices:: Vector{Int16} )
146+ strings = Vector {Union{Nothing, String}} (undef, length (indices))
147+ map! (strings, indices) do idx
148+ if idx >= 0
149+ len = findfirst (== (0x00 ), view (table, 1 + idx: length (table)))
150+ ! isnothing (len) ||
151+ throw (ArgumentError (" Terminfo table entry @$idx does not terminate with a null byte" ))
152+ String (table[1 + idx: idx+ len- 1 ])
153+ elseif idx ∈ (- 1 , - 2 )
154+ else
155+ throw (ArgumentError (" Terminfo table index is invalid: -2 ≰ $idx " ))
156+ end
157+ end
158+ strings
159+ end
160+
149161"""
150162 TermInfo(raw::TermInfoRaw)
151163
@@ -155,45 +167,60 @@ NCurses 6.3, see `TERM_FLAGS`, `TERM_NUMBERS`, and `TERM_STRINGS`).
155167function TermInfo (raw:: TermInfoRaw )
156168 capabilities = Dict {Symbol, Union{Bool, Int, String}} ()
157169 sizehint! (capabilities, 2 * (length (raw. flags) + length (raw. numbers) + length (raw. strings)))
170+ flags = Dict {Symbol, Bool} ()
171+ numbers = Dict {Symbol, Int} ()
172+ strings = Dict {Symbol, String} ()
173+ extensions = nothing
158174 for (flag, value) in zip (TERM_FLAGS, raw. flags)
159- capabilities [flag. short] = value
160- capabilities [flag. long] = value
175+ flags [flag. short] = value
176+ flags [flag. long] = value
161177 end
162178 for (num, value) in zip (TERM_NUMBERS, raw. numbers)
163- if value != typemax (eltype (raw. numbers))
164- capabilities[num. short] = Int (value)
165- capabilities[num. long] = Int (value)
166- end
179+ numbers[num. short] = Int (value)
180+ numbers[num. long] = Int (value)
167181 end
168182 for (str, value) in zip (TERM_STRINGS, raw. strings)
169183 if ! isnothing (value)
170- capabilities [str. short] = value
171- capabilities [str. long] = value
184+ strings [str. short] = value
185+ strings [str. long] = value
172186 end
173187 end
174- extensions = if ! isnothing (raw. extended)
175- capabilities = merge (capabilities, raw. extended)
176- keys (raw. extended) |> collect
177- else
178- Symbol[]
188+ if ! isnothing (raw. extended)
189+ extensions = Set {Symbol} ()
190+ for (key, value) in raw. extended
191+ push! (extensions, key)
192+ if value isa Bool
193+ flags[key] = value
194+ elseif value isa Int
195+ numbers[key] = value
196+ elseif value isa String
197+ strings[key] = value
198+ end
199+ end
179200 end
180- TermInfo (raw. names, length (raw. flags),
181- raw. numbers .!= typemax (eltype (raw. numbers)),
182- map (! isnothing, raw. strings),
183- extensions, capabilities)
201+ TermInfo (raw. names, flags, numbers, strings, extensions)
202+ end
203+
204+ get (ti:: TermInfo , key:: Symbol , default:: Bool ) = get (ti. flags, key, default)
205+ get (ti:: TermInfo , key:: Symbol , default:: Int ) = get (ti. numbers, key, default)
206+ get (ti:: TermInfo , key:: Symbol , default:: String ) = get (ti. strings, key, default)
207+
208+ haskey (ti:: TermInfo , key:: Symbol ) =
209+ haskey (ti. flags, key) || haskey (ti. numbers, key) || haskey (ti. strings, key)
210+
211+ function getindex (ti:: TermInfo , key:: Symbol )
212+ haskey (ti. flags, key) && return ti. flags[key]
213+ haskey (ti. numbers, key) && return ti. numbers[key]
214+ haskey (ti. strings, key) && return ti. strings[key]
215+ throw (KeyError (key))
184216end
185217
186- getindex (ti:: TermInfo , key:: Symbol ) = ti. capabilities[key]
187- get (ti:: TermInfo , key:: Symbol , default:: D ) where D<: Union{Bool, Int, String} =
188- get (ti. capabilities, key, default):: D
189- get (ti:: TermInfo , key:: Symbol , default) = get (ti. capabilities, key, default)
190- keys (ti:: TermInfo ) = keys (ti. capabilities)
191- haskey (ti:: TermInfo , key:: Symbol ) = haskey (ti. capabilities, key)
218+ keys (ti:: TermInfo ) = keys (ti. flags) ∪ keys (ti. numbers) ∪ keys (ti. strings)
192219
193220function show (io:: IO , :: MIME"text/plain" , ti:: TermInfo )
194- print (io, " TermInfo(" , ti. names, " ; " , ti. flags, " flags, " ,
195- sum (ti. numbers), " numbers, " , sum (ti. strings), " strings" )
196- ! isempty (ti. extensions) > 0 &&
221+ print (io, " TermInfo(" , ti. names, " ; " , length ( ti. flags) , " flags, " ,
222+ length (ti. numbers), " numbers, " , length (ti. strings), " strings" )
223+ ! isnothing (ti. extensions) &&
197224 print (io, " , " , length (ti. extensions), " extended capabilities" )
198225 print (io, ' )' )
199226end
@@ -213,13 +240,15 @@ function find_terminfo_file(term::String)
213240 [ENV [" TERMINFO" ]]
214241 elseif isdir (joinpath (homedir (), " .terminfo" ))
215242 [joinpath (homedir (), " .terminfo" )]
216- elseif haskey (ENV , " TERMINFO_DIRS" )
217- split (ENV [" TERMINFO_DIRS" ], ' :' )
218- elseif Sys. isunix ()
219- [" /usr/share/terminfo" ]
220243 else
221244 String[]
222245 end
246+ haskey (ENV , " TERMINFO_DIRS" ) &&
247+ append! (terminfo_dirs,
248+ replace (split (ENV [" TERMINFO_DIRS" ], ' :' ),
249+ " " => " /usr/share/terminfo" ))
250+ Sys. isunix () &&
251+ push! (terminfo_dirs, " /etc/terminfo" , " /usr/share/terminfo" )
223252 for dir in terminfo_dirs
224253 if isfile (joinpath (dir, chr, term))
225254 return joinpath (dir, chr, term)
0 commit comments