euporie 2.8.3__tar.gz → 2.8.4__tar.gz
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-2.8.3 → euporie-2.8.4}/PKG-INFO +5 -5
- {euporie-2.8.3 → euporie-2.8.4}/euporie/console/tabs/console.py +227 -104
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/__init__.py +1 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/__main__.py +1 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/app.py +20 -18
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/clipboard.py +1 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/comm/ipywidgets.py +5 -5
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/commands.py +1 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/config.py +4 -4
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/datum.py +4 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/registry.py +7 -2
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/filters.py +3 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/ft/html.py +2 -4
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/graphics.py +6 -6
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/kernel.py +56 -32
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/bindings/__init__.py +2 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/bindings/mouse.py +24 -22
- euporie-2.8.4/euporie/core/key_binding/bindings/vi.py +46 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/cache.py +33 -23
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/containers.py +235 -73
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/decor.py +3 -3
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/print.py +14 -2
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/scroll.py +15 -21
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/margins.py +59 -30
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/style.py +7 -5
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/tabs/base.py +32 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/tabs/notebook.py +6 -3
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/terminal.py +12 -17
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/utils.py +2 -4
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/cell.py +64 -109
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/dialog.py +25 -20
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/file_browser.py +3 -3
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/forms.py +8 -7
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/layout.py +5 -5
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/status.py +3 -3
- {euporie-2.8.3 → euporie-2.8.4}/euporie/hub/app.py +7 -3
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/app.py +59 -46
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/tabs/log.py +1 -1
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/tabs/notebook.py +5 -3
- {euporie-2.8.3 → euporie-2.8.4}/euporie/preview/app.py +3 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/preview/tabs/notebook.py +9 -14
- {euporie-2.8.3 → euporie-2.8.4}/euporie/web/tabs/web.py +0 -1
- {euporie-2.8.3 → euporie-2.8.4}/pyproject.toml +18 -10
- {euporie-2.8.3 → euporie-2.8.4}/.gitignore +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/LICENSE +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/README.rst +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/console/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/console/__main__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/console/app.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/console/py.typed +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/console/tabs/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/border.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/comm/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/comm/base.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/comm/registry.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/completion.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/ansi.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/base64.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/common.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/ft.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/html.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/jpeg.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/markdown.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/pdf.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/pil.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/png.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/rich.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/sixel.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/formats/svg.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/mime.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/convert/utils.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/current.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/data_structures.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/diagnostics.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/format.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/ft/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/ft/ansi.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/ft/table.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/ft/utils.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/history.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/inspection.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/io.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/bindings/basic.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/bindings/completion.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/bindings/micro.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/bindings/page_navigation.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/key_processor.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/micro_state.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/registry.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/utils.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/key_binding/vi_state.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/keys.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/launch.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/controls.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/mouse.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/layout/screen.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/lexers.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/log.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/lsp.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/path.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/processors.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/py.typed +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/pygments.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/reference.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/renderer.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/suggest.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/tabs/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/validation.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/cell_outputs.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/decor.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/display.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/formatted_text_area.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/inputs.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/menu.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/pager.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/palette.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/search.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/core/widgets/tree.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/data/desktop/euporie-console.desktop +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/data/desktop/euporie-notebook.desktop +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/hub/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/hub/__main__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/hub/py.typed +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/__main__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/current.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/enums.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/filters.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/py.typed +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/tabs/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/tabs/display.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/tabs/edit.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/tabs/json.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/widgets/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/notebook/widgets/side_bar.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/preview/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/preview/__main__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/preview/py.typed +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/preview/tabs/__init__.py +0 -0
- {euporie-2.8.3 → euporie-2.8.4}/euporie/web/widgets/webview.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: euporie
|
3
|
-
Version: 2.8.
|
3
|
+
Version: 2.8.4
|
4
4
|
Summary: Euporie is a suite of terminal applications for interacting with Jupyter kernels
|
5
5
|
Project-URL: Documentation, https://euporie.readthedocs.io/en/latest
|
6
6
|
Project-URL: Issues, https://github.com/joouha/euporie/issues
|
@@ -28,16 +28,16 @@ Requires-Dist: fsspec[http]>=2022.12.0
|
|
28
28
|
Requires-Dist: imagesize~=1.3
|
29
29
|
Requires-Dist: jupyter-client>=7.1
|
30
30
|
Requires-Dist: jupytext>=1.14.0
|
31
|
-
Requires-Dist: linkify-it-py~=
|
32
|
-
Requires-Dist: markdown-it-py~=
|
33
|
-
Requires-Dist: mdit-py-plugins~=0.
|
31
|
+
Requires-Dist: linkify-it-py~=2.0
|
32
|
+
Requires-Dist: markdown-it-py~=3.0
|
33
|
+
Requires-Dist: mdit-py-plugins~=0.4.2
|
34
34
|
Requires-Dist: nbformat~=5.0
|
35
35
|
Requires-Dist: pillow>=9.0
|
36
36
|
Requires-Dist: platformdirs~=3.5
|
37
37
|
Requires-Dist: prompt-toolkit~=3.0.36
|
38
38
|
Requires-Dist: pygments~=2.11
|
39
39
|
Requires-Dist: pyperclip~=1.8
|
40
|
-
Requires-Dist: sixelcrop~=0.1.
|
40
|
+
Requires-Dist: sixelcrop~=0.1.8
|
41
41
|
Requires-Dist: timg~=1.1.6
|
42
42
|
Requires-Dist: typing-extensions~=4.5
|
43
43
|
Requires-Dist: universal-pathlib~=0.2.1
|
@@ -62,7 +62,7 @@ if TYPE_CHECKING:
|
|
62
62
|
from prompt_toolkit.formatted_text import AnyFormattedText, StyleAndTextTuples
|
63
63
|
from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
|
64
64
|
from prompt_toolkit.key_binding.key_processor import KeyPressEvent
|
65
|
-
from prompt_toolkit.layout.containers import Float
|
65
|
+
from prompt_toolkit.layout.containers import Container, Float
|
66
66
|
|
67
67
|
from euporie.core.app import BaseApp
|
68
68
|
from euporie.core.lsp import LspClient
|
@@ -103,6 +103,7 @@ class Console(KernelTab):
|
|
103
103
|
prompt, password
|
104
104
|
),
|
105
105
|
set_execution_count=partial(setattr, self, "execution_count"),
|
106
|
+
add_input=self.new_input,
|
106
107
|
add_output=self.new_output,
|
107
108
|
clear_output=self.clear_output,
|
108
109
|
# set_metadata=self.misc_callback,
|
@@ -132,6 +133,7 @@ class Console(KernelTab):
|
|
132
133
|
|
133
134
|
self.json = nbformat.v4.new_notebook()
|
134
135
|
self.json["metadata"] = self._metadata
|
136
|
+
self.render_queue: list[dict[str, Any]] = []
|
135
137
|
|
136
138
|
self.container = self.load_container()
|
137
139
|
|
@@ -192,13 +194,12 @@ class Console(KernelTab):
|
|
192
194
|
completeness_status = self.kernel.is_complete(code, wait=True).get(
|
193
195
|
"status", "unknown"
|
194
196
|
)
|
195
|
-
|
197
|
+
return not (
|
196
198
|
not code.strip()
|
197
199
|
or completeness_status == "incomplete"
|
198
|
-
or
|
199
|
-
|
200
|
-
|
201
|
-
return True
|
200
|
+
or completeness_status == "unknown"
|
201
|
+
and code[-2:] != "\n\n"
|
202
|
+
)
|
202
203
|
|
203
204
|
def run(self, buffer: Buffer | None = None) -> None:
|
204
205
|
"""Run the code in the input box."""
|
@@ -208,78 +209,125 @@ class Console(KernelTab):
|
|
208
209
|
# Auto-reformat code
|
209
210
|
if app.config.autoformat:
|
210
211
|
self.input_box.reformat()
|
211
|
-
# Get the code to run
|
212
|
+
# # Get the code to run
|
212
213
|
text = buffer.text
|
213
|
-
# Remove any selections from input
|
214
|
+
# # Remove any selections from input
|
214
215
|
buffer.selection_state = None
|
215
216
|
# Disable existing output
|
216
|
-
self.live_output.style = "class:disabled"
|
217
|
+
# self.live_output.style = "class:disabled"
|
217
218
|
# Reset the diagnostics
|
218
219
|
self.reports.clear()
|
220
|
+
# Increment this for display purposes until we get the response from the kernel
|
221
|
+
self.execution_count += 1
|
219
222
|
# Move cursor to the start of the input
|
220
223
|
buffer.cursor_position = 0
|
221
|
-
#
|
222
|
-
|
223
|
-
app.layout = self.input_layout
|
224
|
-
app.draw()
|
225
|
-
app.layout = original_layout
|
226
|
-
# Prevent displayed graphics on terminal being cleaned up (bit of a hack)
|
227
|
-
app.graphics.clear()
|
224
|
+
# Render input
|
225
|
+
self.new_input({"code": text}, own=True, force=True)
|
228
226
|
# Run the previous entry
|
229
227
|
if self.kernel.status == "starting":
|
230
228
|
self.kernel_queue.append(partial(self.kernel.run, text, wait=False))
|
231
229
|
else:
|
232
230
|
self.kernel.run(text, wait=False)
|
233
|
-
# Increment this for display purposes until we get the response from the kernel
|
234
|
-
self.execution_count += 1
|
235
231
|
# Reset the input & output
|
236
232
|
buffer.reset(append_to_history=True)
|
237
|
-
|
238
|
-
|
233
|
+
self.on_advance()
|
234
|
+
|
235
|
+
def new_input(
|
236
|
+
self, input_json: dict[str, Any], own: bool, force: bool = False
|
237
|
+
) -> None:
|
238
|
+
"""Create new cell inputs in response to kernel ``execute_input`` messages."""
|
239
|
+
# Skip our own inputs when relayed from the kernel
|
240
|
+
# We render them immediately when they are run to avoid delays in the UI
|
241
|
+
if own and not force:
|
242
|
+
return
|
243
|
+
|
244
|
+
app = self.app
|
245
|
+
if not own and not app.config.show_remote_inputs:
|
246
|
+
return
|
247
|
+
|
248
|
+
self.flush_live_output()
|
249
|
+
|
239
250
|
# Record the input as a cell in the json
|
240
|
-
|
241
|
-
|
251
|
+
cell_json = nbformat.v4.new_code_cell(
|
252
|
+
source=input_json["code"],
|
253
|
+
execution_count=input_json.get("execution_count", self.execution_count),
|
242
254
|
)
|
255
|
+
self.render_queue.append(cell_json)
|
256
|
+
self.json["cells"].append(cell_json)
|
243
257
|
if (
|
244
258
|
app.config.max_stored_outputs
|
245
259
|
and len(self.json["cells"]) > app.config.max_stored_outputs
|
246
260
|
):
|
247
261
|
del self.json["cells"][0]
|
248
|
-
self.on_advance()
|
249
262
|
|
250
|
-
|
263
|
+
# Invalidate the app so the new input gets printed
|
264
|
+
app.invalidate()
|
265
|
+
|
266
|
+
def new_output(self, output_json: dict[str, Any], own: bool) -> None:
|
251
267
|
"""Print the previous output and replace it with the new one."""
|
268
|
+
if not own and not self.app.config.show_remote_outputs:
|
269
|
+
return
|
270
|
+
|
252
271
|
# Clear the output if we were previously asked to
|
253
272
|
if self.clear_outputs_on_output:
|
254
273
|
self.clear_outputs_on_output = False
|
255
274
|
# Clear the screen
|
256
275
|
get_cmd("clear-screen").run()
|
257
276
|
|
258
|
-
#
|
259
|
-
if self.json["cells"]:
|
260
|
-
self.json["cells"]
|
277
|
+
# If there is no cell in the virtual notebook, add an empty cell
|
278
|
+
if not self.json["cells"]:
|
279
|
+
self.json["cells"].append(
|
280
|
+
nbformat.v4.new_code_cell(execution_count=self.execution_count)
|
281
|
+
)
|
282
|
+
cell = self.json.cells[-1]
|
283
|
+
|
284
|
+
# If there is no code cell in the render queue, add a dummy cell with no input
|
285
|
+
if cell not in self.render_queue:
|
286
|
+
# Add to end of previous cell in virtual notebook
|
287
|
+
# cell["outputs"].append(output_json)
|
288
|
+
# Create virtual cell
|
289
|
+
cell = nbformat.v4.new_code_cell(
|
290
|
+
id=cell.id, execution_count=self.execution_count
|
291
|
+
)
|
292
|
+
self.render_queue.append(cell)
|
261
293
|
|
262
|
-
#
|
294
|
+
# Add widgets to the live output
|
263
295
|
if "application/vnd.jupyter.widget-view+json" in output_json.get("data", {}):
|
264
|
-
# Use a live output to display widgets
|
265
296
|
self.live_output.add_output(output_json)
|
266
297
|
else:
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
298
|
+
cell["outputs"].append(output_json)
|
299
|
+
|
300
|
+
# Invalidate the app so the output get printed
|
301
|
+
self.app.invalidate()
|
302
|
+
|
303
|
+
def flush_live_output(self) -> None:
|
304
|
+
"""Flush any active live outputs to the terminal."""
|
305
|
+
if self.live_output.json:
|
306
|
+
self.render_queue.append(
|
307
|
+
nbformat.v4.new_code_cell(
|
308
|
+
execution_count=None,
|
309
|
+
outputs=self.live_output.json[:],
|
310
|
+
),
|
311
|
+
)
|
312
|
+
self.live_output.reset()
|
271
313
|
|
272
314
|
def render_outputs(self, app: Application[Any]) -> None:
|
273
315
|
"""Request that any unrendered outputs be rendered."""
|
274
|
-
|
316
|
+
# Check for unrendered cells or new output
|
317
|
+
|
318
|
+
# Clear the render queue so it does not get rendered again
|
319
|
+
render_queue = list(self.render_queue)
|
320
|
+
self.render_queue.clear()
|
321
|
+
|
322
|
+
if render_queue:
|
323
|
+
# Render the echo layout with any new cells / outputs
|
275
324
|
app = self.app
|
276
325
|
original_layout = self.app.layout
|
277
|
-
|
278
|
-
app.
|
279
|
-
app.renderer.
|
326
|
+
new_layout = self.echo_layout(render_queue)
|
327
|
+
app.layout = new_layout
|
328
|
+
app.renderer.render(app, new_layout, is_done=True)
|
280
329
|
app.layout = original_layout
|
281
|
-
|
282
|
-
self.output.reset()
|
330
|
+
app.renderer.request_absolute_cursor_position()
|
283
331
|
|
284
332
|
def reset(self) -> None:
|
285
333
|
"""Reset the state of the tab."""
|
@@ -293,10 +341,16 @@ class Console(KernelTab):
|
|
293
341
|
self.app.invalidate()
|
294
342
|
|
295
343
|
def prompt(
|
296
|
-
self,
|
344
|
+
self,
|
345
|
+
text: str,
|
346
|
+
count: int | None = None,
|
347
|
+
offset: int = 0,
|
348
|
+
show_busy: bool = False,
|
297
349
|
) -> StyleAndTextTuples:
|
298
350
|
"""Determine what should be displayed in the prompt of the cell."""
|
299
|
-
|
351
|
+
if count is None:
|
352
|
+
count = self.execution_count
|
353
|
+
prompt = str(count + offset)
|
300
354
|
if show_busy and self.kernel.status in ("busy", "queued"):
|
301
355
|
prompt = "*".center(len(prompt))
|
302
356
|
ft: StyleAndTextTuples = [
|
@@ -317,59 +371,140 @@ class Console(KernelTab):
|
|
317
371
|
"""Return the file extension for scripts in the notebook's language."""
|
318
372
|
return self.lang_info.get("file_extension", ".py")
|
319
373
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
if
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
height=1,
|
341
|
-
)
|
342
|
-
output_margin = Window(
|
343
|
-
char=" ", width=lambda: len(str(self.execution_count)) + 7
|
344
|
-
)
|
374
|
+
# Echo area
|
375
|
+
|
376
|
+
def echo_layout(self, render_queue: list) -> Layout:
|
377
|
+
"""Generate a layout for displaying executed cells."""
|
378
|
+
children: list[Container] = []
|
379
|
+
height_known = renderer_height_is_known()
|
380
|
+
rows_above_layout = self.app.renderer.rows_above_layout if height_known else 1
|
381
|
+
json_cells = self.json.cells
|
382
|
+
for i, cell in enumerate(render_queue):
|
383
|
+
if cell.source:
|
384
|
+
# Spacing between cells
|
385
|
+
if ((json_cells and cell.id != json_cells[0].id) or i > 0) and (
|
386
|
+
(height_known and rows_above_layout > 0) or not height_known
|
387
|
+
):
|
388
|
+
children.append(
|
389
|
+
Window(
|
390
|
+
height=1,
|
391
|
+
dont_extend_height=True,
|
392
|
+
)
|
393
|
+
)
|
345
394
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
395
|
+
# Cell input
|
396
|
+
children.append(
|
397
|
+
VSplit(
|
398
|
+
[
|
399
|
+
Window(
|
400
|
+
FormattedTextControl(
|
401
|
+
partial(
|
402
|
+
self.prompt,
|
403
|
+
"In ",
|
404
|
+
count=cell.execution_count,
|
405
|
+
)
|
406
|
+
),
|
407
|
+
dont_extend_width=True,
|
408
|
+
style="class:cell,input,prompt",
|
409
|
+
height=1,
|
410
|
+
),
|
411
|
+
KernelInput(
|
412
|
+
text=cell.source,
|
413
|
+
kernel_tab=self,
|
414
|
+
language=lambda: self.language,
|
415
|
+
read_only=True,
|
416
|
+
),
|
417
|
+
],
|
418
|
+
),
|
419
|
+
)
|
420
|
+
|
421
|
+
# Outputs
|
422
|
+
if outputs := cell.outputs:
|
423
|
+
children.append(
|
424
|
+
Window(
|
425
|
+
height=1,
|
426
|
+
dont_extend_height=True,
|
427
|
+
)
|
428
|
+
)
|
429
|
+
|
430
|
+
def _flush(
|
431
|
+
buffer: list[dict[str, Any]], prompt: AnyFormattedText
|
432
|
+
) -> None:
|
433
|
+
if buffer:
|
434
|
+
children.append(
|
435
|
+
VSplit(
|
436
|
+
[
|
437
|
+
Window(
|
438
|
+
FormattedTextControl(prompt),
|
439
|
+
dont_extend_width=True,
|
440
|
+
dont_extend_height=True,
|
441
|
+
style="class:cell,output,prompt",
|
442
|
+
height=1,
|
443
|
+
),
|
444
|
+
CellOutputArea(
|
445
|
+
buffer, parent=self, style="class:disabled"
|
446
|
+
),
|
447
|
+
]
|
448
|
+
),
|
449
|
+
)
|
450
|
+
buffer.clear()
|
451
|
+
|
452
|
+
buffer: list[dict[str, Any]] = []
|
453
|
+
ec = cell.execution_count
|
454
|
+
prompt: AnyFormattedText = ""
|
455
|
+
next_prompt: AnyFormattedText
|
456
|
+
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
|
462
|
+
if next_prompt != prompt:
|
463
|
+
_flush(buffer, prompt)
|
464
|
+
prompt = next_prompt
|
465
|
+
buffer.append(output)
|
466
|
+
_flush(buffer, prompt)
|
467
|
+
|
468
|
+
return Layout(
|
363
469
|
FloatContainer(
|
364
|
-
|
470
|
+
PrintingContainer(children),
|
365
471
|
floats=cast("list[Float]", self.app.graphics),
|
366
472
|
)
|
367
473
|
)
|
368
474
|
|
475
|
+
def load_container(self) -> HSplit:
|
476
|
+
"""Build the main application layout."""
|
369
477
|
# Live output area
|
370
478
|
|
371
479
|
self.live_output = CellOutputArea([], parent=self)
|
372
480
|
|
481
|
+
live_output_row = ConditionalContainer(
|
482
|
+
HSplit(
|
483
|
+
[
|
484
|
+
Window(height=1, dont_extend_height=True),
|
485
|
+
VSplit(
|
486
|
+
[
|
487
|
+
Window(
|
488
|
+
FormattedTextControl(
|
489
|
+
lambda: self.prompt(
|
490
|
+
"Out",
|
491
|
+
count=self.live_output.json[0][
|
492
|
+
"execution_count"
|
493
|
+
],
|
494
|
+
)
|
495
|
+
),
|
496
|
+
dont_extend_width=True,
|
497
|
+
style="class:cell,output,prompt",
|
498
|
+
height=1,
|
499
|
+
),
|
500
|
+
self.live_output,
|
501
|
+
]
|
502
|
+
),
|
503
|
+
]
|
504
|
+
),
|
505
|
+
filter=Condition(lambda: bool(self.live_output.json)),
|
506
|
+
)
|
507
|
+
|
373
508
|
# Input area
|
374
509
|
|
375
510
|
input_kb = KeyBindings()
|
@@ -425,13 +560,6 @@ class Console(KernelTab):
|
|
425
560
|
"""Force new line on Shift-Enter."""
|
426
561
|
event.current_buffer.newline(copy_margin=not in_paste_mode())
|
427
562
|
|
428
|
-
input_prompt = Window(
|
429
|
-
FormattedTextControl(partial(self.prompt, "In ", offset=1)),
|
430
|
-
dont_extend_width=True,
|
431
|
-
style="class:cell,input,prompt",
|
432
|
-
height=1,
|
433
|
-
)
|
434
|
-
|
435
563
|
def _handler(buffer: Buffer) -> bool:
|
436
564
|
self.run(buffer)
|
437
565
|
return True
|
@@ -460,7 +588,7 @@ class Console(KernelTab):
|
|
460
588
|
# Spacing
|
461
589
|
ConditionalContainer(
|
462
590
|
Window(height=1, dont_extend_height=True),
|
463
|
-
filter=Condition(lambda: self.
|
591
|
+
filter=Condition(lambda: len(self.json["cells"]) > 0)
|
464
592
|
& (
|
465
593
|
(
|
466
594
|
renderer_height_is_known
|
@@ -473,7 +601,12 @@ class Console(KernelTab):
|
|
473
601
|
ConditionalContainer(
|
474
602
|
VSplit(
|
475
603
|
[
|
476
|
-
|
604
|
+
Window(
|
605
|
+
FormattedTextControl(partial(self.prompt, "In ", offset=1)),
|
606
|
+
dont_extend_width=True,
|
607
|
+
style="class:cell,input,prompt",
|
608
|
+
height=1,
|
609
|
+
),
|
477
610
|
self.input_box,
|
478
611
|
],
|
479
612
|
),
|
@@ -481,19 +614,9 @@ class Console(KernelTab):
|
|
481
614
|
),
|
482
615
|
]
|
483
616
|
|
484
|
-
self.input_layout = Layout(PrintingContainer(input_row))
|
485
|
-
|
486
617
|
return HSplit(
|
487
618
|
[
|
488
|
-
|
489
|
-
HSplit(
|
490
|
-
[
|
491
|
-
Window(height=1, dont_extend_height=True),
|
492
|
-
VSplit([output_margin, self.live_output]),
|
493
|
-
]
|
494
|
-
),
|
495
|
-
filter=Condition(lambda: bool(self.live_output.json)),
|
496
|
-
),
|
619
|
+
live_output_row,
|
497
620
|
# StdIn
|
498
621
|
self.stdin_box,
|
499
622
|
ConditionalContainer(
|
@@ -14,7 +14,7 @@ def main(name: "str" = "launch") -> "None":
|
|
14
14
|
# Monkey-patch prompt_toolkit
|
15
15
|
from euporie.core.layout import containers # noqa: F401
|
16
16
|
|
17
|
-
eps = entry_points()
|
17
|
+
eps = entry_points()
|
18
18
|
if isinstance(eps, dict):
|
19
19
|
points = eps.get("euporie.apps")
|
20
20
|
else:
|
@@ -30,10 +30,7 @@ from prompt_toolkit.key_binding.bindings.emacs import (
|
|
30
30
|
from prompt_toolkit.key_binding.bindings.mouse import (
|
31
31
|
load_mouse_bindings as load_ptk_mouse_bindings,
|
32
32
|
)
|
33
|
-
from prompt_toolkit.key_binding.bindings.vi import
|
34
|
-
load_vi_bindings,
|
35
|
-
load_vi_search_bindings,
|
36
|
-
)
|
33
|
+
from prompt_toolkit.key_binding.bindings.vi import load_vi_search_bindings
|
37
34
|
from prompt_toolkit.key_binding.key_bindings import (
|
38
35
|
ConditionalKeyBindings,
|
39
36
|
merge_key_bindings,
|
@@ -54,6 +51,7 @@ from prompt_toolkit.styles import (
|
|
54
51
|
merge_styles,
|
55
52
|
style_from_pygments_cls,
|
56
53
|
)
|
54
|
+
from prompt_toolkit.utils import Event
|
57
55
|
from pygments.styles import STYLE_MAP as pygments_styles
|
58
56
|
from pygments.styles import get_style_by_name
|
59
57
|
from upath import UPath
|
@@ -241,6 +239,7 @@ class BaseApp(Application):
|
|
241
239
|
)
|
242
240
|
# Contains the opened tab containers
|
243
241
|
self.tabs: list[Tab] = []
|
242
|
+
self.on_tabs_change = Event(self)
|
244
243
|
# Holds the search bar to pass to cell inputs
|
245
244
|
self.search_bar: SearchBar | None = None
|
246
245
|
# Holds the index of the current tab
|
@@ -367,12 +366,12 @@ class BaseApp(Application):
|
|
367
366
|
xcursor=True,
|
368
367
|
ycursor=True,
|
369
368
|
)
|
370
|
-
# Open any files we need to
|
371
|
-
self.open_files()
|
372
369
|
# Load the layout
|
373
370
|
# We delay this until we have terminal responses to allow terminal graphics
|
374
371
|
# support to be detected first
|
375
372
|
self.layout = Layout(self.load_container(), self.focused_element)
|
373
|
+
# Open any files we need to
|
374
|
+
self.open_files()
|
376
375
|
|
377
376
|
async def run_async(
|
378
377
|
self,
|
@@ -462,6 +461,7 @@ class BaseApp(Application):
|
|
462
461
|
from euporie.core.key_binding.bindings.basic import load_basic_bindings
|
463
462
|
from euporie.core.key_binding.bindings.micro import load_micro_bindings
|
464
463
|
from euporie.core.key_binding.bindings.mouse import load_mouse_bindings
|
464
|
+
from euporie.core.key_binding.bindings.vi import load_vi_bindings
|
465
465
|
|
466
466
|
self._default_bindings = merge_key_bindings(
|
467
467
|
[
|
@@ -648,7 +648,7 @@ class BaseApp(Application):
|
|
648
648
|
log.error("Unable to display file %s", path)
|
649
649
|
else:
|
650
650
|
tab = tab_class(self, ppath)
|
651
|
-
self.
|
651
|
+
self.add_tab(tab)
|
652
652
|
# Ensure the opened tab is focused at app start
|
653
653
|
self.focused_element = tab
|
654
654
|
# Ensure the newly opened tab is selected
|
@@ -688,7 +688,7 @@ class BaseApp(Application):
|
|
688
688
|
try:
|
689
689
|
self.layout.focus(container)
|
690
690
|
except ValueError:
|
691
|
-
|
691
|
+
log.exception("Cannot focus tab")
|
692
692
|
|
693
693
|
def focus_tab(self, tab: Tab) -> None:
|
694
694
|
"""Make a tab visible and focuses it."""
|
@@ -704,20 +704,22 @@ class BaseApp(Application):
|
|
704
704
|
# Remove tab
|
705
705
|
if tab in self.tabs:
|
706
706
|
self.tabs.remove(tab)
|
707
|
-
|
708
|
-
#
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
self.layout.focus(self.tab)
|
713
|
-
# If a tab is not open, the status bar is not shown, so focus the logo, so
|
714
|
-
# pressing tab focuses the menu
|
707
|
+
self.on_tabs_change()
|
708
|
+
# Focus the next active tab if one exists
|
709
|
+
if next_tab := self.tab:
|
710
|
+
next_tab.focus()
|
711
|
+
# If no tab is open, ensure something is focused
|
715
712
|
else:
|
716
713
|
try:
|
717
714
|
self.layout.focus_next()
|
718
715
|
except ValueError:
|
719
716
|
pass
|
720
717
|
|
718
|
+
def add_tab(self, tab: Tab) -> None:
|
719
|
+
"""Add a tab to the current tabs list."""
|
720
|
+
self.tabs.append(tab)
|
721
|
+
self.on_tabs_change()
|
722
|
+
|
721
723
|
def close_tab(self, tab: Tab | None = None) -> None:
|
722
724
|
"""Close a notebook tab.
|
723
725
|
|
@@ -1302,8 +1304,8 @@ class BaseApp(Application):
|
|
1302
1304
|
"close-tab": "c-w",
|
1303
1305
|
"next-tab": "c-pagedown",
|
1304
1306
|
"previous-tab": "c-pageup",
|
1305
|
-
"focus-next": "
|
1306
|
-
"focus-previous": "tab",
|
1307
|
+
"focus-next": "tab",
|
1308
|
+
"focus-previous": "s-tab",
|
1307
1309
|
"clear-screen": "c-l",
|
1308
1310
|
}
|
1309
1311
|
}
|
@@ -47,7 +47,7 @@ class Osc52Clipboard(Clipboard):
|
|
47
47
|
if isinstance(output, Vt100_Output):
|
48
48
|
output.get_clipboard()
|
49
49
|
output.flush()
|
50
|
-
self.app.term_info.clipboard_data.await_response()
|
50
|
+
self.app.term_info.clipboard_data.await_response(timeout=5)
|
51
51
|
return self._data
|
52
52
|
|
53
53
|
def _update_clipboard(self, query: TerminalQuery) -> None:
|
@@ -122,7 +122,7 @@ def _separate_buffers(
|
|
122
122
|
cloned_substrate = dict(substate) # clone list/tuple
|
123
123
|
cloned_substrate[k] = vnew
|
124
124
|
else:
|
125
|
-
raise ValueError("
|
125
|
+
raise ValueError(f"Expected state to be a list or dict, not {substate!r}")
|
126
126
|
return cloned_substrate if cloned_substrate is not None else substate
|
127
127
|
|
128
128
|
|
@@ -250,7 +250,7 @@ class OutputModel(IpyWidgetComm):
|
|
250
250
|
{"outputs": partial(setattr, container, "json")},
|
251
251
|
)
|
252
252
|
|
253
|
-
def add_output(self, json: dict[str, Any]) -> None:
|
253
|
+
def add_output(self, json: dict[str, Any], own: bool) -> None:
|
254
254
|
"""Add a new output to this widget."""
|
255
255
|
if self.clear_output_wait:
|
256
256
|
self.set_state("outputs", [json])
|
@@ -285,9 +285,9 @@ class OutputModel(IpyWidgetComm):
|
|
285
285
|
else:
|
286
286
|
# Restore the message's callbacks
|
287
287
|
if self.original_callbacks:
|
288
|
-
self.comm_container.kernel.msg_id_callbacks[
|
289
|
-
self.
|
290
|
-
|
288
|
+
self.comm_container.kernel.msg_id_callbacks[self.prev_msg_id] = (
|
289
|
+
self.original_callbacks
|
290
|
+
)
|
291
291
|
self.original_callbacks = MsgCallbacks()
|
292
292
|
else:
|
293
293
|
del self.comm_container.kernel.msg_id_callbacks[self.prev_msg_id]
|