euporie 2.6.1__py3-none-any.whl → 2.7.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.
Files changed (67) hide show
  1. euporie/console/tabs/console.py +51 -43
  2. euporie/core/__init__.py +5 -2
  3. euporie/core/app.py +74 -57
  4. euporie/core/comm/ipywidgets.py +7 -3
  5. euporie/core/config.py +51 -27
  6. euporie/core/convert/__init__.py +2 -0
  7. euporie/core/convert/datum.py +82 -45
  8. euporie/core/convert/formats/ansi.py +1 -2
  9. euporie/core/convert/formats/common.py +7 -11
  10. euporie/core/convert/formats/ft.py +10 -7
  11. euporie/core/convert/formats/png.py +7 -6
  12. euporie/core/convert/formats/sixel.py +1 -1
  13. euporie/core/convert/formats/svg.py +28 -0
  14. euporie/core/convert/mime.py +4 -7
  15. euporie/core/data_structures.py +24 -22
  16. euporie/core/filters.py +16 -2
  17. euporie/core/format.py +30 -4
  18. euporie/core/ft/ansi.py +2 -1
  19. euporie/core/ft/html.py +155 -42
  20. euporie/core/{widgets/graphics.py → graphics.py} +225 -227
  21. euporie/core/io.py +8 -0
  22. euporie/core/key_binding/bindings/__init__.py +8 -2
  23. euporie/core/key_binding/bindings/basic.py +9 -14
  24. euporie/core/key_binding/bindings/micro.py +0 -12
  25. euporie/core/key_binding/bindings/mouse.py +107 -80
  26. euporie/core/key_binding/bindings/page_navigation.py +129 -0
  27. euporie/core/key_binding/key_processor.py +9 -1
  28. euporie/core/layout/__init__.py +1 -0
  29. euporie/core/layout/containers.py +1011 -0
  30. euporie/core/layout/decor.py +381 -0
  31. euporie/core/layout/print.py +130 -0
  32. euporie/core/layout/screen.py +75 -0
  33. euporie/core/{widgets/page.py → layout/scroll.py} +166 -111
  34. euporie/core/log.py +1 -1
  35. euporie/core/margins.py +11 -5
  36. euporie/core/path.py +43 -176
  37. euporie/core/renderer.py +31 -8
  38. euporie/core/style.py +2 -0
  39. euporie/core/tabs/base.py +2 -1
  40. euporie/core/terminal.py +19 -21
  41. euporie/core/widgets/cell.py +2 -4
  42. euporie/core/widgets/cell_outputs.py +2 -2
  43. euporie/core/widgets/decor.py +3 -359
  44. euporie/core/widgets/dialog.py +5 -5
  45. euporie/core/widgets/display.py +32 -12
  46. euporie/core/widgets/file_browser.py +3 -4
  47. euporie/core/widgets/forms.py +36 -14
  48. euporie/core/widgets/inputs.py +171 -99
  49. euporie/core/widgets/layout.py +80 -5
  50. euporie/core/widgets/menu.py +1 -3
  51. euporie/core/widgets/pager.py +3 -3
  52. euporie/core/widgets/palette.py +3 -2
  53. euporie/core/widgets/status_bar.py +2 -6
  54. euporie/core/widgets/tree.py +3 -6
  55. euporie/notebook/app.py +8 -8
  56. euporie/notebook/tabs/notebook.py +2 -2
  57. euporie/notebook/widgets/side_bar.py +1 -1
  58. euporie/preview/tabs/notebook.py +2 -2
  59. euporie/web/tabs/web.py +6 -1
  60. euporie/web/widgets/webview.py +52 -32
  61. {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/METADATA +9 -11
  62. {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/RECORD +67 -60
  63. {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/WHEEL +1 -1
  64. {euporie-2.6.1.data → euporie-2.7.0.data}/data/share/applications/euporie-console.desktop +0 -0
  65. {euporie-2.6.1.data → euporie-2.7.0.data}/data/share/applications/euporie-notebook.desktop +0 -0
  66. {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/entry_points.txt +0 -0
  67. {euporie-2.6.1.dist-info → euporie-2.7.0.dist-info}/licenses/LICENSE +0 -0
euporie/core/path.py CHANGED
@@ -2,14 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import base64
6
- import binascii
7
5
  import contextlib
8
- import io
9
6
  import logging
10
7
  from pathlib import Path
11
- from typing import TYPE_CHECKING, overload
12
- from urllib.parse import unquote, urljoin, urlunsplit
8
+ from typing import TYPE_CHECKING
9
+ from urllib.parse import urljoin, urlunsplit
13
10
 
14
11
  import upath
15
12
  from aiohttp.client_reqrep import ClientResponse
@@ -19,39 +16,20 @@ from fsspec.registry import register_implementation as fs_register_implementatio
19
16
  from upath import UPath
20
17
  from upath.implementations.http import HTTPPath as _HTTPPath
21
18
  from upath.implementations.http import _HTTPAccessor
22
- from upath.registry import _registry
19
+ from upath.registry import register_implementation
23
20
 
24
21
  if TYPE_CHECKING:
25
- from io import BufferedRandom, BufferedReader, BufferedWriter, FileIO, TextIOWrapper
26
22
  from os import PathLike
27
- from typing import IO, Any, BinaryIO, Literal
23
+ from typing import Any
28
24
  from urllib.parse import SplitResult
29
25
 
30
- from _typeshed import (
31
- OpenBinaryMode,
32
- OpenBinaryModeReading,
33
- OpenBinaryModeUpdating,
34
- OpenBinaryModeWriting,
35
- OpenTextMode,
36
- )
37
26
  from upath.core import PT
38
27
 
39
28
 
40
29
  log = logging.getLogger(__name__)
41
30
 
42
31
 
43
- def parse_path(path: str | PathLike, resolve: bool = True) -> Path:
44
- """Parse and resolve a path."""
45
- if not isinstance(path, Path):
46
- path = UPath(path)
47
- with contextlib.suppress(NotImplementedError):
48
- path = path.expanduser()
49
- if resolve:
50
- try:
51
- path = path.resolve()
52
- except (AttributeError, NotImplementedError, Exception):
53
- log.info("Path %s not resolvable", path)
54
- return path
32
+ # Monkey-patch `aiohttp` to not raise exceptions on non-200 responses
55
33
 
56
34
 
57
35
  def _raise_for_status(self: ClientResponse) -> None:
@@ -61,148 +39,21 @@ def _raise_for_status(self: ClientResponse) -> None:
61
39
  setattr(ClientResponse, "raise_for_status", _raise_for_status) # noqa B010
62
40
 
63
41
 
64
- class DataPath(upath.core.UPath):
65
- """A :py:class:`pathlib` compatible class for reading data URIs."""
42
+ # Define and register non-raising HTTP filesystem implementation for fsspec
43
+
44
+
45
+ class HTTPFileSystem(FsHTTPFileSystem):
46
+ """A :py:class:`HTTPFileSystem` which does not raise exceptions on 404 errors."""
47
+
48
+ def _raise_not_found_for_status(self, response: ClientResponse, url: str) -> None:
49
+ """Do not raise an exception for 404 errors."""
50
+
51
+
52
+ fs_register_implementation("http", HTTPFileSystem, clobber=True)
53
+ fs_register_implementation("https", HTTPFileSystem, clobber=True)
54
+
66
55
 
67
- @overload
68
- def open(
69
- self,
70
- mode: OpenTextMode = "r",
71
- buffering: int = -1,
72
- encoding: str | None = None,
73
- errors: str | None = None,
74
- newline: str | None = None,
75
- ) -> TextIOWrapper:
76
- ...
77
-
78
- # Unbuffered binary mode: returns a FileIO
79
- @overload
80
- def open(
81
- self,
82
- mode: OpenBinaryMode,
83
- buffering: Literal[0],
84
- encoding: None = None,
85
- errors: None = None,
86
- newline: None = None,
87
- ) -> FileIO:
88
- ...
89
-
90
- # Buffering is on: return BufferedRandom, BufferedReader, or BufferedWriter
91
- @overload
92
- def open(
93
- self,
94
- mode: OpenBinaryModeUpdating,
95
- buffering: Literal[-1, 1] = -1,
96
- encoding: None = None,
97
- errors: None = None,
98
- newline: None = None,
99
- ) -> BufferedRandom:
100
- ...
101
-
102
- @overload
103
- def open(
104
- self,
105
- mode: OpenBinaryModeWriting,
106
- buffering: Literal[-1, 1] = -1,
107
- encoding: None = None,
108
- errors: None = None,
109
- newline: None = None,
110
- ) -> BufferedWriter:
111
- ...
112
-
113
- @overload
114
- def open(
115
- self,
116
- mode: OpenBinaryModeReading,
117
- buffering: Literal[-1, 1] = -1,
118
- encoding: None = None,
119
- errors: None = None,
120
- newline: None = None,
121
- ) -> BufferedReader:
122
- ...
123
-
124
- # Buffering cannot be determined: fall back to BinaryIO
125
- @overload
126
- def open(
127
- self,
128
- mode: OpenBinaryMode,
129
- buffering: int = -1,
130
- encoding: None = None,
131
- errors: None = None,
132
- newline: None = None,
133
- ) -> BinaryIO:
134
- ...
135
-
136
- # Fallback if mode is not specified
137
- @overload
138
- def open(
139
- self,
140
- mode: str,
141
- buffering: int = -1,
142
- encoding: str | None = None,
143
- errors: str | None = None,
144
- newline: str | None = None,
145
- ) -> IO[Any]:
146
- ...
147
-
148
- def open(
149
- self,
150
- mode: OpenTextMode
151
- | OpenBinaryMode
152
- | OpenBinaryModeReading
153
- | OpenBinaryModeWriting
154
- | OpenBinaryModeUpdating
155
- | str = "r",
156
- buffering: Literal[-1, 0, 1] | int = -1,
157
- encoding: str | None = None,
158
- errors: str | None = None,
159
- newline: str | None = None,
160
- ) -> IO[Any]:
161
- """Return an io object for the data in the URI."""
162
- assert self._url is not None
163
- data_format, _, encoded_data = self._url.path.partition(",")
164
- _mime, *params = data_format.split(";")
165
-
166
- data_bytes = None
167
- data_str = None
168
-
169
- if "base64" in params:
170
- try:
171
- data_bytes = base64.b64decode(encoded_data)
172
- except binascii.Error:
173
- log.warning("Failed to decode base64 encoded data")
174
- data_bytes = b""
175
- else:
176
- data_str = unquote(encoded_data)
177
-
178
- if "b" in mode:
179
- if data_bytes is None:
180
- assert data_str is not None
181
- data_bytes = data_str.encode()
182
- return io.BytesIO(data_bytes)
183
- else:
184
- if data_str is None:
185
- assert data_bytes is not None
186
- # decode_kwargs: dict[str, str] = {}
187
- # if encoding is not None: #:= kwargs.get("encoding"):
188
- # decode_kwargs["encoding"] = str(encoding)
189
- data_str = data_bytes.decode() # **decode_kwargs)
190
- return io.StringIO(data_str)
191
-
192
- def exists(self, **kwargs: Any) -> bool:
193
- """Affirm that data URIs always exist."""
194
- return True
195
-
196
- @property
197
- def _mime(self) -> str:
198
- """Return the media type of the data URI."""
199
- assert self._url is not None
200
- data_format, _, _encoded_data = self._url.path.partition(",")
201
- mime, *params = data_format.split(";")
202
- return mime
203
-
204
-
205
- _registry.known_implementations["data"] = "euporie.core.path.DataPath"
56
+ # Define custom universal_pathlib path implementations
206
57
 
207
58
 
208
59
  class CachingHTTPAccessor(_HTTPAccessor):
@@ -256,16 +107,32 @@ class HTTPPath(_HTTPPath):
256
107
  return self._hash
257
108
 
258
109
 
259
- _registry.known_implementations["http"] = "euporie.core.path.HTTPPath"
260
- _registry.known_implementations["https"] = "euporie.core.path.HTTPPath"
110
+ class _DataAccessor(upath.core._FSSpecAccessor):
111
+ def _format_path(self, path: upath.core.UPath) -> str:
112
+ """Return the full URI as a string."""
113
+ return str(path)
261
114
 
262
115
 
263
- class HTTPFileSystem(FsHTTPFileSystem):
264
- """A :py:class:`HTTPFileSystem` which does not raise exceptions on 404 errors."""
116
+ class DataPath(upath.core.UPath):
117
+ """A :py:class:`pathlib` compatible class for reading data URIs."""
265
118
 
266
- def _raise_not_found_for_status(self, response: ClientResponse, url: str) -> None:
267
- """Do not raise an exception for 404 errors."""
119
+ _default_accessor = _DataAccessor
268
120
 
269
121
 
270
- fs_register_implementation("http", HTTPFileSystem, clobber=True)
271
- fs_register_implementation("https", HTTPFileSystem, clobber=True)
122
+ register_implementation("data", DataPath, clobber=True)
123
+ register_implementation("http", HTTPPath, clobber=True)
124
+ register_implementation("https", HTTPPath, clobber=True)
125
+
126
+
127
+ def parse_path(path: str | PathLike, resolve: bool = True) -> Path:
128
+ """Parse and resolve a path."""
129
+ if not isinstance(path, Path):
130
+ path = UPath(path)
131
+ with contextlib.suppress(NotImplementedError):
132
+ path = path.expanduser()
133
+ if resolve:
134
+ try:
135
+ path = path.resolve()
136
+ except (AttributeError, NotImplementedError, Exception):
137
+ log.info("Path %s not resolvable", path)
138
+ return path
euporie/core/renderer.py CHANGED
@@ -8,11 +8,11 @@ from typing import TYPE_CHECKING
8
8
  from prompt_toolkit.data_structures import Point, Size
9
9
  from prompt_toolkit.filters import to_filter
10
10
  from prompt_toolkit.layout.mouse_handlers import MouseHandlers
11
- from prompt_toolkit.layout.screen import Char, Screen, WritePosition
12
11
  from prompt_toolkit.renderer import Renderer as PtkRenderer
13
12
  from prompt_toolkit.renderer import _StyleStringHasStyleCache, _StyleStringToAttrsCache
14
13
 
15
14
  from euporie.core.io import Vt100_Output
15
+ from euporie.core.layout.screen import BoundedWritePosition, Screen
16
16
 
17
17
  if TYPE_CHECKING:
18
18
  from typing import Any, Callable
@@ -20,6 +20,8 @@ if TYPE_CHECKING:
20
20
  from prompt_toolkit.application import Application
21
21
  from prompt_toolkit.filters import FilterOrBool
22
22
  from prompt_toolkit.layout.layout import Layout
23
+ from prompt_toolkit.layout.screen import Char
24
+ from prompt_toolkit.layout.screen import Screen as PtkScreen
23
25
  from prompt_toolkit.output import ColorDepth, Output
24
26
  from prompt_toolkit.styles import BaseStyle
25
27
 
@@ -32,10 +34,10 @@ log = logging.getLogger(__name__)
32
34
  def _output_screen_diff(
33
35
  app: Application[Any],
34
36
  output: Output,
35
- screen: Screen,
37
+ screen: PtkScreen,
36
38
  current_pos: Point,
37
39
  color_depth: ColorDepth,
38
- previous_screen: Screen | None,
40
+ previous_screen: PtkScreen | None,
39
41
  last_style: str | None,
40
42
  is_done: bool, # XXX: drop is_done
41
43
  full_screen: bool,
@@ -123,7 +125,9 @@ def _output_screen_diff(
123
125
  for index, cell in row.items()
124
126
  if cell.char != " " or style_string_has_style[cell.style]
125
127
  }
126
- | zwe_row.keys()
128
+ # Lag ZWE indices by one, as one could exist after the last line character
129
+ # but we don't want that to count towards the line width
130
+ | {x - 1 for x in zwe_row}
127
131
  | {0}
128
132
  )
129
133
 
@@ -148,6 +152,7 @@ def _output_screen_diff(
148
152
  output.erase_down()
149
153
 
150
154
  previous_screen = Screen()
155
+ assert previous_screen is not None
151
156
 
152
157
  # Get height of the screen.
153
158
  # (height changes as we loop over data_buffer, so remember the current value.)
@@ -174,7 +179,8 @@ def _output_screen_diff(
174
179
 
175
180
  prev_diff_char = False
176
181
 
177
- # We might have a ZWE sequence just beyond the end of the line
182
+ # Loop just beyond the line length to check for ZWE sequences right at the end
183
+ # of the line
178
184
  while c <= new_max_line_len + 1:
179
185
  new_char = new_row[c]
180
186
  old_char = previous_row[c]
@@ -268,16 +274,21 @@ class Renderer(PtkRenderer):
268
274
  style, output, full_screen, mouse_support, cpr_not_supported_callback
269
275
  )
270
276
  self._extended_keys_enabled = False
277
+ self._private_sixel_colors_enabled = False
271
278
  self.extend_height = to_filter(extend_height)
272
279
  self.extend_width = to_filter(extend_width)
273
280
 
274
281
  def reset(self, _scroll: bool = False, leave_alternate_screen: bool = True) -> None:
275
- """Disable extended keys before resetting the output."""
276
- # Disable extended keys
282
+ """Reset the output."""
277
283
  if isinstance(self.output, Vt100_Output):
284
+ # Disable extended keys before resetting the output
278
285
  self.output.disable_extended_keys()
279
286
  self._extended_keys_enabled = False
280
287
 
288
+ # Disable private sixel colors before resetting the output
289
+ self.output.disable_private_sixel_colors()
290
+ self._private_sixel_colors_enabled = False
291
+
281
292
  super().reset(_scroll, leave_alternate_screen)
282
293
 
283
294
  def render(
@@ -318,6 +329,13 @@ class Renderer(PtkRenderer):
318
329
  self.output.enable_extended_keys()
319
330
  self._extended_keys_enabled = True
320
331
 
332
+ # Ensable private sixel graphic color registers
333
+ if not self._private_sixel_colors_enabled and isinstance(
334
+ self.output, Vt100_Output
335
+ ):
336
+ self.output.enable_private_sixel_colors()
337
+ self._private_sixel_colors_enabled = True
338
+
321
339
  # Create screen and write layout to it.
322
340
  size = output.get_size()
323
341
  screen = Screen()
@@ -376,7 +394,12 @@ class Renderer(PtkRenderer):
376
394
  layout.container.write_to_screen(
377
395
  screen,
378
396
  mouse_handlers,
379
- WritePosition(xpos=0, ypos=0, width=size.columns, height=height),
397
+ BoundedWritePosition(
398
+ xpos=0,
399
+ ypos=0,
400
+ width=size.columns,
401
+ height=height,
402
+ ),
380
403
  parent_style="",
381
404
  erase_bg=False,
382
405
  z_index=None,
euporie/core/style.py CHANGED
@@ -424,6 +424,8 @@ def build_style(
424
424
  # status of cursor-line.
425
425
  **dict(default_ui_style().style_rules),
426
426
  "default": f"fg:{cp.bg.base} bg:{cp.bg.base}",
427
+ # Remove non-breaking space style from PTK
428
+ "nbsp": "nounderline fg:default",
427
429
  # Logo
428
430
  "logo": "fg:#dd0000",
429
431
  # Pattern
euporie/core/tabs/base.py CHANGED
@@ -10,7 +10,7 @@ from functools import partial
10
10
  from typing import TYPE_CHECKING, ClassVar
11
11
 
12
12
  from prompt_toolkit.history import InMemoryHistory
13
- from prompt_toolkit.layout.containers import Window, WindowAlign
13
+ from prompt_toolkit.layout.containers import WindowAlign
14
14
  from prompt_toolkit.layout.controls import FormattedTextControl
15
15
 
16
16
  from euporie.core.comm.registry import open_comm
@@ -24,6 +24,7 @@ from euporie.core.kernel import Kernel, MsgCallbacks
24
24
  from euporie.core.key_binding.registry import (
25
25
  register_bindings,
26
26
  )
27
+ from euporie.core.layout.containers import Window
27
28
  from euporie.core.suggest import HistoryAutoSuggest
28
29
  from euporie.core.utils import run_in_thread_with_context
29
30
 
euporie/core/terminal.py CHANGED
@@ -14,7 +14,6 @@ from functools import lru_cache
14
14
  from typing import TYPE_CHECKING, ClassVar
15
15
 
16
16
  from aenum import extend_enum
17
- from prompt_toolkit.application.current import get_app
18
17
  from prompt_toolkit.application.run_in_terminal import run_in_terminal
19
18
  from prompt_toolkit.key_binding.key_processor import KeyProcessor, _Flush
20
19
  from prompt_toolkit.keys import Keys
@@ -22,7 +21,8 @@ from prompt_toolkit.output import ColorDepth
22
21
  from prompt_toolkit.utils import Event
23
22
 
24
23
  from euporie.core.commands import add_cmd
25
- from euporie.core.filters import in_tmux
24
+ from euporie.core.current import get_app
25
+ from euporie.core.filters import in_screen, in_tmux
26
26
  from euporie.core.key_binding.registry import register_bindings
27
27
  from euporie.core.style import DEFAULT_COLORS
28
28
 
@@ -50,11 +50,17 @@ def _have_termios_tty_fcntl() -> bool:
50
50
  return True
51
51
 
52
52
 
53
- def tmuxify(cmd: str) -> str:
54
- """Wrap an escape sequence for tmux passthrough."""
55
- if in_tmux():
56
- cmd = cmd.replace("\x1b", "\x1b\x1b")
57
- cmd = f"\x1bPtmux;{cmd}\033\\"
53
+ def passthrough(cmd: str) -> str:
54
+ """Wrap an escape sequence for terminal passthrough."""
55
+ if get_app().config.multiplexer_passthrough:
56
+ if in_tmux():
57
+ cmd = cmd.replace("\x1b", "\x1b\x1b")
58
+ cmd = f"\x1bPtmux;{cmd}\x1b\\"
59
+ elif in_screen():
60
+ # Screen limits escape sequences to 768 bytes, so we have to chunk it
61
+ cmd = "".join(
62
+ f"\x1bP{cmd[i: i+764]}\x1b\\" for i in range(0, len(cmd), 764)
63
+ )
58
64
  return cmd
59
65
 
60
66
 
@@ -198,7 +204,7 @@ class Colors(TerminalQuery):
198
204
  )
199
205
 
200
206
  def _cmd(self) -> str:
201
- return tmuxify(self.cmd)
207
+ return passthrough(self.cmd)
202
208
 
203
209
  def verify(self, data: str) -> dict[str, str]:
204
210
  """Verify the response contains a colour."""
@@ -247,7 +253,7 @@ class KittyGraphicsStatus(TerminalQuery):
247
253
 
248
254
  def _cmd(self) -> str:
249
255
  """Hide the command in case the terminal does not support this sequence."""
250
- return "\x1b[s" + tmuxify(self.cmd) + "\x1b[u\x1b[2K"
256
+ return "\x1b[s" + passthrough(self.cmd) + "\x1b[u\x1b[2K"
251
257
 
252
258
  def verify(self, data: str) -> bool:
253
259
  """Verify the terminal response means kitty graphics are supported."""
@@ -269,10 +275,7 @@ class SixelGraphicsStatus(TerminalQuery):
269
275
  pattern = re.compile(r"^\x1b\[\?(?:\d+;)*(?P<sixel>4)(?:;\d+)*c\Z")
270
276
 
271
277
  def _cmd(self) -> str:
272
- if self.config.tmux_graphics:
273
- return tmuxify(self.cmd)
274
- else:
275
- return self.cmd
278
+ return passthrough(self.cmd)
276
279
 
277
280
  def verify(self, data: str) -> bool:
278
281
  """Verify the terminal response means sixel graphics are supported."""
@@ -294,10 +297,7 @@ class ItermGraphicsStatus(TerminalQuery):
294
297
  pattern = re.compile(r"^\x1bP>\|(?P<term>[^\x1b]+)\x1b\\")
295
298
 
296
299
  def _cmd(self) -> str:
297
- if self.config.tmux_graphics:
298
- return tmuxify(self.cmd)
299
- else:
300
- return self.cmd
300
+ return passthrough(self.cmd)
301
301
 
302
302
  def verify(self, data: str) -> bool:
303
303
  """Verify iterm graphics are supported by the terminal."""
@@ -345,13 +345,11 @@ class SgrPixelStatus(TerminalQuery):
345
345
 
346
346
  def verify(self, data: str) -> bool:
347
347
  """Verify the terminal response means SGR pixel-mode is supported."""
348
- if (
348
+ return bool(
349
349
  (match := self.pattern.match(data))
350
350
  and (values := match.groupdict())
351
351
  and (values.get("Pm") in {"1", "3"})
352
- ):
353
- return True
354
- return False
352
+ )
355
353
 
356
354
 
357
355
  class CsiUStatus(TerminalQuery):
@@ -15,9 +15,6 @@ from prompt_toolkit.filters import Condition
15
15
  from prompt_toolkit.layout.containers import (
16
16
  ConditionalContainer,
17
17
  Container,
18
- HSplit,
19
- VSplit,
20
- Window,
21
18
  )
22
19
  from prompt_toolkit.layout.controls import FormattedTextControl
23
20
  from prompt_toolkit.layout.dimension import Dimension
@@ -30,6 +27,7 @@ from euporie.core.config import add_setting
30
27
  from euporie.core.current import get_app
31
28
  from euporie.core.filters import multiple_cells_selected
32
29
  from euporie.core.format import format_code
30
+ from euporie.core.layout.containers import HSplit, VSplit, Window
33
31
  from euporie.core.utils import on_click
34
32
  from euporie.core.widgets.cell_outputs import CellOutputArea
35
33
  from euporie.core.widgets.inputs import KernelInput, StdInput
@@ -442,7 +440,7 @@ class Cell:
442
440
  self.rendered = False
443
441
  if position is not None:
444
442
  self.input_box.buffer.cursor_position = position % (
445
- len(self.input_box.buffer.text) or 1
443
+ len(self.input_box.buffer.text) + 1
446
444
  )
447
445
  else:
448
446
  to_focus = self.kernel_tab.cell.control
@@ -11,10 +11,8 @@ from prompt_toolkit.cache import SimpleCache
11
11
  from prompt_toolkit.filters import buffer_has_focus
12
12
  from prompt_toolkit.layout.containers import (
13
13
  DynamicContainer,
14
- HSplit,
15
14
  to_container,
16
15
  )
17
- from prompt_toolkit.widgets.base import Box
18
16
 
19
17
  from euporie.core.config import add_setting
20
18
  from euporie.core.convert.datum import Datum
@@ -22,7 +20,9 @@ from euporie.core.convert.formats import BASE64_FORMATS
22
20
  from euporie.core.convert.mime import MIME_FORMATS
23
21
  from euporie.core.convert.registry import find_route
24
22
  from euporie.core.current import get_app
23
+ from euporie.core.layout.containers import HSplit
25
24
  from euporie.core.widgets.display import Display
25
+ from euporie.core.widgets.layout import Box
26
26
  from euporie.core.widgets.tree import JsonView
27
27
 
28
28
  if TYPE_CHECKING: