euporie 2.8.0__py3-none-any.whl → 2.8.5__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/_commands.py +143 -0
- euporie/console/_settings.py +58 -0
- euporie/console/app.py +25 -71
- euporie/console/tabs/console.py +267 -147
- euporie/core/__init__.py +1 -9
- euporie/core/__main__.py +31 -5
- euporie/core/_settings.py +104 -0
- euporie/core/app/__init__.py +3 -0
- euporie/core/app/_commands.py +70 -0
- euporie/core/app/_settings.py +427 -0
- euporie/core/{app.py → app/app.py} +214 -572
- euporie/core/app/base.py +51 -0
- euporie/core/{current.py → app/current.py} +13 -4
- euporie/core/app/cursor.py +35 -0
- euporie/core/app/dummy.py +12 -0
- euporie/core/app/launch.py +28 -0
- euporie/core/bars/__init__.py +11 -0
- euporie/core/bars/command.py +182 -0
- euporie/core/bars/menu.py +258 -0
- euporie/core/{widgets → bars}/search.py +154 -57
- euporie/core/{widgets → bars}/status.py +9 -26
- euporie/core/clipboard.py +19 -80
- euporie/core/comm/base.py +8 -6
- euporie/core/comm/ipywidgets.py +21 -12
- euporie/core/comm/registry.py +2 -1
- euporie/core/commands.py +11 -5
- euporie/core/completion.py +3 -2
- euporie/core/config.py +368 -341
- euporie/core/convert/__init__.py +0 -30
- euporie/core/convert/datum.py +131 -60
- euporie/core/convert/formats/__init__.py +31 -0
- euporie/core/convert/formats/ansi.py +46 -30
- euporie/core/convert/formats/common.py +11 -23
- euporie/core/convert/formats/html.py +45 -40
- euporie/core/convert/formats/pil.py +1 -1
- euporie/core/convert/formats/png.py +3 -5
- euporie/core/convert/formats/sixel.py +3 -3
- euporie/core/convert/registry.py +11 -8
- euporie/core/convert/utils.py +50 -23
- euporie/core/diagnostics.py +2 -2
- euporie/core/filters.py +72 -82
- euporie/core/format.py +13 -2
- euporie/core/ft/ansi.py +1 -1
- euporie/core/ft/html.py +36 -36
- euporie/core/ft/table.py +1 -3
- euporie/core/ft/utils.py +4 -1
- euporie/core/graphics.py +216 -124
- euporie/core/history.py +2 -2
- euporie/core/inspection.py +3 -2
- euporie/core/io.py +207 -28
- euporie/core/kernel/__init__.py +1 -0
- euporie/core/{kernel.py → kernel/client.py} +100 -139
- euporie/core/kernel/manager.py +114 -0
- euporie/core/key_binding/bindings/__init__.py +2 -8
- euporie/core/key_binding/bindings/basic.py +47 -7
- euporie/core/key_binding/bindings/completion.py +3 -8
- euporie/core/key_binding/bindings/micro.py +5 -7
- euporie/core/key_binding/bindings/mouse.py +26 -24
- euporie/core/key_binding/bindings/terminal.py +193 -0
- euporie/core/key_binding/bindings/vi.py +46 -0
- euporie/core/key_binding/key_processor.py +43 -2
- euporie/core/key_binding/registry.py +2 -0
- euporie/core/key_binding/utils.py +22 -2
- euporie/core/keys.py +7156 -92
- euporie/core/layout/cache.py +35 -25
- euporie/core/layout/containers.py +280 -74
- euporie/core/layout/decor.py +5 -5
- euporie/core/layout/mouse.py +1 -1
- euporie/core/layout/print.py +16 -3
- euporie/core/layout/scroll.py +26 -28
- euporie/core/log.py +75 -60
- euporie/core/lsp.py +118 -24
- euporie/core/margins.py +60 -31
- euporie/core/path.py +2 -1
- euporie/core/renderer.py +58 -17
- euporie/core/style.py +60 -40
- euporie/core/suggest.py +103 -85
- euporie/core/tabs/__init__.py +34 -0
- euporie/core/tabs/_settings.py +113 -0
- euporie/core/tabs/base.py +11 -435
- euporie/core/tabs/kernel.py +420 -0
- euporie/core/tabs/notebook.py +20 -54
- euporie/core/utils.py +98 -6
- euporie/core/validation.py +1 -1
- euporie/core/widgets/_settings.py +188 -0
- euporie/core/widgets/cell.py +90 -158
- euporie/core/widgets/cell_outputs.py +26 -37
- euporie/core/widgets/decor.py +11 -41
- euporie/core/widgets/dialog.py +55 -44
- euporie/core/widgets/display.py +27 -24
- euporie/core/widgets/file_browser.py +5 -26
- euporie/core/widgets/forms.py +16 -12
- euporie/core/widgets/inputs.py +37 -81
- euporie/core/widgets/layout.py +7 -6
- euporie/core/widgets/logo.py +49 -0
- euporie/core/widgets/menu.py +13 -11
- euporie/core/widgets/pager.py +9 -11
- euporie/core/widgets/palette.py +6 -6
- euporie/hub/app.py +52 -31
- euporie/notebook/_commands.py +24 -0
- euporie/notebook/_settings.py +107 -0
- euporie/notebook/app.py +109 -210
- euporie/notebook/filters.py +1 -1
- euporie/notebook/tabs/__init__.py +46 -7
- euporie/notebook/tabs/_commands.py +714 -0
- euporie/notebook/tabs/_settings.py +32 -0
- euporie/notebook/tabs/display.py +2 -2
- euporie/notebook/tabs/edit.py +12 -7
- euporie/notebook/tabs/json.py +3 -3
- euporie/notebook/tabs/log.py +1 -18
- euporie/notebook/tabs/notebook.py +21 -674
- euporie/notebook/widgets/_commands.py +11 -0
- euporie/notebook/widgets/_settings.py +19 -0
- euporie/notebook/widgets/side_bar.py +14 -34
- euporie/preview/_settings.py +104 -0
- euporie/preview/app.py +8 -30
- euporie/preview/tabs/notebook.py +15 -86
- euporie/web/tabs/web.py +4 -6
- euporie/web/widgets/webview.py +5 -12
- {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/METADATA +11 -15
- euporie-2.8.5.dist-info/RECORD +172 -0
- {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/WHEEL +1 -1
- {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/entry_points.txt +2 -2
- {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/licenses/LICENSE +1 -1
- euporie/core/launch.py +0 -59
- euporie/core/terminal.py +0 -527
- euporie-2.8.0.dist-info/RECORD +0 -146
- {euporie-2.8.0.data → euporie-2.8.5.data}/data/share/applications/euporie-console.desktop +0 -0
- {euporie-2.8.0.data → euporie-2.8.5.data}/data/share/applications/euporie-notebook.desktop +0 -0
euporie/console/tabs/console.py
CHANGED
@@ -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.
|
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.
|
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
|
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
|
-
from prompt_toolkit.layout.containers import Float
|
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,
|
@@ -103,6 +102,7 @@ class Console(KernelTab):
|
|
103
102
|
prompt, password
|
104
103
|
),
|
105
104
|
set_execution_count=partial(setattr, self, "execution_count"),
|
105
|
+
add_input=self.new_input,
|
106
106
|
add_output=self.new_output,
|
107
107
|
clear_output=self.clear_output,
|
108
108
|
# set_metadata=self.misc_callback,
|
@@ -132,6 +132,8 @@ class Console(KernelTab):
|
|
132
132
|
|
133
133
|
self.json = nbformat.v4.new_notebook()
|
134
134
|
self.json["metadata"] = self._metadata
|
135
|
+
self.render_queue: list[dict[str, Any]] = []
|
136
|
+
self.last_rendered: NotebookNode | None = None
|
135
137
|
|
136
138
|
self.container = self.load_container()
|
137
139
|
|
@@ -192,13 +194,11 @@ 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
200
|
or (completeness_status == "unknown" and code[-2:] != "\n\n")
|
199
|
-
)
|
200
|
-
return False
|
201
|
-
return True
|
201
|
+
)
|
202
202
|
|
203
203
|
def run(self, buffer: Buffer | None = None) -> None:
|
204
204
|
"""Run the code in the input box."""
|
@@ -208,82 +208,149 @@ class Console(KernelTab):
|
|
208
208
|
# Auto-reformat code
|
209
209
|
if app.config.autoformat:
|
210
210
|
self.input_box.reformat()
|
211
|
-
# Get the code to run
|
211
|
+
# # Get the code to run
|
212
212
|
text = buffer.text
|
213
|
-
# Remove any selections from input
|
213
|
+
# # Remove any selections from input
|
214
214
|
buffer.selection_state = None
|
215
215
|
# Disable existing output
|
216
|
-
self.live_output.style = "class:disabled"
|
216
|
+
# self.live_output.style = "class:disabled"
|
217
217
|
# Reset the diagnostics
|
218
218
|
self.reports.clear()
|
219
|
+
# Increment this for display purposes until we get the response from the kernel
|
220
|
+
self.execution_count += 1
|
219
221
|
# Move cursor to the start of the input
|
220
222
|
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()
|
223
|
+
# Render input
|
224
|
+
self.new_input({"code": text}, own=True, force=True)
|
228
225
|
# Run the previous entry
|
229
226
|
if self.kernel.status == "starting":
|
230
227
|
self.kernel_queue.append(partial(self.kernel.run, text, wait=False))
|
231
228
|
else:
|
232
229
|
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
230
|
# Reset the input & output
|
236
231
|
buffer.reset(append_to_history=True)
|
237
|
-
|
238
|
-
|
232
|
+
self.on_advance()
|
233
|
+
|
234
|
+
def new_input(
|
235
|
+
self, input_json: dict[str, Any], own: bool, force: bool = False
|
236
|
+
) -> None:
|
237
|
+
"""Create new cell inputs in response to kernel ``execute_input`` messages."""
|
238
|
+
# Skip our own inputs when relayed from the kernel
|
239
|
+
# We render them immediately when they are run to avoid delays in the UI
|
240
|
+
if own and not force:
|
241
|
+
return
|
242
|
+
|
243
|
+
app = self.app
|
244
|
+
if not own and not app.config.show_remote_inputs:
|
245
|
+
return
|
246
|
+
|
247
|
+
self.flush_live_output()
|
248
|
+
|
239
249
|
# Record the input as a cell in the json
|
240
|
-
|
241
|
-
|
250
|
+
cell_json = nbformat.v4.new_code_cell(
|
251
|
+
source=input_json["code"],
|
252
|
+
execution_count=input_json.get("execution_count", self.execution_count),
|
242
253
|
)
|
254
|
+
self.render_queue.append(cell_json)
|
255
|
+
self.json["cells"].append(cell_json)
|
243
256
|
if (
|
244
257
|
app.config.max_stored_outputs
|
245
258
|
and len(self.json["cells"]) > app.config.max_stored_outputs
|
246
259
|
):
|
247
260
|
del self.json["cells"][0]
|
248
|
-
self.on_advance()
|
249
261
|
|
250
|
-
|
262
|
+
# Invalidate the app so the new input gets printed
|
263
|
+
app.invalidate()
|
264
|
+
|
265
|
+
def new_output(self, output_json: dict[str, Any], own: bool) -> None:
|
251
266
|
"""Print the previous output and replace it with the new one."""
|
267
|
+
if not own and not self.app.config.show_remote_outputs:
|
268
|
+
return
|
269
|
+
|
252
270
|
# Clear the output if we were previously asked to
|
253
271
|
if self.clear_outputs_on_output:
|
254
272
|
self.clear_outputs_on_output = False
|
255
273
|
# Clear the screen
|
256
274
|
get_cmd("clear-screen").run()
|
257
275
|
|
258
|
-
#
|
259
|
-
if self.json["cells"]:
|
260
|
-
self.json["cells"]
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
276
|
+
# If there is no cell in the virtual notebook, add an empty cell
|
277
|
+
if not self.json["cells"]:
|
278
|
+
self.json["cells"].append(
|
279
|
+
nbformat.v4.new_code_cell(execution_count=self.execution_count)
|
280
|
+
)
|
281
|
+
cell = self.json.cells[-1]
|
282
|
+
|
283
|
+
# If there is no code cell in the render queue, add a dummy cell with no input
|
284
|
+
if cell not in self.render_queue:
|
285
|
+
# Add to end of previous cell in virtual notebook
|
286
|
+
# cell["outputs"].append(output_json)
|
287
|
+
# Create virtual cell
|
288
|
+
cell = nbformat.v4.new_code_cell(
|
289
|
+
id=cell.id, execution_count=self.execution_count
|
290
|
+
)
|
291
|
+
self.render_queue.append(cell)
|
292
|
+
|
293
|
+
# Add widgets to the live output
|
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
|
+
)
|
266
311
|
else:
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
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)
|
318
|
+
|
319
|
+
# Invalidate the app so the output get printed
|
320
|
+
self.app.invalidate()
|
321
|
+
|
322
|
+
def flush_live_output(self) -> None:
|
323
|
+
"""Flush any active live outputs to the terminal."""
|
324
|
+
if self.live_output.json:
|
325
|
+
self.render_queue.append(
|
326
|
+
nbformat.v4.new_code_cell(
|
327
|
+
execution_count=None,
|
328
|
+
outputs=self.live_output.json[:],
|
329
|
+
),
|
330
|
+
)
|
331
|
+
self.live_output.reset()
|
271
332
|
|
272
333
|
def render_outputs(self, app: Application[Any]) -> None:
|
273
334
|
"""Request that any unrendered outputs be rendered."""
|
274
|
-
|
335
|
+
# Check for unrendered cells or new output
|
336
|
+
|
337
|
+
# Clear the render queue so it does not get rendered again
|
338
|
+
render_queue = list(self.render_queue)
|
339
|
+
self.render_queue.clear()
|
340
|
+
|
341
|
+
if render_queue:
|
342
|
+
# Render the echo layout with any new cells / outputs
|
275
343
|
app = self.app
|
276
344
|
original_layout = self.app.layout
|
277
|
-
|
278
|
-
app.
|
279
|
-
app.renderer.
|
345
|
+
new_layout = self.echo_layout(render_queue)
|
346
|
+
app.layout = new_layout
|
347
|
+
app.renderer.render(app, new_layout, is_done=True)
|
280
348
|
app.layout = original_layout
|
281
|
-
|
282
|
-
self.output.reset()
|
349
|
+
app.renderer.request_absolute_cursor_position()
|
283
350
|
|
284
351
|
def reset(self) -> None:
|
285
352
|
"""Reset the state of the tab."""
|
286
|
-
from euporie.core.
|
353
|
+
from euporie.core.bars.search import stop_search
|
287
354
|
|
288
355
|
self.live_output.reset()
|
289
356
|
stop_search()
|
@@ -293,10 +360,16 @@ class Console(KernelTab):
|
|
293
360
|
self.app.invalidate()
|
294
361
|
|
295
362
|
def prompt(
|
296
|
-
self,
|
363
|
+
self,
|
364
|
+
text: str,
|
365
|
+
count: int | None = None,
|
366
|
+
offset: int = 0,
|
367
|
+
show_busy: bool = False,
|
297
368
|
) -> StyleAndTextTuples:
|
298
369
|
"""Determine what should be displayed in the prompt of the cell."""
|
299
|
-
|
370
|
+
if count is None:
|
371
|
+
return [("", " " * (len(text) + 4 + len(str(self.execution_count))))]
|
372
|
+
prompt = str(count + offset)
|
300
373
|
if show_busy and self.kernel.status in ("busy", "queued"):
|
301
374
|
prompt = "*".center(len(prompt))
|
302
375
|
ft: StyleAndTextTuples = [
|
@@ -317,59 +390,145 @@ class Console(KernelTab):
|
|
317
390
|
"""Return the file extension for scripts in the notebook's language."""
|
318
391
|
return self.lang_info.get("file_extension", ".py")
|
319
392
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
393
|
+
# Echo area
|
394
|
+
|
395
|
+
def echo_layout(self, render_queue: list) -> Layout:
|
396
|
+
"""Generate a layout for displaying executed cells."""
|
397
|
+
children: list[Container] = []
|
398
|
+
height_known = renderer_height_is_known()
|
399
|
+
rows_above_layout = self.app.renderer.rows_above_layout if height_known else 1
|
400
|
+
json_cells = self.json.cells
|
401
|
+
for i, cell in enumerate(render_queue):
|
402
|
+
if cell.source:
|
403
|
+
# Spacing between cells
|
404
|
+
if ((json_cells and cell.id != json_cells[0].id) or i > 0) and (
|
405
|
+
(height_known and rows_above_layout > 0) or not height_known
|
406
|
+
):
|
407
|
+
children.append(
|
408
|
+
Window(
|
409
|
+
height=1,
|
410
|
+
dont_extend_height=True,
|
411
|
+
)
|
412
|
+
)
|
325
413
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
414
|
+
# Cell input
|
415
|
+
children.append(
|
416
|
+
VSplit(
|
417
|
+
[
|
418
|
+
Window(
|
419
|
+
FormattedTextControl(
|
420
|
+
partial(
|
421
|
+
self.prompt,
|
422
|
+
"In ",
|
423
|
+
count=cell.execution_count,
|
424
|
+
)
|
425
|
+
),
|
426
|
+
dont_extend_width=True,
|
427
|
+
style="class:cell,input,prompt",
|
428
|
+
height=1,
|
429
|
+
),
|
430
|
+
KernelInput(
|
431
|
+
text=cell.source,
|
432
|
+
kernel_tab=self,
|
433
|
+
language=lambda: self.language,
|
434
|
+
read_only=True,
|
435
|
+
),
|
436
|
+
],
|
437
|
+
),
|
438
|
+
)
|
439
|
+
|
440
|
+
# Outputs
|
441
|
+
if outputs := cell.outputs:
|
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
|
+
)
|
453
|
+
)
|
345
454
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
455
|
+
def _flush(
|
456
|
+
buffer: list[dict[str, Any]], prompt: AnyFormattedText
|
457
|
+
) -> None:
|
458
|
+
if buffer:
|
459
|
+
children.append(
|
460
|
+
VSplit(
|
461
|
+
[
|
462
|
+
Window(
|
463
|
+
FormattedTextControl(prompt),
|
464
|
+
dont_extend_width=True,
|
465
|
+
dont_extend_height=True,
|
466
|
+
style="class:cell,output,prompt",
|
467
|
+
height=1,
|
468
|
+
),
|
469
|
+
CellOutputArea(
|
470
|
+
buffer, parent=self, style="class:disabled"
|
471
|
+
),
|
472
|
+
]
|
473
|
+
),
|
474
|
+
)
|
475
|
+
buffer.clear()
|
476
|
+
|
477
|
+
buffer: list[dict[str, Any]] = []
|
478
|
+
# ec = cell.execution_count
|
479
|
+
prompt: AnyFormattedText = ""
|
480
|
+
next_prompt: AnyFormattedText
|
481
|
+
for output in outputs:
|
482
|
+
next_ec = output.get("execution_count")
|
483
|
+
next_prompt = self.prompt("Out", count=next_ec, show_busy=False)
|
484
|
+
if next_prompt != prompt:
|
485
|
+
_flush(buffer, prompt)
|
486
|
+
prompt = next_prompt
|
487
|
+
buffer.append(output)
|
488
|
+
_flush(buffer, prompt)
|
489
|
+
|
490
|
+
self.last_rendered = cell
|
491
|
+
|
492
|
+
return Layout(
|
363
493
|
FloatContainer(
|
364
|
-
|
494
|
+
PrintingContainer(children),
|
365
495
|
floats=cast("list[Float]", self.app.graphics),
|
366
496
|
)
|
367
497
|
)
|
368
498
|
|
499
|
+
def load_container(self) -> HSplit:
|
500
|
+
"""Build the main application layout."""
|
369
501
|
# Live output area
|
370
502
|
|
371
503
|
self.live_output = CellOutputArea([], parent=self)
|
372
504
|
|
505
|
+
live_output_row = ConditionalContainer(
|
506
|
+
HSplit(
|
507
|
+
[
|
508
|
+
Window(height=1, dont_extend_height=True),
|
509
|
+
VSplit(
|
510
|
+
[
|
511
|
+
Window(
|
512
|
+
FormattedTextControl(
|
513
|
+
lambda: self.prompt(
|
514
|
+
"Out",
|
515
|
+
count=self.live_output.json[0].get(
|
516
|
+
"execution_count",
|
517
|
+
),
|
518
|
+
)
|
519
|
+
),
|
520
|
+
dont_extend_width=True,
|
521
|
+
style="class:cell,output,prompt",
|
522
|
+
height=1,
|
523
|
+
),
|
524
|
+
self.live_output,
|
525
|
+
]
|
526
|
+
),
|
527
|
+
]
|
528
|
+
),
|
529
|
+
filter=Condition(lambda: bool(self.live_output.json)),
|
530
|
+
)
|
531
|
+
|
373
532
|
# Input area
|
374
533
|
|
375
534
|
input_kb = KeyBindings()
|
@@ -425,13 +584,6 @@ class Console(KernelTab):
|
|
425
584
|
"""Force new line on Shift-Enter."""
|
426
585
|
event.current_buffer.newline(copy_margin=not in_paste_mode())
|
427
586
|
|
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
587
|
def _handler(buffer: Buffer) -> bool:
|
436
588
|
self.run(buffer)
|
437
589
|
return True
|
@@ -460,7 +612,7 @@ class Console(KernelTab):
|
|
460
612
|
# Spacing
|
461
613
|
ConditionalContainer(
|
462
614
|
Window(height=1, dont_extend_height=True),
|
463
|
-
filter=Condition(lambda: self.
|
615
|
+
filter=Condition(lambda: len(self.json["cells"]) > 0)
|
464
616
|
& (
|
465
617
|
(
|
466
618
|
renderer_height_is_known
|
@@ -473,7 +625,16 @@ class Console(KernelTab):
|
|
473
625
|
ConditionalContainer(
|
474
626
|
VSplit(
|
475
627
|
[
|
476
|
-
|
628
|
+
Window(
|
629
|
+
FormattedTextControl(
|
630
|
+
lambda: self.prompt(
|
631
|
+
"In ", self.execution_count, offset=1
|
632
|
+
)
|
633
|
+
),
|
634
|
+
dont_extend_width=True,
|
635
|
+
style="class:cell,input,prompt",
|
636
|
+
height=1,
|
637
|
+
),
|
477
638
|
self.input_box,
|
478
639
|
],
|
479
640
|
),
|
@@ -481,19 +642,9 @@ class Console(KernelTab):
|
|
481
642
|
),
|
482
643
|
]
|
483
644
|
|
484
|
-
self.input_layout = Layout(PrintingContainer(input_row))
|
485
|
-
|
486
645
|
return HSplit(
|
487
646
|
[
|
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
|
-
),
|
647
|
+
live_output_row,
|
497
648
|
# StdIn
|
498
649
|
self.stdin_box,
|
499
650
|
ConditionalContainer(
|
@@ -503,7 +654,8 @@ class Console(KernelTab):
|
|
503
654
|
*input_row,
|
504
655
|
],
|
505
656
|
key_bindings=load_registered_bindings(
|
506
|
-
"euporie.console.tabs.console.Console"
|
657
|
+
"euporie.console.tabs.console.Console",
|
658
|
+
config=self.app.config,
|
507
659
|
),
|
508
660
|
)
|
509
661
|
|
@@ -714,38 +866,6 @@ class Console(KernelTab):
|
|
714
866
|
tab.reset()
|
715
867
|
app.layout.focus(tab.input_box)
|
716
868
|
|
717
|
-
# ################################### Settings ####################################
|
718
|
-
|
719
|
-
add_setting(
|
720
|
-
name="max_stored_outputs",
|
721
|
-
flags=["--max-stored-outputs"],
|
722
|
-
type_=int,
|
723
|
-
help_="The number of inputs / outputs to store in an in-memory notebook",
|
724
|
-
default=100,
|
725
|
-
schema={
|
726
|
-
"minimum": 0,
|
727
|
-
},
|
728
|
-
description="""
|
729
|
-
Defines the maximum number of executed "cells" to store in case the console
|
730
|
-
session is saved to a file or converted into a notebook.
|
731
|
-
""",
|
732
|
-
)
|
733
|
-
|
734
|
-
add_setting(
|
735
|
-
name="connection_file",
|
736
|
-
flags=["--connection-file", "--kernel-connection-file"],
|
737
|
-
type_=UPath,
|
738
|
-
help_="Attempt to connect to an existing kernel using a JSON connection info file",
|
739
|
-
default=None,
|
740
|
-
description="""
|
741
|
-
If the file does not exist, kernel connection information will be written
|
742
|
-
to the file path provided.
|
743
|
-
|
744
|
-
If the file exists, kernel connection info will be read from the file,
|
745
|
-
allowing euporie to connect to existing kernels.
|
746
|
-
""",
|
747
|
-
)
|
748
|
-
|
749
869
|
# ################################# Key Bindings ##################################
|
750
870
|
|
751
871
|
register_bindings(
|
euporie/core/__init__.py
CHANGED
@@ -1,18 +1,10 @@
|
|
1
1
|
"""This package defines the euporie application and its components."""
|
2
2
|
|
3
3
|
__app_name__ = "euporie"
|
4
|
-
__version__ = "2.8.
|
4
|
+
__version__ = "2.8.5"
|
5
5
|
__logo__ = "⚈"
|
6
6
|
__strapline__ = "Jupyter in the terminal"
|
7
7
|
__author__ = "Josiah Outram Halstead"
|
8
8
|
__email__ = "josiah@halstead.email"
|
9
9
|
__copyright__ = f"© 2024, {__author__}"
|
10
10
|
__license__ = "MIT"
|
11
|
-
|
12
|
-
|
13
|
-
# Register extensions to external packages
|
14
|
-
from euporie.core import path # noqa F401
|
15
|
-
from euporie.core import pygments # noqa F401
|
16
|
-
|
17
|
-
# Monkey-patch prompt_toolkit
|
18
|
-
from euporie.core.layout import containers # noqa: F401
|
euporie/core/__main__.py
CHANGED
@@ -1,21 +1,47 @@
|
|
1
1
|
"""Main entry point into euporie.core."""
|
2
2
|
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
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, SelectableGroups
|
7
11
|
|
8
|
-
|
12
|
+
|
13
|
+
@cache
|
14
|
+
def available_apps() -> dict[str, EntryPoint]:
|
15
|
+
"""Return a list of loadable euporie apps."""
|
16
|
+
eps: dict | SelectableGroups | EntryPoints
|
17
|
+
try:
|
18
|
+
eps = entry_points(group="euporie.apps")
|
19
|
+
except TypeError:
|
20
|
+
eps = entry_points()
|
9
21
|
if isinstance(eps, dict):
|
10
22
|
points = eps.get("euporie.apps")
|
11
23
|
else:
|
12
24
|
points = eps.select(group="euporie.apps")
|
13
25
|
apps = {x.name: x for x in points} if points else {}
|
26
|
+
return apps
|
27
|
+
|
28
|
+
|
29
|
+
def main(name: str = "launch") -> None:
|
30
|
+
"""Load and launches the application."""
|
31
|
+
# Register extensions to external packages
|
32
|
+
from euporie.core import (
|
33
|
+
path, # noqa F401
|
34
|
+
pygments, # noqa F401
|
35
|
+
)
|
36
|
+
|
37
|
+
# Monkey-patch prompt_toolkit
|
38
|
+
from euporie.core.layout import containers # noqa: F401
|
14
39
|
|
40
|
+
apps = available_apps()
|
15
41
|
if entry := apps.get(name):
|
16
42
|
return entry.load().launch()
|
17
43
|
else:
|
18
|
-
raise
|
44
|
+
raise ModuleNotFoundError(f"Euporie app `{name}` not installed")
|
19
45
|
|
20
46
|
|
21
47
|
if __name__ == "__main__":
|