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.
Files changed (92) hide show
  1. euporie/console/__main__.py +3 -1
  2. euporie/console/app.py +6 -4
  3. euporie/console/tabs/console.py +34 -9
  4. euporie/core/__init__.py +6 -1
  5. euporie/core/__main__.py +1 -1
  6. euporie/core/app.py +79 -109
  7. euporie/core/border.py +44 -14
  8. euporie/core/comm/base.py +5 -4
  9. euporie/core/comm/ipywidgets.py +11 -11
  10. euporie/core/comm/registry.py +12 -6
  11. euporie/core/commands.py +30 -23
  12. euporie/core/completion.py +1 -4
  13. euporie/core/config.py +15 -5
  14. euporie/core/convert/{base.py → core.py} +117 -53
  15. euporie/core/convert/formats/ansi.py +46 -25
  16. euporie/core/convert/formats/base64.py +3 -3
  17. euporie/core/convert/formats/common.py +38 -13
  18. euporie/core/convert/formats/formatted_text.py +54 -12
  19. euporie/core/convert/formats/html.py +5 -5
  20. euporie/core/convert/formats/jpeg.py +1 -1
  21. euporie/core/convert/formats/markdown.py +4 -4
  22. euporie/core/convert/formats/pdf.py +1 -1
  23. euporie/core/convert/formats/pil.py +5 -3
  24. euporie/core/convert/formats/png.py +7 -6
  25. euporie/core/convert/formats/rich.py +4 -3
  26. euporie/core/convert/formats/sixel.py +5 -5
  27. euporie/core/convert/utils.py +1 -1
  28. euporie/core/current.py +11 -5
  29. euporie/core/formatted_text/ansi.py +4 -8
  30. euporie/core/formatted_text/html.py +1630 -856
  31. euporie/core/formatted_text/markdown.py +177 -166
  32. euporie/core/formatted_text/table.py +20 -14
  33. euporie/core/formatted_text/utils.py +21 -10
  34. euporie/core/io.py +14 -14
  35. euporie/core/kernel.py +48 -37
  36. euporie/core/key_binding/bindings/micro.py +5 -1
  37. euporie/core/key_binding/bindings/mouse.py +2 -2
  38. euporie/core/keys.py +3 -0
  39. euporie/core/launch.py +5 -2
  40. euporie/core/lexers.py +13 -2
  41. euporie/core/log.py +135 -139
  42. euporie/core/margins.py +32 -14
  43. euporie/core/path.py +273 -0
  44. euporie/core/processors.py +35 -0
  45. euporie/core/renderer.py +21 -5
  46. euporie/core/style.py +34 -19
  47. euporie/core/tabs/base.py +101 -17
  48. euporie/core/tabs/notebook.py +72 -30
  49. euporie/core/terminal.py +56 -48
  50. euporie/core/utils.py +12 -16
  51. euporie/core/widgets/cell.py +6 -5
  52. euporie/core/widgets/cell_outputs.py +2 -2
  53. euporie/core/widgets/decor.py +74 -82
  54. euporie/core/widgets/dialog.py +132 -28
  55. euporie/core/widgets/display.py +76 -24
  56. euporie/core/widgets/file_browser.py +87 -31
  57. euporie/core/widgets/formatted_text_area.py +1 -3
  58. euporie/core/widgets/forms.py +79 -40
  59. euporie/core/widgets/inputs.py +23 -13
  60. euporie/core/widgets/layout.py +4 -3
  61. euporie/core/widgets/menu.py +368 -216
  62. euporie/core/widgets/page.py +99 -58
  63. euporie/core/widgets/pager.py +1 -1
  64. euporie/core/widgets/palette.py +30 -27
  65. euporie/core/widgets/search_bar.py +38 -25
  66. euporie/core/widgets/status_bar.py +103 -5
  67. euporie/data/desktop/euporie-console.desktop +7 -0
  68. euporie/data/desktop/euporie-notebook.desktop +7 -0
  69. euporie/hub/__main__.py +3 -1
  70. euporie/hub/app.py +9 -7
  71. euporie/notebook/__main__.py +3 -1
  72. euporie/notebook/app.py +7 -30
  73. euporie/notebook/tabs/__init__.py +7 -3
  74. euporie/notebook/tabs/display.py +18 -9
  75. euporie/notebook/tabs/edit.py +106 -23
  76. euporie/notebook/tabs/json.py +73 -0
  77. euporie/notebook/tabs/log.py +18 -8
  78. euporie/notebook/tabs/notebook.py +60 -41
  79. euporie/preview/__main__.py +3 -1
  80. euporie/preview/app.py +2 -1
  81. euporie/preview/tabs/notebook.py +23 -10
  82. euporie/web/tabs/web.py +149 -0
  83. euporie/web/widgets/webview.py +563 -0
  84. euporie-2.4.1.data/data/share/applications/euporie-console.desktop +7 -0
  85. euporie-2.4.1.data/data/share/applications/euporie-notebook.desktop +7 -0
  86. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/METADATA +6 -5
  87. euporie-2.4.1.dist-info/RECORD +129 -0
  88. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/WHEEL +1 -1
  89. euporie/core/url.py +0 -64
  90. euporie-2.3.2.dist-info/RECORD +0 -122
  91. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/entry_points.txt +0 -0
  92. {euporie-2.3.2.dist-info → euporie-2.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,7 @@
1
+ [Desktop Entry]
2
+ Type=Application
3
+ Terminal=true
4
+ Name=Euporie Console
5
+ Icon=utilities-terminal
6
+ Exec=bash -i -c 'euporie-console %f'
7
+ Categories=Application;
@@ -0,0 +1,7 @@
1
+ [Desktop Entry]
2
+ Type=Application
3
+ Terminal=true
4
+ Name=Euporie Notebook
5
+ Icon=utilities-terminal
6
+ Exec=bash -i -c 'euporie-notebook %f'
7
+ Categories=Application;
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__.split(".")[1])
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.no_auth:
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.no_auth:
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="no_auth",
192
- flags=["--no-auth"],
193
+ name="auth",
194
+ flags=["--auth"],
193
195
  type_=bool,
