pqx 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pqx/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """pqx — a fast, friendly terminal explorer for Parquet files."""
2
+
3
+ try: # written by setuptools-scm from the git tags at build/install time
4
+ from pqx._version import version as __version__
5
+ except ImportError: # a source checkout that was never built or installed
6
+ __version__ = "0.0.0.dev0"
pqx/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())
pqx/_terminal.py ADDED
@@ -0,0 +1,122 @@
1
+ """Work-arounds for terminals and multiplexers that speak older mouse protocols.
2
+
3
+ pqx asks for SGR mouse reporting (mode 1006), like every Textual app. Some
4
+ multiplexers (GNU screen 4.x in particular) don't support SGR and hand the app
5
+ X10-encoded events instead -- ``ESC [ M`` followed by three raw bytes -- or
6
+ urxvt-encoded ones (``ESC [ b;x;y M``). Textual (as of 8.x) has two problems
7
+ with that:
8
+
9
+ * it only parses SGR mouse events, so X10/urxvt clicks are silently dropped;
10
+ * it decodes terminal input as strict UTF-8, and an X10 coordinate above 95
11
+ is a byte > 127 that is not valid UTF-8: the input thread dies with
12
+ ``UnicodeDecodeError`` while the app keeps drawing -- it looks frozen.
13
+
14
+ :func:`install` makes input decoding lenient (an invalid byte becomes the
15
+ character with that code, which is exactly what an X10 coordinate means) and
16
+ translates X10/urxvt mouse events into SGR before Textual parses them.
17
+
18
+ It also keeps mouse coordinates in character cells. When a terminal says it
19
+ supports in-band resize reports (mode 2048), Textual switches the mouse to
20
+ pixel coordinates (mode 1016) and relies on those reports to convert back to
21
+ cells. iTerm2 accepts 1016 but doesn't send the reports, so every click lands
22
+ at a pixel position far off-screen and does nothing. Textual guards against
23
+ this only when ``TERM_PROGRAM``/``LC_TERMINAL`` name iTerm, which they don't
24
+ over ssh. pqx never needs sub-cell mouse precision, so pixel mode stays off.
25
+
26
+ Finally, quitting always leaves a clean terminal. Textual draws on the
27
+ alternate screen (mode 1049) and switches back on exit, which restores the
28
+ shell's previous contents. Where the alternate screen is unavailable (some
29
+ ssh/container setups, or a terminal or tmux with it turned off), pqx would
30
+ have drawn over the normal screen and left its last frame behind, with the
31
+ prompt in the middle of it. pqx clears the screen just before switching
32
+ back: invisible when the alternate screen works, a clean screen when not.
33
+ """
34
+ from __future__ import annotations
35
+
36
+ import codecs
37
+ import functools
38
+ import re
39
+
40
+ _ERRORS = "pqx-bytes"
41
+ _X10 = re.compile(r"\x1b\[M(.)(.)(.)\Z", re.S)
42
+ _URXVT = re.compile(r"\x1b\[(\d+);(\d+);(\d+)M\Z")
43
+ _installed = False
44
+
45
+
46
+ def _bytes_as_chars(err: UnicodeDecodeError):
47
+ return "".join(chr(b) for b in err.object[err.start:err.end]), err.end
48
+
49
+
50
+ def x10_to_sgr(code: str, last_button: int = 0) -> tuple[str, int] | None:
51
+ """Translate an X10 or urxvt mouse sequence to SGR. Returns (sgr, last_button)."""
52
+ m = _X10.match(code)
53
+ if m:
54
+ b, x, y = (ord(c) - 32 for c in m.groups())
55
+ else:
56
+ m = _URXVT.match(code)
57
+ if not m:
58
+ return None
59
+ b, x, y = int(m.group(1)) - 32, int(m.group(2)), int(m.group(3))
60
+ if x < 1 or y < 1:
61
+ return None
62
+ if b & 64 or b & 32: # wheel, or motion (with or without a button held)
63
+ return f"\x1b[<{b};{x};{y}M", last_button
64
+ if b & 3 == 3: # X10 release doesn't say which button: use the one pressed last
65
+ return f"\x1b[<{last_button | (b & ~3)};{x};{y}m", last_button
66
+ return f"\x1b[<{b};{x};{y}M", b & 3
67
+
68
+
69
+ def install() -> None:
70
+ """Patch Textual's input path (idempotent)."""
71
+ global _installed
72
+ if _installed:
73
+ return
74
+ _installed = True
75
+ try:
76
+ codecs.lookup_error(_ERRORS)
77
+ except LookupError:
78
+ codecs.register_error(_ERRORS, _bytes_as_chars)
79
+
80
+ try:
81
+ from textual.drivers import linux_driver
82
+
83
+ real = codecs.getincrementaldecoder
84
+
85
+ def lenient(encoding: str):
86
+ return functools.partial(real(encoding), errors=_ERRORS)
87
+
88
+ linux_driver.getincrementaldecoder = lenient
89
+ except Exception: # pragma: no cover - other platforms / Textual versions
90
+ pass
91
+
92
+ try:
93
+ from textual.drivers.linux_driver import LinuxDriver
94
+
95
+ LinuxDriver._enable_mouse_pixels = lambda self: None # cells, always (see above)
96
+
97
+ stop = LinuxDriver.stop_application_mode
98
+
99
+ @functools.wraps(stop)
100
+ def stop_application_mode(self):
101
+ self.write("\x1b[0m\x1b[H\x1b[2J") # reset attributes, home, clear: before leaving 1049
102
+ stop(self)
103
+
104
+ LinuxDriver.stop_application_mode = stop_application_mode
105
+ except Exception: # pragma: no cover
106
+ pass
107
+
108
+ try:
109
+ from textual._xterm_parser import XTermParser
110
+
111
+ original = XTermParser.parse_mouse_code
112
+
113
+ @functools.wraps(original)
114
+ def parse_mouse_code(self, code: str):
115
+ translated = x10_to_sgr(code, getattr(self, "_pqx_last_button", 0))
116
+ if translated is not None:
117
+ code, self._pqx_last_button = translated
118
+ return original(self, code)
119
+
120
+ XTermParser.parse_mouse_code = parse_mouse_code
121
+ except Exception: # pragma: no cover
122
+ pass
pqx/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = 'g006519c4f'