euporie 2.8.3__py3-none-any.whl → 2.8.4__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.
- euporie/console/tabs/console.py +227 -104
- euporie/core/__init__.py +1 -1
- euporie/core/__main__.py +1 -1
- euporie/core/app.py +20 -18
- euporie/core/clipboard.py +1 -1
- euporie/core/comm/ipywidgets.py +5 -5
- euporie/core/commands.py +1 -1
- euporie/core/config.py +4 -4
- euporie/core/convert/datum.py +4 -1
- euporie/core/convert/registry.py +7 -2
- euporie/core/filters.py +3 -1
- euporie/core/ft/html.py +2 -4
- euporie/core/graphics.py +6 -6
- euporie/core/kernel.py +56 -32
- euporie/core/key_binding/bindings/__init__.py +2 -1
- euporie/core/key_binding/bindings/mouse.py +24 -22
- euporie/core/key_binding/bindings/vi.py +46 -0
- euporie/core/layout/cache.py +33 -23
- euporie/core/layout/containers.py +235 -73
- euporie/core/layout/decor.py +3 -3
- euporie/core/layout/print.py +14 -2
- euporie/core/layout/scroll.py +15 -21
- euporie/core/margins.py +59 -30
- euporie/core/style.py +7 -5
- euporie/core/tabs/base.py +32 -0
- euporie/core/tabs/notebook.py +6 -3
- euporie/core/terminal.py +12 -17
- euporie/core/utils.py +2 -4
- euporie/core/widgets/cell.py +64 -109
- euporie/core/widgets/dialog.py +25 -20
- euporie/core/widgets/file_browser.py +3 -3
- euporie/core/widgets/forms.py +8 -7
- euporie/core/widgets/layout.py +5 -5
- euporie/core/widgets/status.py +3 -3
- euporie/hub/app.py +7 -3
- euporie/notebook/app.py +59 -46
- euporie/notebook/tabs/log.py +1 -1
- euporie/notebook/tabs/notebook.py +5 -3
- euporie/preview/app.py +3 -0
- euporie/preview/tabs/notebook.py +9 -14
- euporie/web/tabs/web.py +0 -1
- {euporie-2.8.3.dist-info → euporie-2.8.4.dist-info}/METADATA +5 -5
- {euporie-2.8.3.dist-info → euporie-2.8.4.dist-info}/RECORD +48 -47
- {euporie-2.8.3.data → euporie-2.8.4.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.3.data → euporie-2.8.4.data}/data/share/applications/euporie-notebook.desktop +0 -0
- {euporie-2.8.3.dist-info → euporie-2.8.4.dist-info}/WHEEL +0 -0
- {euporie-2.8.3.dist-info → euporie-2.8.4.dist-info}/entry_points.txt +0 -0
- {euporie-2.8.3.dist-info → euporie-2.8.4.dist-info}/licenses/LICENSE +0 -0
euporie/core/convert/datum.py
CHANGED
@@ -153,7 +153,10 @@ class Datum(Generic[T], metaclass=_MetaDatum):
|
|
153
153
|
for key, (datum_ref, _size) in list(size_instances.items()):
|
154
154
|
datum = datum_ref()
|
155
155
|
if not datum or datum.hash == data_hash:
|
156
|
-
|
156
|
+
try:
|
157
|
+
del size_instances[key]
|
158
|
+
except KeyError:
|
159
|
+
pass
|
157
160
|
del datum
|
158
161
|
|
159
162
|
def to_bytes(self) -> bytes:
|
euporie/core/convert/registry.py
CHANGED
@@ -58,7 +58,7 @@ def register(
|
|
58
58
|
return decorator
|
59
59
|
|
60
60
|
|
61
|
-
def
|
61
|
+
def _find_route(from_: str, to: str) -> list | None:
|
62
62
|
"""Find the shortest conversion path between two formats."""
|
63
63
|
if from_ == to:
|
64
64
|
return [from_]
|
@@ -100,5 +100,10 @@ def find_route(from_: str, to: str) -> list | None:
|
|
100
100
|
|
101
101
|
|
102
102
|
_CONVERTOR_ROUTE_CACHE: FastDictCache[tuple[str, str], list | None] = FastDictCache(
|
103
|
-
|
103
|
+
_find_route
|
104
104
|
)
|
105
|
+
|
106
|
+
|
107
|
+
def find_route(from_: str, to: str) -> list | None:
|
108
|
+
"""Find and cache conversion routes."""
|
109
|
+
return _CONVERTOR_ROUTE_CACHE[from_, to]
|
euporie/core/filters.py
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
5
|
import os
|
6
|
-
from functools import partial, reduce
|
6
|
+
from functools import lru_cache, partial, reduce
|
7
7
|
from importlib import import_module
|
8
8
|
from shutil import which
|
9
9
|
from typing import TYPE_CHECKING
|
@@ -27,6 +27,7 @@ if TYPE_CHECKING:
|
|
27
27
|
from prompt_toolkit.layout.containers import Window
|
28
28
|
|
29
29
|
|
30
|
+
@lru_cache(maxsize=None)
|
30
31
|
def command_exists(*cmds: str) -> Filter:
|
31
32
|
"""Verify a list of external commands exist on the system."""
|
32
33
|
filters = [
|
@@ -36,6 +37,7 @@ def command_exists(*cmds: str) -> Filter:
|
|
36
37
|
return reduce(lambda a, b: a & b, filters, to_filter(True))
|
37
38
|
|
38
39
|
|
40
|
+
@lru_cache(maxsize=None)
|
39
41
|
def have_modules(*modules: str) -> Filter:
|
40
42
|
"""Verify a list of python modules are importable."""
|
41
43
|
|
euporie/core/ft/html.py
CHANGED
@@ -4318,14 +4318,12 @@ class HTML:
|
|
4318
4318
|
@overload
|
4319
4319
|
async def _render_image(
|
4320
4320
|
self, data: bytes, format_: str, theme: Theme, path: Path | None = None
|
4321
|
-
) -> StyleAndTextTuples:
|
4322
|
-
...
|
4321
|
+
) -> StyleAndTextTuples: ...
|
4323
4322
|
|
4324
4323
|
@overload
|
4325
4324
|
async def _render_image(
|
4326
4325
|
self, data: str, format_: str, theme: Theme, path: Path | None = None
|
4327
|
-
) -> StyleAndTextTuples:
|
4328
|
-
...
|
4326
|
+
) -> StyleAndTextTuples: ...
|
4329
4327
|
|
4330
4328
|
async def _render_image(self, data, format_, theme, path=None):
|
4331
4329
|
"""Render an image and prepare graphic representation."""
|
euporie/core/graphics.py
CHANGED
@@ -335,9 +335,9 @@ class KittyGraphicControl(GraphicControl):
|
|
335
335
|
super().__init__(datum=datum, scale=scale, bbox=bbox)
|
336
336
|
self.kitty_image_id = 0
|
337
337
|
self.loaded = False
|
338
|
-
self._datum_pad_cache: FastDictCache[
|
339
|
-
|
340
|
-
|
338
|
+
self._datum_pad_cache: FastDictCache[tuple[Datum, int, int], Datum] = (
|
339
|
+
FastDictCache(get_value=self._pad_datum, size=1)
|
340
|
+
)
|
341
341
|
|
342
342
|
def _pad_datum(self, datum: Datum, cell_size_x: int, cell_size_y: int) -> Datum:
|
343
343
|
from PIL import ImageOps
|
@@ -704,9 +704,9 @@ class GraphicProcessor:
|
|
704
704
|
self.control = control
|
705
705
|
|
706
706
|
self.positions: dict[str, Point] = {}
|
707
|
-
self._position_cache: FastDictCache[
|
708
|
-
|
709
|
-
|
707
|
+
self._position_cache: FastDictCache[tuple[UIContent], dict[str, Point]] = (
|
708
|
+
FastDictCache(self._load_positions, size=1_000)
|
709
|
+
)
|
710
710
|
self._float_cache: FastDictCache[tuple[str], Float | None] = FastDictCache(
|
711
711
|
self.get_graphic_float, size=1_000
|
712
712
|
)
|
euporie/core/kernel.py
CHANGED
@@ -9,13 +9,13 @@ import os
|
|
9
9
|
import re
|
10
10
|
import sys
|
11
11
|
import threading
|
12
|
+
from _frozen_importlib import _DeadlockError
|
12
13
|
from collections import defaultdict
|
13
14
|
from subprocess import PIPE, STDOUT # S404 - Security implications considered
|
14
15
|
from typing import TYPE_CHECKING, TypedDict
|
15
16
|
from uuid import uuid4
|
16
17
|
|
17
18
|
import nbformat
|
18
|
-
from _frozen_importlib import _DeadlockError
|
19
19
|
from jupyter_client import AsyncKernelManager, KernelManager
|
20
20
|
from jupyter_client.kernelspec import NATIVE_KERNEL_NAME, NoSuchKernel
|
21
21
|
from jupyter_client.provisioning import KernelProvisionerFactory as KPF
|
@@ -130,7 +130,8 @@ class MsgCallbacks(TypedDict, total=False):
|
|
130
130
|
|
131
131
|
get_input: Callable[[str, bool], None] | None
|
132
132
|
set_execution_count: Callable[[int], None] | None
|
133
|
-
add_output: Callable[[dict[str, Any]], None] | None
|
133
|
+
add_output: Callable[[dict[str, Any], bool], None] | None
|
134
|
+
add_input: Callable[[dict[str, Any], bool], None] | None
|
134
135
|
clear_output: Callable[[bool], None] | None
|
135
136
|
done: Callable[[dict[str, Any]], None] | None
|
136
137
|
set_metadata: Callable[[tuple[str, ...], Any], None] | None
|
@@ -151,6 +152,8 @@ class Kernel:
|
|
151
152
|
Has the ability to run itself in it's own thread.
|
152
153
|
"""
|
153
154
|
|
155
|
+
_CLIENT_ID = f"euporie-{os.getpid()}"
|
156
|
+
|
154
157
|
def __init__(
|
155
158
|
self,
|
156
159
|
kernel_tab: KernelTab,
|
@@ -457,6 +460,9 @@ class Kernel:
|
|
457
460
|
]
|
458
461
|
self.dead = False
|
459
462
|
|
463
|
+
# Set username so we can identify our own messages
|
464
|
+
self.kc.session.username = self._CLIENT_ID
|
465
|
+
|
460
466
|
# Start monitoring the kernel status
|
461
467
|
if self.monitor_task is not None:
|
462
468
|
self.monitor_task.cancel()
|
@@ -507,22 +513,24 @@ class Kernel:
|
|
507
513
|
rsp = await msg_getter_coro()
|
508
514
|
# Run msg type handler
|
509
515
|
msg_type = rsp.get("header", {}).get("msg_type")
|
516
|
+
own = rsp.get("parent_header", {}).get("username") == self._CLIENT_ID
|
510
517
|
if callable(handler := getattr(self, f"on_{channel}_{msg_type}", None)):
|
511
|
-
handler(rsp)
|
518
|
+
handler(rsp, own)
|
512
519
|
else:
|
513
|
-
self.on_unhandled(channel, rsp)
|
520
|
+
self.on_unhandled(channel, rsp, own)
|
514
521
|
|
515
|
-
def on_unhandled(self, channel: str, rsp: dict[str, Any]) -> None:
|
522
|
+
def on_unhandled(self, channel: str, rsp: dict[str, Any], own: bool) -> None:
|
516
523
|
"""Report unhandled messages to the debug log."""
|
517
524
|
log.debug(
|
518
|
-
"Unhandled %s message:\nparent_id = '%s'\ntype = '%s'\ncontent='%s'",
|
525
|
+
"Unhandled %s message:\nparent_id = '%s'\ntype = '%s'\ncontent='%s'\nown: %s",
|
519
526
|
channel,
|
520
527
|
rsp.get("parent_header", {}).get("msg_id"),
|
521
528
|
rsp["header"]["msg_type"],
|
522
529
|
rsp.get("content"),
|
530
|
+
own,
|
523
531
|
)
|
524
532
|
|
525
|
-
def on_stdin_input_request(self, rsp: dict[str, Any]) -> None:
|
533
|
+
def on_stdin_input_request(self, rsp: dict[str, Any], own: bool) -> None:
|
526
534
|
"""Call ``get_input`` callback for a stdin input request message."""
|
527
535
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
528
536
|
content = rsp.get("content", {})
|
@@ -532,7 +540,7 @@ class Kernel:
|
|
532
540
|
content.get("password", False),
|
533
541
|
)
|
534
542
|
|
535
|
-
def on_shell_status(self, rsp: dict[str, Any]) -> None:
|
543
|
+
def on_shell_status(self, rsp: dict[str, Any], own: bool) -> None:
|
536
544
|
"""Call ``set_execution_count`` callback for a shell status response."""
|
537
545
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
538
546
|
content = rsp.get("content", {})
|
@@ -548,7 +556,7 @@ class Kernel:
|
|
548
556
|
):
|
549
557
|
set_execution_count(execution_count)
|
550
558
|
|
551
|
-
def on_shell_execute_reply(self, rsp: dict[str, Any]) -> None:
|
559
|
+
def on_shell_execute_reply(self, rsp: dict[str, Any], own: bool) -> None:
|
552
560
|
"""Call callbacks for a shell execute reply response."""
|
553
561
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
554
562
|
content = rsp.get("content", {})
|
@@ -579,7 +587,8 @@ class Kernel:
|
|
579
587
|
nbformat.v4.new_output(
|
580
588
|
"execute_result",
|
581
589
|
data=data,
|
582
|
-
)
|
590
|
+
),
|
591
|
+
own,
|
583
592
|
)
|
584
593
|
elif source == "set_next_input":
|
585
594
|
if callable(
|
@@ -605,7 +614,7 @@ class Kernel:
|
|
605
614
|
):
|
606
615
|
done(content)
|
607
616
|
|
608
|
-
def on_shell_kernel_info_reply(self, rsp: dict[str, Any]) -> None:
|
617
|
+
def on_shell_kernel_info_reply(self, rsp: dict[str, Any], own: bool) -> None:
|
609
618
|
"""Call callbacks for a shell kernel info response."""
|
610
619
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
611
620
|
if callable(
|
@@ -613,25 +622,25 @@ class Kernel:
|
|
613
622
|
):
|
614
623
|
set_kernel_info(rsp.get("content", {}))
|
615
624
|
|
616
|
-
def on_shell_complete_reply(self, rsp: dict[str, Any]) -> None:
|
625
|
+
def on_shell_complete_reply(self, rsp: dict[str, Any], own: bool) -> None:
|
617
626
|
"""Call callbacks for a shell completion reply response."""
|
618
627
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
619
628
|
if callable(done := self.msg_id_callbacks[msg_id].get("done")):
|
620
629
|
done(rsp.get("content", {}))
|
621
630
|
|
622
|
-
def on_shell_history_reply(self, rsp: dict[str, Any]) -> None:
|
631
|
+
def on_shell_history_reply(self, rsp: dict[str, Any], own: bool) -> None:
|
623
632
|
"""Call callbacks for a shell history reply response."""
|
624
633
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
625
634
|
if callable(done := self.msg_id_callbacks[msg_id].get("done")):
|
626
635
|
done(rsp.get("content", {}))
|
627
636
|
|
628
|
-
def on_shell_inspect_reply(self, rsp: dict[str, Any]) -> None:
|
637
|
+
def on_shell_inspect_reply(self, rsp: dict[str, Any], own: bool) -> None:
|
629
638
|
"""Call callbacks for a shell inspection reply response."""
|
630
639
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
631
640
|
if callable(done := self.msg_id_callbacks[msg_id].get("done")):
|
632
641
|
done(rsp.get("content", {}))
|
633
642
|
|
634
|
-
def on_shell_is_complete_reply(self, rsp: dict[str, Any]) -> None:
|
643
|
+
def on_shell_is_complete_reply(self, rsp: dict[str, Any], own: bool) -> None:
|
635
644
|
"""Call callbacks for a shell completeness reply response."""
|
636
645
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
637
646
|
if callable(
|
@@ -641,7 +650,7 @@ class Kernel:
|
|
641
650
|
):
|
642
651
|
completeness_status(rsp.get("content", {}))
|
643
652
|
|
644
|
-
def on_iopub_status(self, rsp: dict[str, Any]) -> None:
|
653
|
+
def on_iopub_status(self, rsp: dict[str, Any], own: bool) -> None:
|
645
654
|
"""Call callbacks for an iopub status response."""
|
646
655
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
647
656
|
status = rsp.get("content", {}).get("execution_state")
|
@@ -670,9 +679,11 @@ class Kernel:
|
|
670
679
|
rsp["header"]["date"].isoformat(),
|
671
680
|
)
|
672
681
|
|
673
|
-
def on_iopub_execute_input(self, rsp: dict[str, Any]) -> None:
|
682
|
+
def on_iopub_execute_input(self, rsp: dict[str, Any], own: bool) -> None:
|
674
683
|
"""Call callbacks for an iopub execute input response."""
|
675
684
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
685
|
+
content = rsp.get("content", {})
|
686
|
+
|
676
687
|
if self.kernel_tab.app.config.record_cell_timing and callable(
|
677
688
|
set_metadata := self.msg_id_callbacks[msg_id]["set_metadata"]
|
678
689
|
):
|
@@ -681,23 +692,36 @@ class Kernel:
|
|
681
692
|
rsp["header"]["date"].isoformat(),
|
682
693
|
)
|
683
694
|
|
684
|
-
|
695
|
+
execution_count: int | None = None
|
696
|
+
if (execution_count := content.get("execution_count")) and (
|
697
|
+
callable(
|
698
|
+
set_execution_count := self.msg_id_callbacks[msg_id][
|
699
|
+
"set_execution_count"
|
700
|
+
]
|
701
|
+
)
|
702
|
+
):
|
703
|
+
set_execution_count(execution_count)
|
704
|
+
|
705
|
+
if callable(add_input := self.msg_id_callbacks[msg_id].get("add_input")):
|
706
|
+
add_input(content, own)
|
707
|
+
|
708
|
+
def on_iopub_display_data(self, rsp: dict[str, Any], own: bool) -> None:
|
685
709
|
"""Call callbacks for an iopub display data response."""
|
686
710
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
687
711
|
if callable(add_output := self.msg_id_callbacks[msg_id]["add_output"]):
|
688
|
-
add_output(nbformat.v4.output_from_msg(rsp))
|
712
|
+
add_output(nbformat.v4.output_from_msg(rsp), own)
|
689
713
|
|
690
|
-
def on_iopub_update_display_data(self, rsp: dict[str, Any]) -> None:
|
714
|
+
def on_iopub_update_display_data(self, rsp: dict[str, Any], own: bool) -> None:
|
691
715
|
"""Call callbacks for an iopub update display data response."""
|
692
716
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
693
717
|
if callable(add_output := self.msg_id_callbacks[msg_id]["add_output"]):
|
694
|
-
add_output(nbformat.v4.output_from_msg(rsp))
|
718
|
+
add_output(nbformat.v4.output_from_msg(rsp), own)
|
695
719
|
|
696
|
-
def on_iopub_execute_result(self, rsp: dict[str, Any]) -> None:
|
720
|
+
def on_iopub_execute_result(self, rsp: dict[str, Any], own: bool) -> None:
|
697
721
|
"""Call callbacks for an iopub execute result response."""
|
698
722
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
699
723
|
if callable(add_output := self.msg_id_callbacks[msg_id]["add_output"]):
|
700
|
-
add_output(nbformat.v4.output_from_msg(rsp))
|
724
|
+
add_output(nbformat.v4.output_from_msg(rsp), own)
|
701
725
|
|
702
726
|
if (execution_count := rsp.get("content", {}).get("execution_count")) and (
|
703
727
|
callable(
|
@@ -708,21 +732,21 @@ class Kernel:
|
|
708
732
|
):
|
709
733
|
set_execution_count(execution_count)
|
710
734
|
|
711
|
-
def on_iopub_error(self, rsp: dict[str,
|
735
|
+
def on_iopub_error(self, rsp: dict[str, Any], own: bool) -> None:
|
712
736
|
"""Call callbacks for an iopub error response."""
|
713
737
|
msg_id = rsp.get("parent_header", {}).get("msg_id", "")
|
714
738
|
if callable(add_output := self.msg_id_callbacks[msg_id].get("add_output")):
|
715
|
-
add_output(nbformat.v4.output_from_msg(rsp))
|
739
|
+
add_output(nbformat.v4.output_from_msg(rsp), own)
|
716
740
|
if callable(done := self.msg_id_callbacks[msg_id].get("done")):
|
717
741
|
done(rsp.get("content", {}))
|
718
742
|
|
719
|
-
def on_iopub_stream(self, rsp: dict[str, Any]) -> None:
|
743
|
+
def on_iopub_stream(self, rsp: dict[str, Any], own: bool) -> None:
|
720
744
|
"""Call callbacks for an iopub stream response."""
|
721
745
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
722
746
|
if callable(add_output := self.msg_id_callbacks[msg_id]["add_output"]):
|
723
|
-
add_output(nbformat.v4.output_from_msg(rsp))
|
747
|
+
add_output(nbformat.v4.output_from_msg(rsp), own)
|
724
748
|
|
725
|
-
def on_iopub_clear_output(self, rsp: dict[str, Any]) -> None:
|
749
|
+
def on_iopub_clear_output(self, rsp: dict[str, Any], own: bool) -> None:
|
726
750
|
"""Call callbacks for an iopub clear output response."""
|
727
751
|
# Clear cell output, either now or when we get the next output
|
728
752
|
msg_id = rsp.get("parent_header", {}).get("msg_id")
|
@@ -737,7 +761,7 @@ class Kernel:
|
|
737
761
|
)
|
738
762
|
'''
|
739
763
|
|
740
|
-
def on_iopub_comm_open(self, rsp: dict[str, Any]) -> None:
|
764
|
+
def on_iopub_comm_open(self, rsp: dict[str, Any], own: bool) -> None:
|
741
765
|
"""Call callbacks for an comm open response."""
|
742
766
|
# TODO
|
743
767
|
# "If the target_name key is not found on the receiving side, then it should
|
@@ -747,13 +771,13 @@ class Kernel:
|
|
747
771
|
content=rsp.get("content", {}), buffers=rsp.get("buffers", [])
|
748
772
|
)
|
749
773
|
|
750
|
-
def on_iopub_comm_msg(self, rsp: dict[str, Any]) -> None:
|
774
|
+
def on_iopub_comm_msg(self, rsp: dict[str, Any], own: bool) -> None:
|
751
775
|
"""Call callbacks for an iopub comm message response."""
|
752
776
|
self.kernel_tab.comm_msg(
|
753
777
|
content=rsp.get("content", {}), buffers=rsp.get("buffers", [])
|
754
778
|
)
|
755
779
|
|
756
|
-
def on_iopub_comm_close(self, rsp: dict[str, Any]) -> None:
|
780
|
+
def on_iopub_comm_close(self, rsp: dict[str, Any], own: bool) -> None:
|
757
781
|
"""Call callbacks for an iopub comm close response."""
|
758
782
|
self.kernel_tab.comm_close(
|
759
783
|
content=rsp.get("content", {}), buffers=rsp.get("buffers", [])
|
@@ -795,7 +819,7 @@ class Kernel:
|
|
795
819
|
source: str,
|
796
820
|
get_input: Callable[[str, bool], None] | None = None,
|
797
821
|
set_execution_count: Callable[[int], None] | None = None,
|
798
|
-
add_output: Callable[[dict[str, Any]], None] | None = None,
|
822
|
+
add_output: Callable[[dict[str, Any], bool], None] | None = None,
|
799
823
|
clear_output: Callable[[bool], None] | None = None,
|
800
824
|
done: Callable[[dict[str, Any]], None] | None = None,
|
801
825
|
set_metadata: Callable[[tuple[str, ...], Any], None] | None = None,
|
@@ -22,6 +22,7 @@ from prompt_toolkit.key_binding.bindings.mouse import (
|
|
22
22
|
from prompt_toolkit.keys import Keys
|
23
23
|
from prompt_toolkit.mouse_events import MouseButton, MouseEventType, MouseModifier
|
24
24
|
from prompt_toolkit.mouse_events import MouseEvent as PtkMouseEvent
|
25
|
+
from prompt_toolkit.renderer import HeightIsUnknownError
|
25
26
|
|
26
27
|
from euporie.core.app import BaseApp
|
27
28
|
|
@@ -102,29 +103,23 @@ def _parse_mouse_data(
|
|
102
103
|
# Parse event type.
|
103
104
|
if sgr:
|
104
105
|
if sgr_pixels:
|
105
|
-
#
|
106
|
+
# Scale down pixel-wise mouse position to cell based, and calculate
|
107
|
+
# relative position of mouse within the cell
|
106
108
|
cell_x, cell_y = cell_size_xy
|
107
109
|
px, py = x, y
|
108
110
|
fx, fy = px / cell_x + 1, py / cell_y + 1
|
109
111
|
x, y = int(fx), int(fy)
|
110
112
|
rx, ry = fx - x, fy - y
|
111
|
-
|
112
113
|
try:
|
113
|
-
(
|
114
|
-
|
115
|
-
|
116
|
-
mouse_modifiers,
|
117
|
-
) = xterm_sgr_mouse_events[mouse_event, m]
|
114
|
+
(mouse_button, mouse_event_type, mouse_modifiers) = (
|
115
|
+
xterm_sgr_mouse_events[mouse_event, m]
|
116
|
+
)
|
118
117
|
except KeyError:
|
119
118
|
return None
|
120
119
|
|
121
120
|
else:
|
122
121
|
# Some other terminals, like urxvt, Hyper terminal, ...
|
123
|
-
(
|
124
|
-
mouse_button,
|
125
|
-
mouse_event_type,
|
126
|
-
mouse_modifiers,
|
127
|
-
) = urxvt_mouse_events.get(
|
122
|
+
(mouse_button, mouse_event_type, mouse_modifiers) = urxvt_mouse_events.get(
|
128
123
|
mouse_event, (UNKNOWN_BUTTON, MOUSE_MOVE, UNKNOWN_MODIFIER)
|
129
124
|
)
|
130
125
|
|
@@ -153,9 +148,10 @@ def load_mouse_bindings() -> KeyBindings:
|
|
153
148
|
def _(event: KeyPressEvent) -> NotImplementedOrNone:
|
154
149
|
"""Handle incoming mouse event, include SGR-pixel mode."""
|
155
150
|
# Ensure mypy knows this would only run in a euporie appo
|
156
|
-
|
151
|
+
app = event.app
|
152
|
+
assert isinstance(app, BaseApp)
|
157
153
|
|
158
|
-
if not
|
154
|
+
if not app.renderer.height_is_known:
|
159
155
|
return NotImplemented
|
160
156
|
|
161
157
|
mouse_event = _MOUSE_EVENT_CACHE[
|
@@ -169,10 +165,9 @@ def load_mouse_bindings() -> KeyBindings:
|
|
169
165
|
if mouse_event.event_type is not None:
|
170
166
|
# Take region above the layout into account. The reported
|
171
167
|
# coordinates are absolute to the visible part of the terminal.
|
172
|
-
from prompt_toolkit.renderer import HeightIsUnknownError
|
173
|
-
|
174
168
|
x, y = mouse_event.position
|
175
169
|
|
170
|
+
# Adjust position to take into account space above non-full screen apps
|
176
171
|
try:
|
177
172
|
rows_above = app.renderer.rows_above_layout
|
178
173
|
except HeightIsUnknownError:
|
@@ -180,28 +175,35 @@ def load_mouse_bindings() -> KeyBindings:
|
|
180
175
|
else:
|
181
176
|
y -= rows_above
|
182
177
|
|
183
|
-
# Save
|
184
|
-
app.mouse_position =
|
178
|
+
# Save mouse position within the app
|
179
|
+
app.mouse_position = Point(x=x, y=y)
|
185
180
|
|
186
181
|
# Apply limits to mouse position if enabled
|
187
182
|
if (mouse_limits := app.mouse_limits) is not None:
|
188
183
|
x = max(
|
189
184
|
mouse_limits.xpos,
|
190
|
-
min(x, mouse_limits.xpos + (mouse_limits.width - 1)
|
185
|
+
min(x, mouse_limits.xpos + (mouse_limits.width) - 1),
|
191
186
|
)
|
192
187
|
y = max(
|
193
188
|
mouse_limits.ypos,
|
194
|
-
min(y, mouse_limits.ypos + (mouse_limits.height - 1)
|
189
|
+
min(y, mouse_limits.ypos + (mouse_limits.height) - 1),
|
195
190
|
)
|
196
191
|
|
197
|
-
|
192
|
+
# Do not modify the mouse event in the cache, instead create a new instance
|
193
|
+
mouse_event = MouseEvent(
|
194
|
+
position=Point(x=x, y=y),
|
195
|
+
event_type=mouse_event.event_type,
|
196
|
+
button=mouse_event.button,
|
197
|
+
modifiers=mouse_event.modifiers,
|
198
|
+
cell_position=mouse_event.cell_position,
|
199
|
+
)
|
198
200
|
|
199
201
|
# Call the mouse handler from the renderer.
|
200
202
|
# Note: This can return `NotImplemented` if no mouse handler was
|
201
203
|
# found for this position, or if no repainting needs to
|
202
204
|
# happen. this way, we avoid excessive repaints during mouse
|
203
205
|
# movements.
|
204
|
-
handler =
|
206
|
+
handler = app.renderer.mouse_handlers.mouse_handlers[y][x]
|
205
207
|
|
206
208
|
return handler(mouse_event)
|
207
209
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"""Add additional keys to the prompt_toolkit vi key-bindings."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
from typing import TYPE_CHECKING, cast
|
6
|
+
|
7
|
+
from prompt_toolkit.buffer import indent, unindent
|
8
|
+
from prompt_toolkit.filters.app import vi_insert_mode
|
9
|
+
from prompt_toolkit.key_binding.bindings.vi import (
|
10
|
+
load_vi_bindings as load_ptk_vi_bindings,
|
11
|
+
)
|
12
|
+
|
13
|
+
from euporie.core.filters import cursor_in_leading_ws
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from prompt_toolkit.key_binding.key_bindings import (
|
17
|
+
ConditionalKeyBindings,
|
18
|
+
KeyBindings,
|
19
|
+
KeyBindingsBase,
|
20
|
+
)
|
21
|
+
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
22
|
+
|
23
|
+
|
24
|
+
def load_vi_bindings() -> KeyBindingsBase:
|
25
|
+
"""Load vi keybindings from PTK, adding additional bindings."""
|
26
|
+
# We know the type of the vi bindings
|
27
|
+
vi_bindings = cast(
|
28
|
+
"KeyBindings",
|
29
|
+
cast("ConditionalKeyBindings", load_ptk_vi_bindings()).key_bindings,
|
30
|
+
)
|
31
|
+
handle = vi_bindings.add
|
32
|
+
|
33
|
+
@handle("c-i", filter=vi_insert_mode & cursor_in_leading_ws)
|
34
|
+
def _indent(event: KeyPressEvent) -> None:
|
35
|
+
"""Indent lines."""
|
36
|
+
buffer = event.current_buffer
|
37
|
+
current_row = buffer.document.cursor_position_row
|
38
|
+
indent(buffer, current_row, current_row + event.arg)
|
39
|
+
|
40
|
+
@handle("s-tab", filter=vi_insert_mode & cursor_in_leading_ws)
|
41
|
+
def _unindent(event: KeyPressEvent) -> None:
|
42
|
+
"""Unindent lines."""
|
43
|
+
current_row = event.current_buffer.document.cursor_position_row
|
44
|
+
unindent(event.current_buffer, current_row, current_row + event.arg)
|
45
|
+
|
46
|
+
return vi_bindings
|
euporie/core/layout/cache.py
CHANGED
@@ -256,7 +256,10 @@ class CachedContainer(Container):
|
|
256
256
|
cols: The columns to copy
|
257
257
|
rows: The rows to copy
|
258
258
|
.
|
259
|
-
"""
|
259
|
+
"""
|
260
|
+
# Copy write positions
|
261
|
+
new_wps = {}
|
262
|
+
|
260
263
|
for win, wp in self.screen.visible_windows_to_write_positions.items():
|
261
264
|
new_wp = BoundedWritePosition(
|
262
265
|
xpos=wp.xpos + left,
|
@@ -270,10 +273,9 @@ class CachedContainer(Container):
|
|
270
273
|
left=max(0, wp.width - (cols.stop - wp.xpos)),
|
271
274
|
),
|
272
275
|
)
|
273
|
-
|
274
|
-
screen.height = max(screen.height, self.screen.height)
|
276
|
+
new_wps[win] = new_wp
|
275
277
|
|
276
|
-
#
|
278
|
+
# Modify render info
|
277
279
|
info = win.render_info
|
278
280
|
if info is not None:
|
279
281
|
visible_line_to_row_col = {
|
@@ -308,7 +310,10 @@ class CachedContainer(Container):
|
|
308
310
|
win.render_info, "horizontal_scroll", horizontal_scroll
|
309
311
|
)
|
310
312
|
|
311
|
-
|
313
|
+
screen.visible_windows_to_write_positions.update(new_wps)
|
314
|
+
screen.height = max(screen.height, self.screen.height)
|
315
|
+
|
316
|
+
@lru_cache(maxsize=None)
|
312
317
|
def _wrap_mouse_handler(handler: Callable) -> MouseHandler:
|
313
318
|
def _wrapped(mouse_event: MouseEvent) -> NotImplementedOrNone:
|
314
319
|
# Modify mouse events to reflect position of content
|
@@ -334,14 +339,17 @@ class CachedContainer(Container):
|
|
334
339
|
output_db = screen.data_buffer
|
335
340
|
output_zwes = screen.zero_width_escapes
|
336
341
|
output_mhs = mouse_handlers.mouse_handlers
|
337
|
-
|
342
|
+
|
343
|
+
rows_range = range(max(0, rows.start), rows.stop)
|
344
|
+
cols_range = range(max(0, cols.start), cols.stop)
|
345
|
+
for y in rows_range:
|
338
346
|
input_db_row = input_db[y]
|
339
347
|
input_zwes_row = input_zwes[y]
|
340
348
|
input_mhs_row = input_mhs[y]
|
341
349
|
output_dbs_row = output_db[top + y]
|
342
350
|
output_zwes_row = output_zwes[top + y]
|
343
351
|
output_mhs_row = output_mhs[top + y]
|
344
|
-
for x in
|
352
|
+
for x in cols_range:
|
345
353
|
# Data
|
346
354
|
output_dbs_row[left + x] = input_db_row[x]
|
347
355
|
# Escape sequences
|
@@ -353,24 +361,26 @@ class CachedContainer(Container):
|
|
353
361
|
layout = get_app().layout
|
354
362
|
if self.screen.show_cursor:
|
355
363
|
for window, point in self.screen.cursor_positions.items():
|
356
|
-
if
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
)
|
369
|
-
screen.show_cursor = True
|
364
|
+
if (
|
365
|
+
layout.current_control == window.content
|
366
|
+
and window.render_info is not None
|
367
|
+
and window.render_info.ui_content.show_cursor
|
368
|
+
and not window.always_hide_cursor()
|
369
|
+
and cols.start <= point.x < cols.stop
|
370
|
+
and rows.start <= point.y < rows.stop
|
371
|
+
):
|
372
|
+
screen.cursor_positions[window] = Point(
|
373
|
+
x=left + point.x, y=top + point.y
|
374
|
+
)
|
375
|
+
screen.show_cursor = True
|
370
376
|
|
371
377
|
# Copy menu positions
|
372
|
-
|
373
|
-
|
378
|
+
screen.menu_positions.update(
|
379
|
+
{
|
380
|
+
window: Point(x=left + point.x, y=top + point.y)
|
381
|
+
for window, point in self.screen.menu_positions.items()
|
382
|
+
}
|
383
|
+
)
|
374
384
|
|
375
385
|
def get_children(self) -> list[Container]:
|
376
386
|
"""Return a list of all child containers."""
|