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
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
1"""
2OSC 8 hyperlink parsing and measurement.
4.. versionadded:: 0.7.0
5"""
7from __future__ import annotations
9# std imports
10import re
12import typing
14# local
15from ._width import width as _width
16from .escape_sequences import _SEQUENCE_CLASSIFY
18HYPERLINK_OPEN_RE = re.compile(r'\x1b]8;([^;]*);([^\x07\x1b]*)(\x07|\x1b\\)')
19HYPERLINK_CLOSE_RE = re.compile(r'\x1b]8;;(\x07|\x1b\\)')
22class HyperlinkParams(typing.NamedTuple):
23 r"""
24 Parsed parameters from an OSC 8 hyperlink open sequence.
26 :param url: The hyperlink URL.
27 :param params: Colon-separated metadata string (often empty).
28 :param terminator: Sequence terminator (``\x07`` or ``\x1b\\``).
29 """
31 url: str
32 params: str = ''
33 terminator: str = '\x07'
35 @classmethod
36 def parse(cls, seq: str) -> HyperlinkParams | None:
37 r"""
38 Parse an OSC 8 open sequence string.
40 Returns ``None`` if *seq* is not a valid OSC 8 open.
42 Example::
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))
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}'
56 def make_close(self) -> str:
57 """Generate the OSC 8 close escape sequence."""
58 return f'\x1b]8;;{self.terminator}'
61class Hyperlink(typing.NamedTuple):
62 """
63 A complete OSC 8 hyperlink with target and inner text.
65 :param params: Parsed open sequence parameters.
66 :param text: Inner text between the open and close sequences.
67 """
69 params: HyperlinkParams
70 text: str
72 @classmethod
73 def find_close(cls, text: str, open_end: int) -> tuple[int, int]:
74 """
75 Find the matching OSC 8 close sequence.
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.
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())
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.
100 Delegates to :func:`wcwidth.width` with the given parameters.
102 Example::
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 )
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*.
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.
124 Example::
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])
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()