euporie 2.8.4__py3-none-any.whl → 2.8.6__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 (131) hide show
  1. euporie/console/_commands.py +143 -0
  2. euporie/console/_settings.py +58 -0
  3. euporie/console/app.py +25 -71
  4. euporie/console/tabs/console.py +58 -62
  5. euporie/core/__init__.py +1 -1
  6. euporie/core/__main__.py +28 -11
  7. euporie/core/_settings.py +109 -0
  8. euporie/core/app/__init__.py +3 -0
  9. euporie/core/app/_commands.py +95 -0
  10. euporie/core/app/_settings.py +457 -0
  11. euporie/core/{app.py → app/app.py} +212 -576
  12. euporie/core/app/base.py +51 -0
  13. euporie/core/{current.py → app/current.py} +13 -4
  14. euporie/core/app/cursor.py +35 -0
  15. euporie/core/app/dummy.py +12 -0
  16. euporie/core/app/launch.py +28 -0
  17. euporie/core/bars/__init__.py +11 -0
  18. euporie/core/bars/command.py +205 -0
  19. euporie/core/bars/menu.py +258 -0
  20. euporie/core/{widgets → bars}/search.py +20 -16
  21. euporie/core/{widgets → bars}/status.py +6 -23
  22. euporie/core/clipboard.py +19 -80
  23. euporie/core/comm/base.py +8 -6
  24. euporie/core/comm/ipywidgets.py +16 -7
  25. euporie/core/comm/registry.py +2 -1
  26. euporie/core/commands.py +10 -20
  27. euporie/core/completion.py +3 -2
  28. euporie/core/config.py +368 -341
  29. euporie/core/convert/__init__.py +0 -30
  30. euporie/core/convert/datum.py +116 -53
  31. euporie/core/convert/formats/__init__.py +31 -0
  32. euporie/core/convert/formats/ansi.py +9 -23
  33. euporie/core/convert/formats/common.py +11 -23
  34. euporie/core/convert/formats/html.py +45 -40
  35. euporie/core/convert/formats/pil.py +1 -1
  36. euporie/core/convert/formats/png.py +3 -5
  37. euporie/core/convert/formats/sixel.py +3 -3
  38. euporie/core/convert/registry.py +4 -6
  39. euporie/core/convert/utils.py +41 -4
  40. euporie/core/diagnostics.py +2 -2
  41. euporie/core/filters.py +98 -40
  42. euporie/core/format.py +2 -3
  43. euporie/core/ft/ansi.py +1 -1
  44. euporie/core/ft/html.py +12 -21
  45. euporie/core/ft/table.py +1 -3
  46. euporie/core/ft/utils.py +4 -1
  47. euporie/core/graphics.py +386 -133
  48. euporie/core/history.py +2 -2
  49. euporie/core/inspection.py +3 -2
  50. euporie/core/io.py +207 -28
  51. euporie/core/kernel/__init__.py +1 -0
  52. euporie/core/{kernel.py → kernel/client.py} +45 -108
  53. euporie/core/kernel/manager.py +114 -0
  54. euporie/core/key_binding/bindings/__init__.py +1 -8
  55. euporie/core/key_binding/bindings/basic.py +47 -7
  56. euporie/core/key_binding/bindings/completion.py +3 -8
  57. euporie/core/key_binding/bindings/micro.py +1 -6
  58. euporie/core/key_binding/bindings/mouse.py +2 -2
  59. euporie/core/key_binding/bindings/terminal.py +193 -0
  60. euporie/core/key_binding/key_processor.py +43 -2
  61. euporie/core/key_binding/registry.py +2 -0
  62. euporie/core/key_binding/utils.py +22 -2
  63. euporie/core/keys.py +7156 -93
  64. euporie/core/layout/cache.py +3 -3
  65. euporie/core/layout/containers.py +48 -4
  66. euporie/core/layout/decor.py +2 -2
  67. euporie/core/layout/mouse.py +1 -1
  68. euporie/core/layout/print.py +2 -1
  69. euporie/core/layout/scroll.py +39 -34
  70. euporie/core/log.py +76 -64
  71. euporie/core/lsp.py +118 -24
  72. euporie/core/margins.py +1 -1
  73. euporie/core/path.py +62 -13
  74. euporie/core/renderer.py +58 -17
  75. euporie/core/style.py +57 -39
  76. euporie/core/suggest.py +103 -85
  77. euporie/core/tabs/__init__.py +32 -0
  78. euporie/core/tabs/_settings.py +113 -0
  79. euporie/core/tabs/base.py +80 -470
  80. euporie/core/tabs/kernel.py +419 -0
  81. euporie/core/tabs/notebook.py +24 -101
  82. euporie/core/utils.py +92 -15
  83. euporie/core/validation.py +1 -1
  84. euporie/core/widgets/_settings.py +188 -0
  85. euporie/core/widgets/cell.py +19 -50
  86. euporie/core/widgets/cell_outputs.py +25 -36
  87. euporie/core/widgets/decor.py +11 -41
  88. euporie/core/widgets/dialog.py +62 -27
  89. euporie/core/widgets/display.py +12 -15
  90. euporie/core/widgets/file_browser.py +2 -23
  91. euporie/core/widgets/forms.py +8 -5
  92. euporie/core/widgets/inputs.py +13 -70
  93. euporie/core/widgets/layout.py +2 -1
  94. euporie/core/widgets/logo.py +49 -0
  95. euporie/core/widgets/menu.py +10 -8
  96. euporie/core/widgets/pager.py +6 -10
  97. euporie/core/widgets/palette.py +6 -6
  98. euporie/hub/app.py +52 -35
  99. euporie/notebook/_commands.py +24 -0
  100. euporie/notebook/_settings.py +107 -0
  101. euporie/notebook/app.py +49 -171
  102. euporie/notebook/filters.py +1 -1
  103. euporie/notebook/tabs/__init__.py +46 -7
  104. euporie/notebook/tabs/_commands.py +714 -0
  105. euporie/notebook/tabs/_settings.py +32 -0
  106. euporie/notebook/tabs/display.py +4 -4
  107. euporie/notebook/tabs/edit.py +11 -44
  108. euporie/notebook/tabs/json.py +5 -5
  109. euporie/notebook/tabs/log.py +1 -18
  110. euporie/notebook/tabs/notebook.py +11 -660
  111. euporie/notebook/widgets/_commands.py +11 -0
  112. euporie/notebook/widgets/_settings.py +19 -0
  113. euporie/notebook/widgets/side_bar.py +14 -34
  114. euporie/preview/_settings.py +104 -0
  115. euporie/preview/app.py +6 -31
  116. euporie/preview/tabs/notebook.py +6 -72
  117. euporie/web/__init__.py +1 -0
  118. euporie/web/tabs/__init__.py +14 -0
  119. euporie/web/tabs/web.py +11 -6
  120. euporie/web/widgets/__init__.py +1 -0
  121. euporie/web/widgets/webview.py +5 -15
  122. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/METADATA +10 -8
  123. euporie-2.8.6.dist-info/RECORD +175 -0
  124. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/WHEEL +1 -1
  125. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/entry_points.txt +2 -2
  126. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/licenses/LICENSE +1 -1
  127. euporie/core/launch.py +0 -64
  128. euporie/core/terminal.py +0 -522
  129. euporie-2.8.4.dist-info/RECORD +0 -147
  130. {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-console.desktop +0 -0
  131. {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-notebook.desktop +0 -0
@@ -0,0 +1,143 @@
1
+ """Contains commands for the console."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from prompt_toolkit.filters.app import (
8
+ buffer_has_focus,
9
+ has_selection,
10
+ )
11
+
12
+ from euporie.core.commands import add_cmd
13
+ from euporie.core.filters import (
14
+ buffer_is_code,
15
+ buffer_is_empty,
16
+ kernel_tab_has_focus,
17
+ )
18
+ from euporie.core.tabs.kernel import KernelTab
19
+
20
+ if TYPE_CHECKING:
21
+ from prompt_toolkit.key_binding.key_processor import KeyPressEvent
22
+
23
+
24
+ @add_cmd()
25
+ async def _convert_to_notebook() -> None:
26
+ """Convert the current console session to a notebook."""
27
+ from prompt_toolkit.application.run_in_terminal import in_terminal
28
+
29
+ from euporie.console.app import get_app
30
+ from euporie.console.tabs.console import Console
31
+ from euporie.notebook.app import NotebookApp
32
+
33
+ app = get_app()
34
+ NotebookApp.config = app.config
35
+ NotebookApp.load_settings()
36
+ NotebookApp.config.__init__(app="notebook") # type: ignore [misc]
37
+ nb_app = NotebookApp()
38
+ # Use same event loop
39
+ nb_app.loop = app.loop
40
+ for tab in app.tabs:
41
+ if isinstance(tab, Console):
42
+ from euporie.notebook.tabs.notebook import Notebook
43
+
44
+ nb = Notebook(
45
+ app=nb_app,
46
+ path=tab.path,
47
+ kernel=tab.kernel,
48
+ comms=tab.comms,
49
+ json=tab.json,
50
+ )
51
+ # Set the history to the console's history
52
+ nb.history = tab.history
53
+ # Add the current input
54
+ nb.add(len(nb.json["cells"]) + 1, source=tab.input_box.buffer.text)
55
+ # Add the new notebook to the notebook app
56
+ nb_app.tabs.append(nb)
57
+ # Tell notebook that the kernel has already started
58
+ nb.kernel_started()
59
+
60
+ async with in_terminal():
61
+ await nb_app.run_async()
62
+
63
+ app.exit()
64
+
65
+
66
+ @add_cmd()
67
+ def _accept_input() -> None:
68
+ """Accept the current console input."""
69
+ from euporie.console.app import get_app
70
+
71
+ buffer = get_app().current_buffer
72
+ if buffer:
73
+ buffer.validate_and_handle()
74
+
75
+
76
+ @add_cmd(
77
+ filter=buffer_is_code & buffer_has_focus & ~has_selection & ~buffer_is_empty,
78
+ )
79
+ def _clear_input() -> None:
80
+ """Clear the console input."""
81
+ from euporie.console.app import get_app
82
+
83
+ buffer = get_app().current_buffer
84
+ buffer.reset()
85
+
86
+
87
+ @add_cmd(
88
+ filter=buffer_is_code & buffer_has_focus,
89
+ )
90
+ def _run_input() -> None:
91
+ """Run the console input."""
92
+ from euporie.console.app import get_app
93
+ from euporie.console.tabs.console import Console
94
+
95
+ if isinstance(console := get_app().tab, Console):
96
+ console.run()
97
+
98
+
99
+ @add_cmd(
100
+ name="cc-interrupt-kernel",
101
+ hidden=True,
102
+ filter=buffer_is_code & buffer_is_empty,
103
+ )
104
+ @add_cmd(filter=kernel_tab_has_focus)
105
+ def _interrupt_kernel() -> None:
106
+ """Interrupt the notebook's kernel."""
107
+ from euporie.console.app import get_app
108
+
109
+ if isinstance(kt := get_app().tab, KernelTab):
110
+ kt.interrupt_kernel()
111
+
112
+
113
+ @add_cmd(filter=kernel_tab_has_focus)
114
+ def _restart_kernel() -> None:
115
+ """Restart the notebook's kernel."""
116
+ from euporie.console.app import get_app
117
+
118
+ if isinstance(kt := get_app().tab, KernelTab):
119
+ kt.restart_kernel()
120
+
121
+
122
+ @add_cmd(
123
+ filter=buffer_is_code & buffer_is_empty,
124
+ hidden=True,
125
+ description="Signals the end of the input, causing the console to exit.",
126
+ )
127
+ def _end_of_file(event: KeyPressEvent) -> None:
128
+ """Exit when Control-D has been pressed."""
129
+ event.app.exit(exception=EOFError)
130
+
131
+
132
+ @add_cmd()
133
+ def _clear_screen() -> None:
134
+ """Clear the screen and the previous output."""
135
+ from euporie.console.app import get_app
136
+ from euporie.console.tabs.console import Console
137
+
138
+ app = get_app()
139
+ app.renderer.clear()
140
+
141
+ if isinstance(console := app.tab, Console):
142
+ console.reset()
143
+ app.layout.focus(console.input_box)
@@ -0,0 +1,58 @@
1
+ """Settings for the console app."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from upath import UPath
8
+
9
+ from euporie.core.config import add_setting
10
+
11
+ if TYPE_CHECKING:
12
+ from typing import TypeVar
13
+
14
+ _AppResult = TypeVar("_AppResult")
15
+
16
+ add_setting(
17
+ name="mouse_support",
18
+ group="euporie.console.app",
19
+ flags=["--mouse-support"],
20
+ type_=bool,
21
+ help_="Enable or disable mouse support",
22
+ default=None,
23
+ description="""
24
+ When set to True, mouse support is enabled. When set to False, mouse
25
+ support is disabled.
26
+ """,
27
+ )
28
+ add_setting(
29
+ name="max_stored_outputs",
30
+ group="euporie.console.app",
31
+ flags=["--max-stored-outputs"],
32
+ type_=int,
33
+ help_="The number of inputs / outputs to store in an in-memory notebook",
34
+ default=100,
35
+ schema={
36
+ "minimum": 0,
37
+ },
38
+ description="""
39
+ Defines the maximum number of executed "cells" to store in case the console
40
+ session is saved to a file or converted into a notebook.
41
+ """,
42
+ )
43
+
44
+ add_setting(
45
+ name="connection_file",
46
+ group="euporie.console.app",
47
+ flags=["--connection-file", "--kernel-connection-file"],
48
+ type_=UPath,
49
+ help_="Attempt to connect to an existing kernel using a JSON connection info file",
50
+ default=None,
51
+ description="""
52
+ If the file does not exist, kernel connection information will be written
53
+ to the file path provided.
54
+
55
+ If the file exists, kernel connection info will be read from the file,
56
+ allowing euporie to connect to existing kernels.
57
+ """,
58
+ )
euporie/console/app.py CHANGED
@@ -6,7 +6,6 @@ import logging
6
6
  from typing import TYPE_CHECKING, cast
7
7
 
8
8
  from prompt_toolkit.application.current import get_app as ptk_get_app
9
- from prompt_toolkit.application.run_in_terminal import in_terminal
10
9
  from prompt_toolkit.filters.app import (
11
10
  has_completions,
12
11
  is_done,
@@ -15,6 +14,7 @@ from prompt_toolkit.filters.app import (
15
14
  )
16
15
  from prompt_toolkit.layout.containers import (
17
16
  ConditionalContainer,
17
+ Float,
18
18
  FloatContainer,
19
19
  HSplit,
20
20
  VSplit,
@@ -23,10 +23,11 @@ from prompt_toolkit.layout.containers import (
23
23
  from prompt_toolkit.layout.dimension import Dimension
24
24
 
25
25
  from euporie.console.tabs.console import Console
26
- from euporie.core import __logo__
27
- from euporie.core.app import BaseApp
28
- from euporie.core.commands import add_cmd
29
- from euporie.core.config import add_setting
26
+ from euporie.core.app.app import BaseApp
27
+ from euporie.core.bars.command import CommandBar
28
+ from euporie.core.bars.menu import ToolbarCompletionsMenu
29
+ from euporie.core.bars.search import SearchBar
30
+ from euporie.core.bars.status import StatusBar
30
31
  from euporie.core.filters import has_dialog
31
32
  from euporie.core.layout.mouse import DisableMouseOnScroll
32
33
  from euporie.core.widgets.dialog import (
@@ -36,14 +37,15 @@ from euporie.core.widgets.dialog import (
36
37
  SelectKernelDialog,
37
38
  ShortcutsDialog,
38
39
  )
40
+ from euporie.core.widgets.logo import Logo
39
41
  from euporie.core.widgets.pager import Pager
40
42
  from euporie.core.widgets.palette import CommandPalette
41
- from euporie.core.widgets.search import SearchBar
42
- from euporie.core.widgets.status import StatusBar
43
43
 
44
44
  if TYPE_CHECKING:
45
45
  from typing import Any, TypeVar
46
46
 
47
+ from prompt_toolkit.application.application import Application
48
+
47
49
  _AppResult = TypeVar("_AppResult")
48
50
 
49
51
  log = logging.getLogger(__name__)
@@ -63,7 +65,6 @@ class ConsoleApp(BaseApp):
63
65
  """
64
66
 
65
67
  name = "console"
66
- log_stdout_level = "ERROR"
67
68
 
68
69
  def __init__(self, **kwargs: Any) -> None:
69
70
  """Create a new euporie text user interface application instance."""
@@ -72,16 +73,22 @@ class ConsoleApp(BaseApp):
72
73
  kwargs.setdefault("title", "euporie-console")
73
74
  kwargs.setdefault("full_screen", False)
74
75
  kwargs.setdefault("leave_graphics", True)
75
- kwargs.setdefault("mouse_support", self.config.filter("mouse_support"))
76
+ kwargs.setdefault("mouse_support", self.config.filters.mouse_support)
76
77
 
77
78
  # Initialize the application
78
79
  super().__init__(**kwargs)
79
80
 
80
- self.search_bar = SearchBar()
81
81
  self.bindings_to_load += ["euporie.console.app.ConsoleApp"]
82
82
 
83
83
  self.tabs = []
84
- self.pager = Pager()
84
+
85
+ def pre_run(self, app: Application | None = None) -> None:
86
+ """Continue loading the app."""
87
+ super().pre_run(app)
88
+ # Add a toolbar completion menu
89
+ self.menus["toolbar_completions"] = Float(
90
+ content=ToolbarCompletionsMenu(), ycursor=True, transparent=True
91
+ )
85
92
 
86
93
  def _get_reserved_height(self) -> Dimension:
87
94
  if has_dialog():
@@ -95,9 +102,9 @@ class ConsoleApp(BaseApp):
95
102
  """Return a container with all opened tabs."""
96
103
  self.tabs = [Console(self)]
97
104
 
98
- assert self.pager is not None
99
- assert self.search_bar is not None
100
- assert self.tab is not None
105
+ self.command_bar = CommandBar()
106
+ self.search_bar = SearchBar()
107
+ self.pager = Pager()
101
108
 
102
109
  self.dialogs["command-palette"] = CommandPalette(self)
103
110
  self.dialogs["about"] = AboutDialog(self)
@@ -110,7 +117,7 @@ class ConsoleApp(BaseApp):
110
117
  DisableMouseOnScroll(
111
118
  HSplit(
112
119
  [
113
- self.tab,
120
+ self.tabs[0],
114
121
  ConditionalContainer(
115
122
  HSplit(
116
123
  [
@@ -120,17 +127,12 @@ class ConsoleApp(BaseApp):
120
127
  style="class:default",
121
128
  ),
122
129
  self.pager,
123
- self.search_bar,
124
130
  ConditionalContainer(
125
131
  VSplit(
126
132
  [
127
- Window(
128
- char=f" {__logo__} ",
129
- height=1,
130
- width=3,
131
- style="class:menu,logo",
132
- dont_extend_width=True,
133
- ),
133
+ Logo(),
134
+ self.command_bar,
135
+ self.search_bar,
134
136
  StatusBar(),
135
137
  ]
136
138
  ),
@@ -165,54 +167,6 @@ class ConsoleApp(BaseApp):
165
167
  else:
166
168
  super().exit()
167
169
 
168
- # ################################### Commands ####################################
169
-
170
- @staticmethod
171
- @add_cmd()
172
- async def _convert_to_notebook() -> None:
173
- """Convert the current console session to a notebook."""
174
- from euporie.notebook.app import NotebookApp
175
- from euporie.notebook.tabs.notebook import Notebook
176
-
177
- app = get_app()
178
- nb_app = NotebookApp()
179
- for tab in app.tabs:
180
- if isinstance(tab, Console):
181
- nb = Notebook(
182
- app=nb_app,
183
- path=tab.path,
184
- kernel=tab.kernel,
185
- comms=tab.comms,
186
- json=tab.json,
187
- )
188
- # Set the history to the console's history
189
- nb.history = tab.history
190
- # Add the current input
191
- nb.add(len(nb.json["cells"]) + 1, source=tab.input_box.buffer.text)
192
- # Add the new notebook to the notebook app
193
- nb_app.tabs.append(nb)
194
- # Tell notebook that the kernel has already started
195
- nb.kernel_started()
196
-
197
- async with in_terminal():
198
- await nb_app.run_async()
199
-
200
- app.exit()
201
-
202
- # ################################### Settings ####################################
203
-
204
- add_setting(
205
- name="mouse_support",
206
- flags=["--mouse-support"],
207
- type_=bool,
208
- help_="Enable or disable mouse support",
209
- default=None,
210
- description="""
211
- When set to True, mouse support is enabled. When set to False, mouse
212
- support is disabled.
213
- """,
214
- )
215
-
216
170
  # ################################# Key Bindings ##################################
217
171
 
218
172
  # register_bindings({"euporie.console.app.ConsoleApp": {}})
@@ -31,7 +31,6 @@ from prompt_toolkit.utils import Event
31
31
  from upath import UPath
32
32
 
33
33
  from euporie.core.commands import add_cmd, get_cmd
34
- from euporie.core.config import add_setting
35
34
  from euporie.core.diagnostics import Report
36
35
  from euporie.core.filters import (
37
36
  at_end_of_buffer,
@@ -40,7 +39,8 @@ from euporie.core.filters import (
40
39
  kernel_tab_has_focus,
41
40
  )
42
41
  from euporie.core.format import LspFormatter
43
- from euporie.core.kernel import MsgCallbacks
42
+ from euporie.core.io import edit_in_editor
43
+ from euporie.core.kernel.client import MsgCallbacks
44
44
  from euporie.core.key_binding.registry import (
45
45
  load_registered_bindings,
46
46
  register_bindings,
@@ -48,23 +48,24 @@ from euporie.core.key_binding.registry import (
48
48
  from euporie.core.layout.print import PrintingContainer
49
49
  from euporie.core.lsp import LspCell
50
50
  from euporie.core.style import KERNEL_STATUS_REPR
51
- from euporie.core.tabs.base import KernelTab
52
- from euporie.core.terminal import edit_in_editor
51
+ from euporie.core.tabs.kernel import KernelTab
53
52
  from euporie.core.validation import KernelValidator
54
53
  from euporie.core.widgets.cell_outputs import CellOutputArea
55
54
  from euporie.core.widgets.inputs import KernelInput, StdInput
56
55
 
57
56
  if TYPE_CHECKING:
57
+ from collections.abc import Sequence
58
58
  from pathlib import Path
59
- from typing import Any, Callable, Sequence
59
+ from typing import Any, Callable
60
60
 
61
+ from nbformat.notebooknode import NotebookNode
61
62
  from prompt_toolkit.application.application import Application
62
63
  from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
63
64
  from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
64
65
  from prompt_toolkit.key_binding.key_processor import KeyPressEvent
65
66
  from prompt_toolkit.layout.containers import Container, Float
66
67
 
67
- from euporie.core.app import BaseApp
68
+ from euporie.core.app.app import BaseApp
68
69
  from euporie.core.lsp import LspClient
69
70
 
70
71
  log = logging.getLogger(__name__)
@@ -77,8 +78,6 @@ class Console(KernelTab):
77
78
 
78
79
  """
79
80
 
80
- bg_init = False
81
-
82
81
  def __init__(
83
82
  self,
84
83
  app: BaseApp,
@@ -134,6 +133,7 @@ class Console(KernelTab):
134
133
  self.json = nbformat.v4.new_notebook()
135
134
  self.json["metadata"] = self._metadata
136
135
  self.render_queue: list[dict[str, Any]] = []
136
+ self.last_rendered: NotebookNode | None = None
137
137
 
138
138
  self.container = self.load_container()
139
139
 
@@ -197,8 +197,7 @@ class Console(KernelTab):
197
197
  return not (
198
198
  not code.strip()
199
199
  or completeness_status == "incomplete"
200
- or completeness_status == "unknown"
201
- and code[-2:] != "\n\n"
200
+ or (completeness_status == "unknown" and code[-2:] != "\n\n")
202
201
  )
203
202
 
204
203
  def run(self, buffer: Buffer | None = None) -> None:
@@ -292,10 +291,30 @@ class Console(KernelTab):
292
291
  self.render_queue.append(cell)
293
292
 
294
293
  # Add widgets to the live output
295
- if "application/vnd.jupyter.widget-view+json" in output_json.get("data", {}):
296
- self.live_output.add_output(output_json)
294
+ if output_json.get("output_type") == "stream":
295
+ # Use live output to enable emulation of carriage returns
296
+ text = output_json.get("text", "")
297
+ tail = ""
298
+ _text, _, _tail = text.rpartition("\n")
299
+ if "\r" in _tail: # or "\x1b[" in _tail:
300
+ text, tail = _text, _tail
301
+ if text:
302
+ # Partially Flush live output streams
303
+ cell["outputs"].extend(self.live_output.json)
304
+ self.live_output.reset()
305
+ output_json["text"] = text
306
+ cell["outputs"].append(output_json)
307
+ if tail:
308
+ self.live_output.add_output(
309
+ nbformat.v4.new_output(**{**output_json, "text": tail})
310
+ )
297
311
  else:
298
- cell["outputs"].append(output_json)
312
+ if "application/vnd.jupyter.widget-view+json" in output_json.get(
313
+ "data", {}
314
+ ):
315
+ self.live_output.add_output(output_json)
316
+ else:
317
+ cell["outputs"].append(output_json)
299
318
 
300
319
  # Invalidate the app so the output get printed
301
320
  self.app.invalidate()
@@ -331,7 +350,7 @@ class Console(KernelTab):
331
350
 
332
351
  def reset(self) -> None:
333
352
  """Reset the state of the tab."""
334
- from euporie.core.widgets.search import stop_search
353
+ from euporie.core.bars.search import stop_search
335
354
 
336
355
  self.live_output.reset()
337
356
  stop_search()
@@ -349,7 +368,7 @@ class Console(KernelTab):
349
368
  ) -> StyleAndTextTuples:
350
369
  """Determine what should be displayed in the prompt of the cell."""
351
370
  if count is None:
352
- count = self.execution_count
371
+ return [("", " " * (len(text) + 4 + len(str(self.execution_count))))]
353
372
  prompt = str(count + offset)
354
373
  if show_busy and self.kernel.status in ("busy", "queued"):
355
374
  prompt = "*".center(len(prompt))
@@ -420,12 +439,18 @@ class Console(KernelTab):
420
439
 
421
440
  # Outputs
422
441
  if outputs := cell.outputs:
423
- children.append(
424
- Window(
425
- height=1,
426
- dont_extend_height=True,
442
+ # Add space before an output if last rendered cell did not have outputs
443
+ # or we are rendering a new output
444
+ if self.last_rendered is not None and (
445
+ not self.last_rendered.outputs
446
+ or cell.execution_count != self.last_rendered.execution_count
447
+ ):
448
+ children.append(
449
+ Window(
450
+ height=1,
451
+ dont_extend_height=True,
452
+ )
427
453
  )
428
- )
429
454
 
430
455
  def _flush(
431
456
  buffer: list[dict[str, Any]], prompt: AnyFormattedText
@@ -450,21 +475,20 @@ class Console(KernelTab):
450
475
  buffer.clear()
451
476
 
452
477
  buffer: list[dict[str, Any]] = []
453
- ec = cell.execution_count
478
+ # ec = cell.execution_count
454
479
  prompt: AnyFormattedText = ""
455
480
  next_prompt: AnyFormattedText
456
481
  for output in outputs:
457
- if (next_ec := output.get("execution_count")) is None:
458
- next_prompt = " " * (len(str(ec)) + 7)
459
- else:
460
- next_prompt = self.prompt("Out", count=next_ec, show_busy=False)
461
- ec = next_ec
482
+ next_ec = output.get("execution_count")
483
+ next_prompt = self.prompt("Out", count=next_ec, show_busy=False)
462
484
  if next_prompt != prompt:
463
485
  _flush(buffer, prompt)
464
486
  prompt = next_prompt
465
487
  buffer.append(output)
466
488
  _flush(buffer, prompt)
467
489
 
490
+ self.last_rendered = cell
491
+
468
492
  return Layout(
469
493
  FloatContainer(
470
494
  PrintingContainer(children),
@@ -488,9 +512,9 @@ class Console(KernelTab):
488
512
  FormattedTextControl(
489
513
  lambda: self.prompt(
490
514
  "Out",
491
- count=self.live_output.json[0][
492
- "execution_count"
493
- ],
515
+ count=self.live_output.json[0].get(
516
+ "execution_count",
517
+ ),
494
518
  )
495
519
  ),
496
520
  dont_extend_width=True,
@@ -602,7 +626,11 @@ class Console(KernelTab):
602
626
  VSplit(
603
627
  [
604
628
  Window(
605
- FormattedTextControl(partial(self.prompt, "In ", offset=1)),
629
+ FormattedTextControl(
630
+ lambda: self.prompt(
631
+ "In ", self.execution_count, offset=1
632
+ )
633
+ ),
606
634
  dont_extend_width=True,
607
635
  style="class:cell,input,prompt",
608
636
  height=1,
@@ -838,38 +866,6 @@ class Console(KernelTab):
838
866
  tab.reset()
839
867
  app.layout.focus(tab.input_box)
840
868
 
841
- # ################################### Settings ####################################
842
-
843
- add_setting(
844
- name="max_stored_outputs",
845
- flags=["--max-stored-outputs"],
846
- type_=int,
847
- help_="The number of inputs / outputs to store in an in-memory notebook",
848
- default=100,
849
- schema={
850
- "minimum": 0,
851
- },
852
- description="""
853
- Defines the maximum number of executed "cells" to store in case the console
854
- session is saved to a file or converted into a notebook.
855
- """,
856
- )
857
-
858
- add_setting(
859
- name="connection_file",
860
- flags=["--connection-file", "--kernel-connection-file"],
861
- type_=UPath,
862
- help_="Attempt to connect to an existing kernel using a JSON connection info file",
863
- default=None,
864
- description="""
865
- If the file does not exist, kernel connection information will be written
866
- to the file path provided.
867
-
868
- If the file exists, kernel connection info will be read from the file,
869
- allowing euporie to connect to existing kernels.
870
- """,
871
- )
872
-
873
869
  # ################################# Key Bindings ##################################
874
870
 
875
871
  register_bindings(
euporie/core/__init__.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """This package defines the euporie application and its components."""
2
2
 
3
3
  __app_name__ = "euporie"
4
- __version__ = "2.8.4"
4
+ __version__ = "2.8.6"
5
5
  __logo__ = "⚈"
6
6
  __strapline__ = "Jupyter in the terminal"
7
7
  __author__ = "Josiah Outram Halstead"
euporie/core/__main__.py CHANGED
@@ -1,10 +1,33 @@
1
1
  """Main entry point into euporie.core."""
2
2
 
3
+ from __future__ import annotations
3
4
 
4
- def main(name: "str" = "launch") -> "None":
5
- """Load and launches the application."""
6
- from importlib.metadata import entry_points
5
+ from functools import cache
6
+ from importlib.metadata import entry_points
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from importlib.metadata import EntryPoint, EntryPoints
11
+
12
+
13
+ @cache
14
+ def available_apps() -> dict[str, EntryPoint]:
15
+ """Return a list of loadable euporie apps."""
16
+ eps: dict | EntryPoints
17
+ try:
18
+ eps = entry_points(group="euporie.apps")
19
+ except TypeError:
20
+ eps = entry_points()
21
+ if isinstance(eps, dict):
22
+ points = eps.get("euporie.apps")
23
+ else:
24
+ points = eps.select(group="euporie.apps")
25
+ apps = {x.name: x for x in points} if points else {}
26
+ return apps
7
27
 
28
+
29
+ def main(name: str = "launch") -> None:
30
+ """Load and launches the application."""
8
31
  # Register extensions to external packages
9
32
  from euporie.core import (
10
33
  path, # noqa F401
@@ -14,17 +37,11 @@ def main(name: "str" = "launch") -> "None":
14
37
  # Monkey-patch prompt_toolkit
15
38
  from euporie.core.layout import containers # noqa: F401
16
39
 
17
- eps = entry_points()
18
- if isinstance(eps, dict):
19
- points = eps.get("euporie.apps")
20
- else:
21
- points = eps.select(group="euporie.apps")
22
- apps = {x.name: x for x in points} if points else {}
23
-
40
+ apps = available_apps()
24
41
  if entry := apps.get(name):
25
42
  return entry.load().launch()
26
43
  else:
27
- raise Exception(f"Euporie app `{name}` not installed")
44
+ raise ModuleNotFoundError(f"Euporie app `{name}` not installed")
28
45
 
29
46
 
30
47
  if __name__ == "__main__":