euporie 2.3.2__py3-none-any.whl → 2.4.1__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/__main__.py +3 -1
- euporie/console/app.py +6 -4
- euporie/console/tabs/console.py +34 -9
- euporie/core/__init__.py +6 -1
- euporie/core/__main__.py +1 -1
- euporie/core/app.py +79 -109
- euporie/core/border.py +44 -14
- euporie/core/comm/base.py +5 -4
- euporie/core/comm/ipywidgets.py +11 -11
- euporie/core/comm/registry.py +12 -6
- euporie/core/commands.py +30 -23
- euporie/core/completion.py +1 -4
- euporie/core/config.py +15 -5
- euporie/core/convert/{base.py → core.py} +117 -53
- euporie/core/convert/formats/ansi.py +46 -25
- euporie/core/convert/formats/base64.py +3 -3
- euporie/core/convert/formats/common.py +38 -13
- euporie/core/convert/formats/formatted_text.py +54 -12
- euporie/core/convert/formats/html.py +5 -5
- euporie/core/convert/formats/jpeg.py +1 -1
- euporie/core/convert/formats/markdown.py +4 -4
- euporie/core/convert/formats/pdf.py +1 -1
- euporie/core/convert/formats/pil.py +5 -3
- euporie/core/convert/formats/png.py +7 -6
- euporie/core/convert/formats/rich.py +4 -3
- euporie/core/convert/formats/sixel.py +5 -5
- euporie/core/convert/utils.py +1 -1
- euporie/core/current.py +11 -5
- euporie/core/formatted_text/ansi.py +4 -8
- euporie/core/formatted_text/html.py +1630 -856
- euporie/core/formatted_text/markdown.py +177 -166
- euporie/core/formatted_text/table.py +20 -14
- euporie/core/formatted_text/utils.py +21 -10
- euporie/core/io.py +14 -14
- euporie/core/kernel.py +48 -37
- euporie/core/key_binding/bindings/micro.py +5 -1
- euporie/core/key_binding/bindings/mouse.py +2 -2
- euporie/core/keys.py +3 -0
- euporie/core/launch.py +5 -2
- euporie/core/lexers.py +13 -2
- euporie/core/log.py +135 -139
- euporie/core/margins.py +32 -14
- euporie/core/path.py +273 -0
- euporie/core/processors.py +35 -0
- euporie/core/renderer.py +21 -5
- euporie/core/style.py +34 -19
- euporie/core/tabs/base.py +101 -17
- euporie/core/tabs/notebook.py +72 -30
- euporie/core/terminal.py +56 -48
- euporie/core/utils.py +12 -16
- euporie/core/widgets/cell.py +6 -5
- euporie/core/widgets/cell_outputs.py +2 -2
- euporie/core/widgets/decor.py +74 -82
- euporie/core/widgets/dialog.py +132 -28
- euporie/core/widgets/display.py +76 -24
- euporie/core/widgets/file_browser.py +87 -31
- euporie/core/widgets/formatted_text_area.py +1 -3
- euporie/core/widgets/forms.py +79 -40
- euporie/core/widgets/inputs.py +23 -13
- euporie/core/widgets/layout.py +4 -3
- euporie/core/widgets/menu.py +368 -216
- euporie/core/widgets/page.py +99 -58
- euporie/core/widgets/pager.py +1 -1
- euporie/core/widgets/palette.py +30 -27
- euporie/core/widgets/search_bar.py +38 -25
- euporie/core/widgets/status_bar.py +103 -5
- euporie/data/desktop/euporie-console.desktop +7 -0
- euporie/data/desktop/euporie-notebook.desktop +7 -0
- euporie/hub/__main__.py +3 -1
- euporie/hub/app.py +9 -7
- euporie/notebook/__main__.py +3 -1
- euporie/notebook/app.py +7 -30
- euporie/notebook/tabs/__init__.py +7 -3
- euporie/notebook/tabs/display.py +18 -9
- euporie/notebook/tabs/edit.py +106 -23
- euporie/notebook/tabs/json.py +73 -0
- euporie/notebook/tabs/log.py +18 -8
- euporie/notebook/tabs/notebook.py +60 -41
- euporie/preview/__main__.py +3 -1
- euporie/preview/app.py +2 -1
- euporie/preview/tabs/notebook.py +23 -10
- euporie/web/tabs/web.py +149 -0
- euporie/web/widgets/webview.py +563 -0
- euporie-2.4.1.data/data/share/applications/euporie-console.desktop +7 -0
- euporie-2.4.1.data/data/share/applications/euporie-notebook.desktop +7 -0
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/METADATA +6 -5
- euporie-2.4.1.dist-info/RECORD +129 -0
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/WHEEL +1 -1
- euporie/core/url.py +0 -64
- euporie-2.3.2.dist-info/RECORD +0 -122
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/entry_points.txt +0 -0
- {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/licenses/LICENSE +0 -0
euporie/hub/__main__.py
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
|
4
4
|
def main() -> "None":
|
5
5
|
"""Call the main entrypoint to the application."""
|
6
|
+
import sys
|
7
|
+
|
6
8
|
from euporie.core import __main__
|
7
9
|
|
8
|
-
__main__.main(__name__.
|
10
|
+
__main__.main(str(sys.modules[__name__].__package__).rpartition(".")[2])
|
9
11
|
|
10
12
|
|
11
13
|
if __name__ == "__main__":
|
euporie/hub/app.py
CHANGED
@@ -42,7 +42,7 @@ class EuporieSSHServer(asyncssh.SSHServer): # type: ignore
|
|
42
42
|
|
43
43
|
def begin_auth(self, username: str) -> bool | Awaitable[bool]:
|
44
44
|
"""Perform authentication in the SSH server."""
|
45
|
-
if self.app_cls.config.
|
45
|
+
if not self.app_cls.config.auth:
|
46
46
|
# No authentication.
|
47
47
|
return False
|
48
48
|
return super().begin_auth(username)
|
@@ -68,18 +68,20 @@ class HubApp(BaseApp):
|
|
68
68
|
@classmethod
|
69
69
|
def launch(cls) -> None:
|
70
70
|
"""Launch the HubApp SSH server."""
|
71
|
+
# Default logging configuration
|
72
|
+
setup_logs()
|
73
|
+
|
71
74
|
# Configure some setting defaults
|
72
75
|
cls.config.settings["log_file"].value = "-"
|
76
|
+
cls.config.settings["log_level"].value = "info"
|
73
77
|
cls.config.settings[
|
74
78
|
"log_config"
|
75
79
|
].value = '{"loggers": {"asyncssh": {"handlers":["stdout"], "level": "DEBUG"}}}'
|
76
80
|
|
77
81
|
# Load the app's configuration
|
78
82
|
cls.config.load(cls)
|
79
|
-
# Configure the logs
|
80
|
-
setup_logs(cls.config)
|
81
83
|
|
82
|
-
if cls.config.
|
84
|
+
if not cls.config.auth:
|
83
85
|
log.warning(
|
84
86
|
"This server has been configured without SSH authentication, "
|
85
87
|
"meaning anyone can connect"
|
@@ -188,11 +190,11 @@ class HubApp(BaseApp):
|
|
188
190
|
)
|
189
191
|
|
190
192
|
add_setting(
|
191
|
-
name="
|
192
|
-
flags=["--
|
193
|
+
name="auth",
|
194
|
+
flags=["--auth"],
|
193
195
|
type_=bool,
|
194
196
|
help_="Allow unauthenticated access to euporie hub",
|
195
|
-
default=
|
197
|
+
default=True,
|
196
198
|
description="""
|
197
199
|
When set, users will be able to access euporie hub without authentication.
|
198
200
|
|
euporie/notebook/__main__.py
CHANGED
@@ -3,9 +3,11 @@
|
|
3
3
|
|
4
4
|
def main() -> "None":
|
5
5
|
"""Call the main entrypoint to the application."""
|
6
|
+
import sys
|
7
|
+
|
6
8
|
from euporie.core import __main__
|
7
9
|
|
8
|
-
__main__.main(__name__.
|
10
|
+
__main__.main(str(sys.modules[__name__].__package__).rpartition(".")[2])
|
9
11
|
|
10
12
|
|
11
13
|
if __name__ == "__main__":
|
euporie/notebook/app.py
CHANGED
@@ -25,6 +25,7 @@ from euporie.core.app import BaseApp
|
|
25
25
|
from euporie.core.commands import add_cmd, get_cmd
|
26
26
|
from euporie.core.config import add_setting
|
27
27
|
from euporie.core.key_binding.registry import register_bindings
|
28
|
+
from euporie.core.tabs.base import Tab
|
28
29
|
from euporie.core.widgets.decor import Pattern
|
29
30
|
from euporie.core.widgets.dialog import (
|
30
31
|
AboutDialog,
|
@@ -55,10 +56,9 @@ if TYPE_CHECKING:
|
|
55
56
|
|
56
57
|
from prompt_toolkit.formatted_text import StyleAndTextTuples
|
57
58
|
from prompt_toolkit.layout.containers import AnyContainer, Float
|
58
|
-
from upath import UPath
|
59
59
|
|
60
|
-
from euporie.core.tabs.base import Tab
|
61
60
|
from euporie.core.widgets.cell import Cell
|
61
|
+
from euporie.core.widgets.status_bar import StatusBarFields
|
62
62
|
|
63
63
|
log = logging.getLogger(__name__)
|
64
64
|
|
@@ -81,16 +81,14 @@ class NotebookApp(BaseApp):
|
|
81
81
|
kwargs.setdefault("full_screen", True)
|
82
82
|
kwargs.setdefault("leave_graphics", False)
|
83
83
|
super().__init__(**kwargs)
|
84
|
-
self.search_bar = SearchBar()
|
85
84
|
self.bindings_to_load.append("euporie.notebook.app.NotebookApp")
|
86
|
-
self.pre_run_callables.append(self.load_default_statusbar_fields)
|
87
85
|
|
88
86
|
# Register config hooks
|
89
87
|
self.config.get_item("show_cell_borders").event += lambda x: self.refresh()
|
90
88
|
|
91
|
-
def
|
89
|
+
def statusbar_defaults(self) -> StatusBarFields | None:
|
92
90
|
"""Load the default statusbar fields (run after keybindings are loaded)."""
|
93
|
-
|
91
|
+
return (
|
94
92
|
[
|
95
93
|
[
|
96
94
|
("", "Press "),
|
@@ -101,26 +99,6 @@ class NotebookApp(BaseApp):
|
|
101
99
|
[[("", "Press "), ("bold", get_cmd("quit").key_str()), ("", " to quit")]],
|
102
100
|
)
|
103
101
|
|
104
|
-
def get_file_tab(self, path: UPath) -> type[Tab]:
|
105
|
-
"""Return the tab to use for a file path."""
|
106
|
-
if path.suffix == ".ipynb":
|
107
|
-
return Notebook
|
108
|
-
else:
|
109
|
-
import mimetypes
|
110
|
-
|
111
|
-
from euporie.core.convert.base import MIME_FORMATS
|
112
|
-
|
113
|
-
mime, _ = mimetypes.guess_type(path)
|
114
|
-
log.debug("File %s has mime type: %s", path, mime)
|
115
|
-
if (mime or "").startswith("text/") and mime not in MIME_FORMATS:
|
116
|
-
from euporie.notebook.tabs.edit import EditorTab
|
117
|
-
|
118
|
-
return EditorTab
|
119
|
-
else:
|
120
|
-
from euporie.notebook.tabs.display import DisplayTab
|
121
|
-
|
122
|
-
return DisplayTab
|
123
|
-
|
124
102
|
async def _poll_terminal_colors(self) -> None:
|
125
103
|
"""Repeatedly query the terminal for its background and foreground colours."""
|
126
104
|
while self.config.terminal_polling_interval:
|
@@ -221,8 +199,7 @@ class NotebookApp(BaseApp):
|
|
221
199
|
)
|
222
200
|
|
223
201
|
self.pager = Pager()
|
224
|
-
|
225
|
-
assert self.search_bar is not None
|
202
|
+
self.search_bar = SearchBar()
|
226
203
|
|
227
204
|
self.dialogs["command-palette"] = CommandPalette(self)
|
228
205
|
self.dialogs["about"] = AboutDialog(self)
|
@@ -282,7 +259,7 @@ class NotebookApp(BaseApp):
|
|
282
259
|
height=Dimension(min=1),
|
283
260
|
),
|
284
261
|
self.search_bar,
|
285
|
-
StatusBar(),
|
262
|
+
StatusBar(default=self.statusbar_defaults()),
|
286
263
|
],
|
287
264
|
style="class:body",
|
288
265
|
),
|
@@ -385,7 +362,7 @@ class NotebookApp(BaseApp):
|
|
385
362
|
get_cmd("new-notebook").menu,
|
386
363
|
get_cmd("open-file").menu,
|
387
364
|
separator,
|
388
|
-
get_cmd("save-
|
365
|
+
get_cmd("save-file").menu,
|
389
366
|
get_cmd("save-as").menu,
|
390
367
|
get_cmd("close-tab").menu,
|
391
368
|
separator,
|
@@ -1,6 +1,10 @@
|
|
1
1
|
"""Tab for use in euporie notebook editor."""
|
2
2
|
|
3
|
-
from .
|
4
|
-
from .notebook import
|
3
|
+
from euporie.notebook.tabs.display import DisplayTab
|
4
|
+
from euporie.notebook.tabs.edit import EditorTab
|
5
|
+
from euporie.notebook.tabs.json import JsonTab
|
6
|
+
from euporie.notebook.tabs.log import LogView
|
7
|
+
from euporie.notebook.tabs.notebook import Notebook
|
8
|
+
from euporie.web.tabs.web import WebTab
|
5
9
|
|
6
|
-
__all__ = ["LogView", "Notebook"]
|
10
|
+
__all__ = ["DisplayTab", "EditorTab", "JsonTab", "LogView", "Notebook", "WebTab"]
|
euporie/notebook/tabs/display.py
CHANGED
@@ -5,22 +5,22 @@ from __future__ import annotations
|
|
5
5
|
import logging
|
6
6
|
from typing import TYPE_CHECKING
|
7
7
|
|
8
|
+
from prompt_toolkit.eventloop.utils import run_in_executor_with_context
|
8
9
|
from prompt_toolkit.layout.containers import VSplit
|
9
10
|
from prompt_toolkit.layout.dimension import Dimension
|
10
11
|
|
11
|
-
from euporie.core.convert.
|
12
|
+
from euporie.core.convert.core import MIME_FORMATS, get_format
|
12
13
|
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
13
14
|
from euporie.core.tabs.base import Tab
|
14
15
|
from euporie.core.widgets.display import Display
|
15
16
|
|
16
17
|
if TYPE_CHECKING:
|
17
|
-
from
|
18
|
+
from pathlib import Path
|
18
19
|
|
19
|
-
from prompt_toolkit.formatted_text import AnyFormattedText
|
20
20
|
from prompt_toolkit.layout.containers import AnyContainer
|
21
|
-
from upath import UPath
|
22
21
|
|
23
22
|
from euporie.core.app import BaseApp
|
23
|
+
from euporie.core.widgets.status_bar import StatusBarFields
|
24
24
|
|
25
25
|
log = logging.getLogger(__name__)
|
26
26
|
|
@@ -28,15 +28,24 @@ log = logging.getLogger(__name__)
|
|
28
28
|
class DisplayTab(Tab):
|
29
29
|
"""Tab class for displaying files."""
|
30
30
|
|
31
|
-
|
31
|
+
name = "File Viewer"
|
32
|
+
mime_types = set(MIME_FORMATS.keys())
|
33
|
+
|
34
|
+
def __init__(self, app: BaseApp, path: Path | None = None) -> None:
|
32
35
|
"""Call when the tab is created."""
|
33
36
|
super().__init__(app, path)
|
37
|
+
|
38
|
+
# Load file and container in background
|
34
39
|
if self.path is not None:
|
35
|
-
self.container = self.load_container()
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
41
|
+
def _load() -> None:
|
42
|
+
self.container = self.load_container()
|
43
|
+
self.app.layout.focus(self.container)
|
44
|
+
self.app.invalidate()
|
45
|
+
|
46
|
+
run_in_executor_with_context(_load)
|
47
|
+
|
48
|
+
def __pt_status__(self) -> StatusBarFields | None:
|
40
49
|
"""Return a list of statusbar field values shown then this tab is active."""
|
41
50
|
return ([str(self.path)], [])
|
42
51
|
|
euporie/notebook/tabs/edit.py
CHANGED
@@ -8,22 +8,23 @@ from typing import TYPE_CHECKING
|
|
8
8
|
from prompt_toolkit.layout.containers import VSplit
|
9
9
|
from prompt_toolkit.layout.dimension import Dimension
|
10
10
|
|
11
|
+
from euporie.core.filters import insert_mode, replace_mode
|
11
12
|
from euporie.core.kernel import Kernel, MsgCallbacks
|
13
|
+
from euporie.core.key_binding.registry import load_registered_bindings
|
12
14
|
from euporie.core.lexers import detect_lexer
|
13
|
-
|
14
|
-
# from euporie.core.margins import MarginContainer
|
15
|
+
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
15
16
|
from euporie.core.tabs.base import KernelTab
|
16
17
|
from euporie.core.widgets.inputs import KernelInput
|
17
18
|
|
18
19
|
if TYPE_CHECKING:
|
19
|
-
from
|
20
|
+
from pathlib import Path
|
21
|
+
from typing import Callable
|
20
22
|
|
21
|
-
from prompt_toolkit.formatted_text import AnyFormattedText
|
22
23
|
from prompt_toolkit.layout.containers import AnyContainer
|
23
|
-
from upath import UPath
|
24
24
|
|
25
25
|
from euporie.core.app import BaseApp
|
26
26
|
from euporie.core.comm.base import Comm
|
27
|
+
from euporie.core.widgets.status_bar import StatusBarFields
|
27
28
|
|
28
29
|
log = logging.getLogger(__name__)
|
29
30
|
|
@@ -31,61 +32,143 @@ log = logging.getLogger(__name__)
|
|
31
32
|
class EditorTab(KernelTab):
|
32
33
|
"""Tab class for editing text files."""
|
33
34
|
|
35
|
+
name = "Text Editor"
|
36
|
+
weight = 1
|
37
|
+
mime_types = {"text/*"}
|
38
|
+
|
34
39
|
allow_stdin = True
|
35
40
|
_metadata = {}
|
36
41
|
|
37
42
|
def __init__(
|
38
43
|
self,
|
39
44
|
app: BaseApp,
|
40
|
-
path:
|
45
|
+
path: Path | None = None,
|
41
46
|
kernel: Kernel | None = None,
|
42
47
|
comms: dict[str, Comm] | None = None,
|
43
48
|
use_kernel_history: bool = False,
|
44
49
|
) -> None:
|
45
50
|
"""Call when the tab is created."""
|
46
51
|
self.default_callbacks = MsgCallbacks({})
|
52
|
+
self._metadata = {}
|
53
|
+
self.loaded = False
|
54
|
+
|
47
55
|
super().__init__(app, path, kernel, comms, use_kernel_history)
|
48
56
|
|
49
|
-
|
57
|
+
def post_init_kernel(self) -> None:
|
58
|
+
"""Load UI and file in background after kernel has inited."""
|
59
|
+
# Load the UI
|
60
|
+
self.container = self.load_container()
|
61
|
+
self.app.layout.focus(self.container)
|
62
|
+
|
63
|
+
# Read file
|
64
|
+
self.load()
|
65
|
+
|
66
|
+
def load(self) -> None:
|
67
|
+
"""Load the text file."""
|
50
68
|
if self.path is not None:
|
51
69
|
text = self.path.read_text()
|
52
70
|
else:
|
53
71
|
text = ""
|
54
72
|
|
55
|
-
#
|
56
|
-
|
57
|
-
self.
|
73
|
+
# Set text
|
74
|
+
self.input_box.text = text
|
75
|
+
self.input_box.buffer.on_text_changed += lambda b: setattr(self, "dirty", True)
|
76
|
+
self.input_box.read_only = False
|
77
|
+
self.loaded = True
|
58
78
|
|
59
|
-
#
|
60
|
-
|
79
|
+
# Detect language
|
80
|
+
lexer = detect_lexer(text[:1000], self.path)
|
81
|
+
if lexer:
|
82
|
+
self._metadata = {"kernelspec": {"language": lexer.name}}
|
83
|
+
# Re-lex the file
|
84
|
+
self.input_box.control._fragment_cache.clear()
|
85
|
+
self.app.invalidate()
|
61
86
|
|
62
|
-
|
87
|
+
@property
|
88
|
+
def position(self) -> str:
|
89
|
+
"""Return the position of the cursor in the document."""
|
90
|
+
doc = self.input_box.buffer.document
|
91
|
+
return f"{doc.cursor_position_row + 1}:{doc.cursor_position_col}"
|
63
92
|
|
64
|
-
def
|
65
|
-
self,
|
66
|
-
) -> tuple[Sequence[AnyFormattedText], Sequence[AnyFormattedText]]:
|
93
|
+
def __pt_status__(self) -> StatusBarFields | None:
|
67
94
|
"""Return a list of statusbar field values shown then this tab is active."""
|
68
|
-
|
95
|
+
if not self.loaded:
|
96
|
+
return (["Loading…"], [])
|
97
|
+
return (
|
98
|
+
[
|
99
|
+
("I" if insert_mode() else ("o" if replace_mode() else ">")),
|
100
|
+
self.position,
|
101
|
+
],
|
102
|
+
[str(self.path)],
|
103
|
+
)
|
69
104
|
|
70
105
|
@property
|
71
|
-
def
|
72
|
-
"""Return the
|
106
|
+
def path_name(self) -> str:
|
107
|
+
"""Return the path name."""
|
73
108
|
if self.path is not None:
|
74
|
-
return
|
109
|
+
return self.path.name
|
75
110
|
else:
|
76
|
-
return "
|
111
|
+
return "(New file)"
|
112
|
+
|
113
|
+
@property
|
114
|
+
def title(self) -> str:
|
115
|
+
"""Return the tab title."""
|
116
|
+
return ("* " if self.dirty else "") + self.path_name
|
77
117
|
|
78
118
|
def load_container(self) -> AnyContainer:
|
79
119
|
"""Abcract method for loading the notebook's main container."""
|
80
120
|
assert self.path is not None
|
81
121
|
|
82
|
-
self.input_box = KernelInput(kernel_tab=self)
|
122
|
+
self.input_box = KernelInput(kernel_tab=self, right_margins=[], read_only=True)
|
83
123
|
|
84
124
|
return VSplit(
|
85
125
|
[
|
86
126
|
self.input_box,
|
87
|
-
|
127
|
+
MarginContainer(ScrollbarMargin(), target=self.input_box.window),
|
88
128
|
],
|
89
129
|
width=Dimension(weight=1),
|
90
130
|
height=Dimension(weight=1),
|
131
|
+
key_bindings=load_registered_bindings("euporie.core.tabs.base.Tab"),
|
91
132
|
)
|
133
|
+
|
134
|
+
def save(self, path: Path | None = None, cb: Callable | None = None) -> None:
|
135
|
+
"""Save the current file."""
|
136
|
+
if path is not None:
|
137
|
+
self.path = path
|
138
|
+
|
139
|
+
if self.path is None:
|
140
|
+
if dialog := self.app.dialogs.get("save-as"):
|
141
|
+
dialog.show(tab=self, cb=cb)
|
142
|
+
else:
|
143
|
+
log.debug("Saving file...")
|
144
|
+
self.saving = True
|
145
|
+
self.app.invalidate()
|
146
|
+
|
147
|
+
# Save to a temp file, then replace the original
|
148
|
+
temp_path = self.path.parent / f".{self.path.stem}.tmp{self.path.suffix}"
|
149
|
+
log.debug("Using temporary file %s", temp_path.name)
|
150
|
+
try:
|
151
|
+
open_file = temp_path.open("w")
|
152
|
+
except NotImplementedError:
|
153
|
+
if dialog := self.app.dialogs.get("save-as"):
|
154
|
+
dialog.show(tab=self, cb=cb)
|
155
|
+
else:
|
156
|
+
try:
|
157
|
+
open_file.write(self.input_box.buffer.text)
|
158
|
+
except Exception:
|
159
|
+
if dialog := self.app.dialogs.get("save-as"):
|
160
|
+
dialog.show(tab=self, cb=cb)
|
161
|
+
else:
|
162
|
+
try:
|
163
|
+
temp_path.rename(self.path)
|
164
|
+
except Exception:
|
165
|
+
if dialog := self.app.dialogs.get("save-as"):
|
166
|
+
dialog.show(tab=self, cb=cb)
|
167
|
+
else:
|
168
|
+
self.dirty = False
|
169
|
+
self.saving = False
|
170
|
+
self.app.invalidate()
|
171
|
+
log.debug("File saved")
|
172
|
+
# Run the callback
|
173
|
+
if callable(cb):
|
174
|
+
cb()
|
@@ -0,0 +1,73 @@
|
|
1
|
+
"""Contain a tab for displaying JSON data."""
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import json
|
6
|
+
import logging
|
7
|
+
from typing import TYPE_CHECKING
|
8
|
+
|
9
|
+
from prompt_toolkit.eventloop.utils import run_in_executor_with_context
|
10
|
+
from prompt_toolkit.layout.containers import VSplit
|
11
|
+
from prompt_toolkit.layout.dimension import Dimension
|
12
|
+
|
13
|
+
from euporie.core.tabs.base import Tab
|
14
|
+
from euporie.core.widgets.tree import JsonView
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from pathlib import Path
|
18
|
+
|
19
|
+
from prompt_toolkit.layout.containers import AnyContainer
|
20
|
+
|
21
|
+
from euporie.core.app import BaseApp
|
22
|
+
from euporie.core.widgets.status_bar import StatusBarFields
|
23
|
+
|
24
|
+
log = logging.getLogger(__name__)
|
25
|
+
|
26
|
+
|
27
|
+
class JsonTab(Tab):
|
28
|
+
"""Tab class for JSON data."""
|
29
|
+
|
30
|
+
name = "JSON Viewer"
|
31
|
+
mime_types = {"*json"}
|
32
|
+
filte_types = {".json"}
|
33
|
+
|
34
|
+
def __init__(self, app: BaseApp, path: Path | None = None) -> None:
|
35
|
+
"""Call when the tab is created."""
|
36
|
+
super().__init__(app, path)
|
37
|
+
|
38
|
+
# Load file and container in background
|
39
|
+
if self.path is not None:
|
40
|
+
|
41
|
+
def _load() -> None:
|
42
|
+
self.container = self.load_container()
|
43
|
+
self.app.layout.focus(self.container)
|
44
|
+
self.app.invalidate()
|
45
|
+
|
46
|
+
run_in_executor_with_context(_load)
|
47
|
+
|
48
|
+
def __pt_status__(self) -> StatusBarFields | None:
|
49
|
+
"""Return a list of statusbar field values shown then this tab is active."""
|
50
|
+
return ([str(self.path)], [])
|
51
|
+
|
52
|
+
@property
|
53
|
+
def title(self) -> str:
|
54
|
+
"""Return the tab title."""
|
55
|
+
if self.path is not None:
|
56
|
+
return str(self.path.name) or str(self.path)
|
57
|
+
else:
|
58
|
+
return "<file>"
|
59
|
+
|
60
|
+
def load_container(self) -> AnyContainer:
|
61
|
+
"""Abcract method for loading the notebook's main container."""
|
62
|
+
assert self.path is not None
|
63
|
+
|
64
|
+
data = json.load(self.path.open())
|
65
|
+
|
66
|
+
return VSplit(
|
67
|
+
[
|
68
|
+
JsonView(data, title=self.path.name, expanded=True),
|
69
|
+
# MarginContainer(ScrollbarMargin(), target=self.display.window),
|
70
|
+
],
|
71
|
+
width=Dimension(weight=1),
|
72
|
+
height=Dimension(weight=1),
|
73
|
+
)
|
euporie/notebook/tabs/log.py
CHANGED
@@ -2,33 +2,33 @@
|
|
2
2
|
|
3
3
|
from __future__ import annotations
|
4
4
|
|
5
|
-
from pathlib import Path
|
6
5
|
from typing import TYPE_CHECKING
|
7
6
|
|
8
7
|
from prompt_toolkit.filters import Condition, has_focus
|
9
8
|
from prompt_toolkit.formatted_text.base import FormattedText
|
10
|
-
from prompt_toolkit.layout.containers import HSplit
|
9
|
+
from prompt_toolkit.layout.containers import HSplit, VSplit
|
11
10
|
from prompt_toolkit.layout.dimension import Dimension
|
12
11
|
from prompt_toolkit.widgets import SearchToolbar
|
13
12
|
|
14
13
|
from euporie.core.commands import add_cmd
|
15
14
|
from euporie.core.current import get_app
|
16
15
|
from euporie.core.log import LOG_QUEUE, QueueHandler
|
16
|
+
from euporie.core.margins import MarginContainer, ScrollbarMargin
|
17
|
+
from euporie.core.path import parse_path
|
17
18
|
from euporie.core.tabs.base import Tab
|
18
19
|
from euporie.core.widgets.formatted_text_area import FormattedTextArea
|
19
20
|
|
20
21
|
if TYPE_CHECKING:
|
22
|
+
from pathlib import Path
|
21
23
|
from typing import Callable
|
22
24
|
|
23
|
-
from upath import UPath
|
24
|
-
|
25
25
|
from euporie.core.app import BaseApp
|
26
26
|
|
27
27
|
|
28
28
|
class LogView(Tab):
|
29
29
|
"""A tab which allows you to view log entries."""
|
30
30
|
|
31
|
-
def __init__(self, app: BaseApp, path:
|
31
|
+
def __init__(self, app: BaseApp, path: Path | None = None) -> None:
|
32
32
|
"""Create a new log view tab instance."""
|
33
33
|
super().__init__(app, path)
|
34
34
|
# Build the container
|
@@ -38,7 +38,7 @@ class LogView(Tab):
|
|
38
38
|
self.text_area = FormattedTextArea(
|
39
39
|
formatted_text=[],
|
40
40
|
read_only=True,
|
41
|
-
scrollbar=
|
41
|
+
scrollbar=False,
|
42
42
|
line_numbers=Condition(lambda: self.app.config.line_numbers),
|
43
43
|
search_field=self.search_field,
|
44
44
|
focus_on_click=True,
|
@@ -46,7 +46,17 @@ class LogView(Tab):
|
|
46
46
|
dont_extend_width=False,
|
47
47
|
)
|
48
48
|
self.container = HSplit(
|
49
|
-
[
|
49
|
+
[
|
50
|
+
VSplit(
|
51
|
+
[
|
52
|
+
self.text_area,
|
53
|
+
MarginContainer(
|
54
|
+
ScrollbarMargin(), target=self.text_area.window
|
55
|
+
),
|
56
|
+
]
|
57
|
+
),
|
58
|
+
self.search_field,
|
59
|
+
],
|
50
60
|
width=Dimension(weight=1),
|
51
61
|
height=Dimension(weight=1),
|
52
62
|
)
|
@@ -72,7 +82,7 @@ class LogView(Tab):
|
|
72
82
|
def title(self) -> str:
|
73
83
|
"""Return the title of this tab."""
|
74
84
|
suffix = (
|
75
|
-
f" ({
|
85
|
+
f" ({parse_path(self.app.config.log_file).name})"
|
76
86
|
if self.app.config.log_file
|
77
87
|
else ""
|
78
88
|
)
|