194
196
  help_="Allow unauthenticated access to euporie hub",
195
- default=False,
197
+ default=True,
196
198
  description="""
197
199
  When set, users will be able to access euporie hub without authentication.
198
200
 
@@ -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__.split(".")[1])
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 load_default_statusbar_fields(self) -> None:
89
+ def statusbar_defaults(self) -> StatusBarFields | None:
92
90
  """Load the default statusbar fields (run after keybindings are loaded)."""
93
- self.status_default = (
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-notebook").menu,
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 .log import LogView
4
- from .notebook import Notebook
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"]
@@ -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.base import get_format
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 typing import Sequence
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
- def __init__(self, app: BaseApp, path: UPath | None = None) -> None:
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
- def statusbar_fields(
38
- self,
39
- ) -> tuple[Sequence[AnyFormattedText], Sequence[AnyFormattedText]]:
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
 
@@ -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 typing import Sequence
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: UPath | None = None,
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
- # Load file
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
- # Detect language
56
- lexer = detect_lexer(text, path)
57
- self._metadata = {"kernelspec": {"language": lexer.name}}
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
- # Load UI
60
- self.container = self.load_container()
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
- self.input_box.text = text
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 statusbar_fields(
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
- return ([str(self.path)], [])
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 title(self) -> str:
72
- """Return the tab title."""
106
+ def path_name(self) -> str:
107
+ """Return the path name."""
73
108
  if self.path is not None:
74
- return str(self.path.name)
109
+ return self.path.name
75
110
  else:
76
- return "<New File>"
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
- # MarginContainer(ScrollbarMargin(), target=self.input_box.window),
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
+ )
@@ -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: UPath | None = None) -> None:
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=True,
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
- [self.text_area, self.search_field],
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" ({Path(self.app.config.log_file).name})"
85
+ f" ({parse_path(self.app.config.log_file).name})"
76
86
  if self.app.config.log_file
77
87
  else ""
78
88
  )