7171 "OTGV" , "OTGC" ,"meml" , "memu" , "box1"
7272)
7373# fmt: on
74- _STRING_CAPABILITY_NAMES = {name : i for i , name in enumerate (_STRING_NAMES )}
7574
7675
7776def _get_terminfo_dirs () -> list [Path ]:
@@ -322,10 +321,6 @@ class TermInfo:
322321 terminal_name : str | bytes | None
323322 fallback : bool = True
324323
325- _names : list [str ] = field (default_factory = list )
326- _booleans : list [int ] = field (default_factory = list )
327- _numbers : list [int ] = field (default_factory = list )
328- _strings : list [bytes | None ] = field (default_factory = list )
329324 _capabilities : dict [str , bytes ] = field (default_factory = dict )
330325
331326 def __post_init__ (self ) -> None :
@@ -362,9 +357,12 @@ def __post_init__(self) -> None:
362357 def _parse_terminfo_file (self , terminal_name : str ) -> None :
363358 """Parse a terminfo file.
364359
360+ Populate the _capabilities dict for easy retrieval
361+
365362 Based on ncurses implementation in:
366363 - ncurses/tinfo/read_entry.c:_nc_read_termtype()
367364 - ncurses/tinfo/read_entry.c:_nc_read_file_entry()
365+ - ncurses/tinfo/lib_ti.c:tigetstr()
368366 """
369367 data = _read_terminfo_file (terminal_name )
370368 too_short = f"TermInfo file for { terminal_name !r} too short"
@@ -377,107 +375,67 @@ def _parse_terminfo_file(self, terminal_name: str) -> None:
377375 )
378376
379377 if magic == MAGIC16 :
380- number_format = "<h" # 16-bit signed
381378 number_size = 2
382379 elif magic == MAGIC32 :
383- number_format = "<i" # 32-bit signed
384380 number_size = 4
385381 else :
386382 raise ValueError (
387383 f"TermInfo file for { terminal_name !r} uses unknown magic"
388384 )
389385
390- # Read terminal names
391- if offset + name_size > len (data ):
392- raise ValueError (too_short )
393- names = data [offset : offset + name_size - 1 ].decode (
394- "ascii" , errors = "ignore"
395- )
386+ # Skip data than PyREPL doesn't need:
387+ # - names (`|`-separated ASCII strings)
388+ # - boolean capabilities (bytes with value 0 or 1)
389+ # - numbers (little-endian integers, `number_size` bytes each)
396390 offset += name_size
397-
398- # Read boolean capabilities
399- if offset + bool_count > len (data ):
400- raise ValueError (too_short )
401- booleans = list (data [offset : offset + bool_count ])
402391 offset += bool_count
403-
404- # Align to even byte boundary for numbers
405392 if offset % 2 :
393+ # Align to even byte boundary for numbers
406394 offset += 1
407-
408- # Read numeric capabilities
409- numbers = []
410- for i in range (num_count ):
411- if offset + number_size > len (data ):
412- raise ValueError (too_short )
413- num = struct .unpack (
414- number_format , data [offset : offset + number_size ]
415- )[0 ]
416- numbers .append (num )
417- offset += number_size
395+ offset += num_count * number_size
396+ if offset > len (data ):
397+ raise ValueError (too_short )
418398
419399 # Read string offsets
420- string_offsets = []
421- for i in range (str_count ):
422- if offset + 2 > len (data ):
423- raise ValueError (too_short )
424- off = struct .unpack ("<h" , data [offset : offset + 2 ])[0 ]
425- string_offsets .append (off )
426- offset += 2
400+ end_offset = offset + 2 * str_count
401+ if offset > len (data ):
402+ raise ValueError (too_short )
403+ string_offset_data = data [offset :end_offset ]
404+ string_offsets = [
405+ off for [off ] in struct .iter_unpack ("<h" , string_offset_data )
406+ ]
407+ offset = end_offset
427408
428409 # Read string table
429410 if offset + str_size > len (data ):
430411 raise ValueError (too_short )
431412 string_table = data [offset : offset + str_size ]
432413
433414 # Extract strings from string table
434- strings : list [ bytes | None ] = []
435- for off in string_offsets :
415+ capabilities = {}
416+ for cap , off in zip ( _STRING_NAMES , string_offsets ) :
436417 if off < 0 :
437- strings .append (CANCELLED_STRING )
418+ # CANCELLED_STRING; we do not store those
419+ continue
438420 elif off < len (string_table ):
439421 # Find null terminator
440- end = off
441- while end < len (string_table ) and string_table [end ] != 0 :
442- end += 1
443- if end <= len (string_table ):
444- strings .append (string_table [off :end ])
445- else :
446- strings .append (ABSENT_STRING )
447- else :
448- strings .append (ABSENT_STRING )
449-
450- self ._names = names .split ("|" )
451- self ._booleans = booleans
452- self ._numbers = numbers
453- self ._strings = strings
422+ end = string_table .find (0 , off )
423+ if end >= 0 :
424+ capabilities [cap ] = string_table [off :end ]
425+ # in other cases this is ABSENT_STRING; we don't store those.
454426
455- def get ( self , cap : str ) -> bytes | None :
456- """Get terminal capability string by name .
427+ # Note: we don't support extended capabilities since PyREPL doesn't
428+ # need them .
457429
458- Based on ncurses implementation in:
459- - ncurses/tinfo/lib_ti.c:tigetstr()
430+ self ._capabilities = capabilities
460431
461- The ncurses version searches through compiled terminfo data structures.
462- This version first checks parsed terminfo data, then falls back to
463- hardcoded capabilities.
432+ def get (self , cap : str ) -> bytes | None :
433+ """Get terminal capability string by name.
464434 """
465435 if not isinstance (cap , str ):
466436 raise TypeError (f"`cap` must be a string, not { type (cap )} " )
467437
468- if self ._capabilities :
469- # Fallbacks populated, use them
470- return self ._capabilities .get (cap )
471-
472- # Look up in standard capabilities first
473- if cap in _STRING_CAPABILITY_NAMES :
474- index = _STRING_CAPABILITY_NAMES [cap ]
475- if index < len (self ._strings ):
476- return self ._strings [index ]
477-
478- # Note: we don't support extended capabilities since PyREPL doesn't
479- # need them.
480- return None
438+ return self ._capabilities .get (cap )
481439
482440
483441def tparm (cap_bytes : bytes , * params : int ) -> bytes :
0 commit comments