Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wcwidth/hyperlink.py: 53%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

47 statements  

1""" 

2OSC 8 hyperlink parsing and measurement. 

3 

4.. versionadded:: 0.7.0 

5""" 

6 

7from __future__ import annotations 

8 

9# std imports 

10import re 

11 

12import typing 

13 

14# local 

15from ._width import width as _width 

16from .escape_sequences import _SEQUENCE_CLASSIFY 

17 

18HYPERLINK_OPEN_RE = re.compile(r'\x1b]8;([^;]*);([^\x07\x1b]*)(\x07|\x1b\\)') 

19HYPERLINK_CLOSE_RE = re.compile(r'\x1b]8;;(\x07|\x1b\\)') 

20 

21 

22class HyperlinkParams(typing.NamedTuple): 

23 r""" 

24 Parsed parameters from an OSC 8 hyperlink open sequence. 

25 

26 :param url: The hyperlink URL. 

27 :param params: Colon-separated metadata string (often empty). 

28 :param terminator: Sequence terminator (``\x07`` or ``\x1b\\``). 

29 """ 

30 

31 url: str 

32 params: str = '' 

33 terminator: str = '\x07' 

34 

35 @classmethod 

36 def parse(cls, seq: str) -> HyperlinkParams | None: 

37 r""" 

38 Parse an OSC 8 open sequence string. 

39 

40 Returns ``None`` if *seq* is not a valid OSC 8 open. 

41 

42 Example:: 

43 

44 >>> HyperlinkParams.parse('\x1b]8;;http://example.com\x07') 

45 HyperlinkParams(url='http://example.com', params='', terminator='\\x07') 

46 """ 

47 m = HYPERLINK_OPEN_RE.match(seq) 

48 if m is None: 

49 return None 

50 return cls(url=m.group(2), params=m.group(1), terminator=m.group(3)) 

51 

52 def make_open(self) -> str: 

53 """Generate the OSC 8 open escape sequence.""" 

54 return f'\x1b]8;{self.params};{self.url}{self.terminator}' 

55 

56 def make_close(self) -> str: 

57 """Generate the OSC 8 close escape sequence.""" 

58 return f'\x1b]8;;{self.terminator}' 

59 

60 

61class Hyperlink(typing.NamedTuple): 

62 """ 

63 A complete OSC 8 hyperlink with target and inner text. 

64 

65 :param params: Parsed open sequence parameters. 

66 :param text: Inner text between the open and close sequences. 

67 """ 

68 

69 params: HyperlinkParams 

70 text: str 

71 

72 @classmethod 

73 def find_close(cls, text: str, open_end: int) -> tuple[int, int]: 

74 """ 

75 Find the matching OSC 8 close sequence. 

76 

77 Searches 'text' starting at 'open_end', the position just past the open 

78 sequence. Returns position of close sequence ``(close_start, 

79 close_end)`` or ``(-1, -1)`` if not found. 

80 

81 Per the OSC 8 specification, terminal emulators treat hyperlinks as a 

82 state attribute, not as nested HTML anchors. A close sequence closes 

83 the current hyperlink regardless of how many open sequences preceded it. 

84 """ 

85 m = HYPERLINK_CLOSE_RE.search(text, open_end) 

86 if m is None: 

87 return (-1, -1) 

88 return (m.start(), m.end()) 

89 

90 def display_width( 

91 self, 

92 *, 

93 control_codes: typing.Literal['parse', 'strict', 'ignore'] = 'parse', 

94 tabsize: int = 8, 

95 ambiguous_width: int = 1, 

96 ) -> int: 

97 r""" 

98 Measure the display width of the hyperlink's inner text. 

99 

100 Delegates to :func:`wcwidth.width` with the given parameters. 

101 

102 Example:: 

103 

104 >>> hl = Hyperlink.parse('\x1b]8;;http://example.com\x07Hello\x1b]8;;\x07', 0) 

105 >>> hl.display_width() 

106 5 

107 """ 

108 return _width( 

109 self.text, 

110 control_codes=control_codes, 

111 tabsize=tabsize, 

112 ambiguous_width=ambiguous_width, 

113 ) 

114 

115 @classmethod 

116 def parse(cls, text: str, start: int = 0) -> Hyperlink | None: 

117 r""" 

118 Parse a complete OSC 8 hyperlink unit from *text* at position *start*. 

119 

120 Locates the open sequence, finds the matching close, and returns a 

121 ``Hyperlink`` containing the parsed parameters and inner text. Returns 

122 ``None`` if the text at *start* is not a complete OSC 8 hyperlink. 

123 

124 Example:: 

125 

126 >>> Hyperlink.parse('\x1b]8;;http://example.com\x07Hello\x1b]8;;\x07') 

127 Hyperlink(params=HyperlinkParams(url='http://example.com', ...), text='Hello') 

128 """ 

129 m = _SEQUENCE_CLASSIFY.match(text, start) 

130 if m is None: 

131 return None 

132 params = HyperlinkParams.parse(m.group()) 

133 if params is None: 

134 return None 

135 close_start, close_end = cls.find_close(text, m.end()) 

136 if (close_start, close_end) == (-1, -1): 

137 return None 

138 return cls(params=params, text=text[m.end():close_start]) 

139 

140 def make_sequence(self) -> str: 

141 """Rebuild the complete OSC 8 hyperlink escape sequence.""" 

142 return self.params.make_open() + self.text + self.params.make_close()