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,563 @@
1
+ """Defines a web-view control."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import TYPE_CHECKING, cast
7
+
8
+ from prompt_toolkit.application.current import get_app
9
+ from prompt_toolkit.cache import FastDictCache
10
+ from prompt_toolkit.data_structures import Point
11
+ from prompt_toolkit.eventloop.utils import run_in_executor_with_context
12
+ from prompt_toolkit.filters import Condition
13
+ from prompt_toolkit.formatted_text.utils import split_lines
14
+ from prompt_toolkit.layout.containers import Window
15
+ from prompt_toolkit.layout.controls import UIContent, UIControl
16
+ from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType
17
+ from prompt_toolkit.utils import Event
18
+ from upath import UPath
19
+
20
+ from euporie.core.commands import add_cmd
21
+ from euporie.core.formatted_text.html import HTML, Node
22
+ from euporie.core.formatted_text.utils import max_line_width, paste
23
+ from euporie.core.key_binding.registry import (
24
+ load_registered_bindings,
25
+ register_bindings,
26
+ )
27
+ from euporie.core.path import parse_path
28
+
29
+ if TYPE_CHECKING:
30
+ from pathlib import Path
31
+ from typing import Any, Callable, Iterable
32
+
33
+ from prompt_toolkit.formatted_text.base import StyleAndTextTuples
34
+ from prompt_toolkit.key_binding.key_bindings import (
35
+ KeyBindingsBase,
36
+ NotImplementedOrNone,
37
+ )
38
+ from prompt_toolkit.layout.controls import GetLinePrefixCallable
39
+ from prompt_toolkit.layout.mouse_handlers import MouseHandler
40
+
41
+
42
+ log = logging.getLogger(__name__)
43
+
44
+
45
+ @Condition
46
+ def webview_has_focus() -> bool:
47
+ """Determine if there is a currently focused webview."""
48
+ return isinstance(get_app().layout.current_control, WebViewControl)
49
+
50
+
51
+ class WebViewControl(UIControl):
52
+ """Web view displays.
53
+
54
+ A control which displays rendered HTML content.
55
+ """
56
+
57
+ _window: Window
58
+
59
+ def __init__(
60
+ self,
61
+ url: str | Path,
62
+ link_handler: Callable | None = None,
63
+ ) -> None:
64
+ """Create a new web-view control instance."""
65
+ self._cursor_position = Point(0, 0)
66
+ self.loading = False
67
+ self.resizing = False
68
+ self.fragments: StyleAndTextTuples = []
69
+ self.width = 0
70
+ self.height = 0
71
+ self.url: Path | None = None
72
+ self.status = ""
73
+ self.link_handler = link_handler or self.load_url
74
+
75
+ self.rendered = Event(self)
76
+ self.on_cursor_position_changed = Event(self)
77
+
78
+ self.prev_stack: list[Path] = []
79
+ self.next_stack: list[Path] = []
80
+
81
+ # self.cursor_processor = CursorProcessor(
82
+ # lambda: self.cursor_position, style="fg:red"
83
+ # )
84
+
85
+ self.key_bindings = load_registered_bindings(
86
+ "euporie.web.widgets.webview.WebViewControl"
87
+ )
88
+
89
+ self._dom_cache: FastDictCache[tuple[Path], HTML] = FastDictCache(
90
+ get_value=self.get_dom, size=100
91
+ )
92
+ self._fragment_cache: FastDictCache[
93
+ tuple[HTML, int, int], StyleAndTextTuples
94
+ ] = FastDictCache(get_value=self.get_fragments, size=100_000)
95
+ self._content_cache: FastDictCache = FastDictCache(self.get_content, size=1_000)
96
+
97
+ self.load_url(url)
98
+
99
+ # Start a new event loop in a thread
100
+ self.thread = None
101
+
102
+ @property
103
+ def window(self) -> Window:
104
+ """Get the control's window."""
105
+ try:
106
+ return self._window
107
+ except AttributeError:
108
+ for window in get_app().layout.find_all_windows():
109
+ if window.content == self:
110
+ self._window = window
111
+ return window
112
+ return Window()
113
+
114
+ @property
115
+ def cursor_position(self) -> Point:
116
+ """Get the cursor position."""
117
+ return self._cursor_position
118
+
119
+ @cursor_position.setter
120
+ def cursor_position(self, value: Point) -> None:
121
+ """Set the cursor position."""
122
+ changed = self._cursor_position != value
123
+ self._cursor_position = value
124
+ if changed:
125
+ self.on_cursor_position_changed.fire()
126
+
127
+ def get_dom(self, url: Path) -> HTML:
128
+ """Load a HTML page as renderable formatted text."""
129
+ return HTML(
130
+ markup=url.read_text(),
131
+ base=url,
132
+ mouse_handler=self._node_mouse_handler,
133
+ paste_fixed=False,
134
+ )
135
+
136
+ @property
137
+ def title(self) -> str:
138
+ """Return the title of the current HTML page."""
139
+ if url := self.url:
140
+ dom = self._dom_cache.get((url,))
141
+ if dom is not None:
142
+ return dom.title
143
+ return ""
144
+
145
+ def get_fragments(self, dom: HTML, width: int, height: int) -> StyleAndTextTuples:
146
+ """Render a HTML page as lines of formatted text."""
147
+ return dom.render(width, height)
148
+
149
+ def load_url(self, url: str | Path, **kwargs: Any) -> None:
150
+ """Load a new URL."""
151
+ save_to_history = kwargs.get("save_to_history", True)
152
+ # Trigger "loading" view
153
+ self.loading = True
154
+ get_app().invalidate()
155
+ # Update navigation history
156
+ if self.url and save_to_history:
157
+ self.prev_stack.append(self.url)
158
+ self.next_stack.clear()
159
+ # Update url
160
+ self.url = UPath(url)
161
+ # Reset rendering
162
+ self.rendered.fire()
163
+ get_app().invalidate()
164
+
165
+ def nav_prev(self) -> None:
166
+ """Navigate forwards through the browser history."""
167
+ if self.url and self.prev_stack:
168
+ self.next_stack.append(self.url)
169
+ self.load_url(self.prev_stack.pop(), save_to_history=False)
170
+
171
+ def nav_next(self) -> None:
172
+ """Navigate backwards through the browser history."""
173
+ if self.url and self.next_stack:
174
+ self.prev_stack.append(self.url)
175
+ self.load_url(self.next_stack.pop(), save_to_history=False)
176
+
177
+ def render(self) -> None:
178
+ """Render the HTML DOM in a thread."""
179
+
180
+ def _render() -> None:
181
+ assert self.url is not None
182
+ # Potentiall redirect url
183
+ self.url = parse_path(self.url)
184
+ dom = self._dom_cache[self.url,]
185
+ self.fragments = self._fragment_cache[dom, self.width, self.height]
186
+ self.loading = False
187
+ self.resizing = False
188
+ # Scroll to the top
189
+ self.window.vertical_scroll = 0
190
+ self.cursor_position = Point(0, 0)
191
+ self.rendered.fire()
192
+ get_app().invalidate()
193
+
194
+ if self.url:
195
+ run_in_executor_with_context(_render)
196
+
197
+ def reset(self) -> None:
198
+ """Reset the state of the control."""
199
+
200
+ def preferred_width(self, max_available_width: int) -> int | None:
201
+ """Calculate and return the preferred width of the control."""
202
+ return None
203
+
204
+ def preferred_height(
205
+ self,
206
+ width: int,
207
+ max_available_height: int,
208
+ wrap_lines: bool,
209
+ get_line_prefix: GetLinePrefixCallable | None,
210
+ ) -> int | None:
211
+ """Calculate and return the preferred height of the control."""
212
+ return None
213
+
214
+ def is_focusable(self) -> bool:
215
+ """Tell whether this user control is focusable."""
216
+ return True
217
+
218
+ def get_content(
219
+ self,
220
+ url: Path,
221
+ loading: bool,
222
+ resizing: bool,
223
+ width: int,
224
+ # height: int,
225
+ cursor_position: Point,
226
+ ) -> UIContent:
227
+ """Create a cacheable UIContent."""
228
+ if self.loading:
229
+ lines = [
230
+ cast("StyleAndTextTuples", []),
231
+ cast(
232
+ "StyleAndTextTuples",
233
+ [("", " " * ((width - 8) // 2)), ("class:loading", "Loading…")],
234
+ ),
235
+ ]
236
+ else:
237
+ lines = list(split_lines(self.fragments))
238
+
239
+ def get_line(i: int) -> StyleAndTextTuples:
240
+ try:
241
+ line = lines[i]
242
+ except IndexError:
243
+ return []
244
+
245
+ # Paste fixed elements
246
+ if (dom := self._dom_cache[url,]).fixed:
247
+ visible_line = max(0, i - self.window.vertical_scroll)
248
+ fixed_lines = list(split_lines(dom.fixed_mask))
249
+ if visible_line < len(fixed_lines):
250
+ line = paste(
251
+ fixed_lines[visible_line], line, 0, 0, transparent=True
252
+ )
253
+
254
+ # Apply processors
255
+ # merged_processor = self.cursor_processor
256
+ # line = lines[i]
257
+ # transformation = merged_processor.apply_transformation(
258
+ # TransformationInput(
259
+ # buffer_control=self, document=Document(), lineno=i, source_to_display=lambda i: i, fragments=line, width=width, height=height,
260
+ # )
261
+ # )
262
+ # return transformation.fragments
263
+
264
+ return line
265
+
266
+ return UIContent(
267
+ get_line=get_line,
268
+ line_count=len(lines),
269
+ cursor_position=self.cursor_position,
270
+ show_cursor=False,
271
+ )
272
+
273
+ def create_content(self, width: int, height: int) -> UIContent:
274
+ """Generate the content for this user control.
275
+
276
+ Returns:
277
+ A :class:`.UIContent` instance.
278
+ """
279
+ # Trigger a re-render if things have changed
280
+ if self.loading:
281
+ self.render()
282
+ if width != self.width: # or height != self.height:
283
+ self.resizing = True
284
+ self.width = width
285
+ self.height = height
286
+ self.render()
287
+
288
+ return self._content_cache[
289
+ self.url,
290
+ self.loading,
291
+ self.resizing,
292
+ width,
293
+ # height,
294
+ self.cursor_position,
295
+ ]
296
+
297
+ def _node_mouse_handler(
298
+ self, node: Node, mouse_event: MouseEvent
299
+ ) -> NotImplementedOrNone:
300
+ """Handle click events."""
301
+ if url := node.attrs.get("_link_path"):
302
+ # TODO - Check for #anchor links and scroll accordingly
303
+ if (
304
+ mouse_event.button == MouseButton.LEFT
305
+ and mouse_event.event_type == MouseEventType.MOUSE_UP
306
+ ):
307
+ self.link_handler(url, save_to_history=True, new_tab=False)
308
+ return None
309
+ elif (
310
+ mouse_event.button == MouseButton.MIDDLE
311
+ and mouse_event.event_type == MouseEventType.MOUSE_UP
312
+ ):
313
+ self.link_handler(url, save_to_history=False, new_tab=True)
314
+ return None
315
+ elif mouse_event.event_type == MouseEventType.MOUSE_MOVE:
316
+ self.status = str(url)
317
+ return None
318
+ return NotImplemented
319
+
320
+ def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone:
321
+ """Handle mouse events.
322
+
323
+ When `NotImplemented` is returned, it means that the given event is not
324
+ handled by the `UIControl` itself. The `Window` or key bindings can
325
+ decide to handle this event as scrolling or changing focus.
326
+
327
+ Args:
328
+ mouse_event: `MouseEvent` instance.
329
+
330
+ Returns:
331
+ NotImplemented if the UI does not need to be updates, None if it does
332
+ """
333
+ # Focus on mouse down
334
+ if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
335
+ get_app().layout.focus(self)
336
+ return None
337
+ # if mouse_event.event_type == MouseEventType.MOUSE_MOVE:
338
+ # self.cursor_position = mouse_event.position
339
+
340
+ # if mouse_event.event_type == MouseEventType.MOUSE_UP:
341
+ handler: MouseHandler | None = None
342
+ try:
343
+ content = self._content_cache[
344
+ self.url,
345
+ self.loading,
346
+ self.resizing,
347
+ self.width,
348
+ # self.height,
349
+ self.cursor_position,
350
+ ]
351
+ line = content.get_line(mouse_event.position.y)
352
+ except IndexError:
353
+ return NotImplemented
354
+ else:
355
+ # Find position in the fragment list.
356
+ xpos = mouse_event.position.x
357
+ # Find mouse handler for this character.
358
+ count = 0
359
+ for item in line:
360
+ count += len(item[1])
361
+ if count > xpos:
362
+ if len(item) >= 3:
363
+ handler = item[2]
364
+ if callable(handler):
365
+ return handler(mouse_event)
366
+ else:
367
+ break
368
+
369
+ if callable(handler):
370
+ return handler
371
+
372
+ self.status = ""
373
+
374
+ return NotImplemented
375
+
376
+ @property
377
+ def content_width(self) -> int:
378
+ """Return the width of the content."""
379
+ # return max(fragment_list_width(line) for line in self.lines)
380
+ return max_line_width(self.fragments)
381
+
382
+ def move_cursor_down(self) -> None:
383
+ """Move the cursor down one line."""
384
+ x, y = self.cursor_position
385
+ self.cursor_position = Point(x=x, y=y + 1)
386
+
387
+ def move_cursor_up(self) -> None:
388
+ """Move the cursor up one line."""
389
+ x, y = self.cursor_position
390
+ self.cursor_position = Point(x=x, y=max(0, y - 1))
391
+
392
+ def move_cursor_left(self) -> None:
393
+ """Move the cursor down one line."""
394
+ x, y = self.cursor_position
395
+ self.cursor_position = Point(x=max(0, x - 1), y=y)
396
+
397
+ def move_cursor_right(self) -> None:
398
+ """Move the cursor up one line."""
399
+ x, y = self.cursor_position
400
+ self.cursor_position = Point(x=x + 1, y=y)
401
+
402
+ def get_key_bindings(self) -> KeyBindingsBase | None:
403
+ """Return key bindings that are specific for this user control.
404
+
405
+ Returns:
406
+ A :class:`.KeyBindings` object if some key bindings are specified, or
407
+ `None` otherwise.
408
+ """
409
+ return self.key_bindings
410
+
411
+ def get_invalidate_events(self) -> Iterable[Event[object]]:
412
+ """Return a list of `Event` objects, which can be a generator.
413
+
414
+ the application collects all these events, in order to bind redraw
415
+ handlers to these events.
416
+ """
417
+ yield self.rendered
418
+ yield self.on_cursor_position_changed
419
+
420
+ # ################################### Commands ####################################
421
+
422
+ @staticmethod
423
+ @add_cmd(filter=webview_has_focus)
424
+ def _webview_nav_prev() -> None:
425
+ """Navigate backwards in the browser history."""
426
+ from euporie.web.widgets.webview import WebViewControl
427
+
428
+ current_control = get_app().layout.current_control
429
+ if isinstance(current_control, WebViewControl):
430
+ current_control.nav_prev()
431
+
432
+ @staticmethod
433
+ @add_cmd(filter=webview_has_focus)
434
+ def _webview_nav_next() -> None:
435
+ """Navigate forwards in the browser history."""
436
+ from euporie.web.widgets.webview import WebViewControl
437
+
438
+ current_control = get_app().layout.current_control
439
+ if isinstance(current_control, WebViewControl):
440
+ current_control.nav_next()
441
+
442
+ @staticmethod
443
+ @add_cmd(filter=webview_has_focus)
444
+ def _scroll_webview_left() -> None:
445
+ """Scroll the display up one line."""
446
+ from euporie.core.widgets.display import DisplayWindow
447
+
448
+ window = get_app().layout.current_window
449
+ assert isinstance(window, DisplayWindow)
450
+ window._scroll_left()
451
+
452
+ @staticmethod
453
+ @add_cmd(filter=webview_has_focus)
454
+ def _scroll_webview_right() -> None:
455
+ """Scroll the display down one line."""
456
+ from euporie.core.widgets.display import DisplayWindow
457
+
458
+ window = get_app().layout.current_window
459
+ assert isinstance(window, DisplayWindow)
460
+ window._scroll_right()
461
+
462
+ @staticmethod
463
+ @add_cmd(filter=webview_has_focus)
464
+ def _scroll_webview_up() -> None:
465
+ """Scroll the display up one line."""
466
+ get_app().layout.current_window._scroll_up()
467
+
468
+ @staticmethod
469
+ @add_cmd(filter=webview_has_focus)
470
+ def _scroll_webview_down() -> None:
471
+ """Scroll the display down one line."""
472
+ get_app().layout.current_window._scroll_down()
473
+
474
+ @staticmethod
475
+ @add_cmd(filter=webview_has_focus)
476
+ def _page_up_webview() -> None:
477
+ """Scroll the display up one page."""
478
+ window = get_app().layout.current_window
479
+ if window.render_info is not None:
480
+ for _ in range(window.render_info.window_height):
481
+ window._scroll_up()
482
+
483
+ @staticmethod
484
+ @add_cmd(filter=webview_has_focus)
485
+ def _page_down_webview() -> None:
486
+ """Scroll the display down one page."""
487
+ window = get_app().layout.current_window
488
+ if window.render_info is not None:
489
+ for _ in range(window.render_info.window_height):
490
+ window._scroll_down()
491
+
492
+ @staticmethod
493
+ @add_cmd(filter=webview_has_focus)
494
+ def _go_to_start_of_webview() -> None:
495
+ """Scroll the display to the top."""
496
+ from euporie.web.widgets.webview import WebViewControl
497
+
498
+ current_control = get_app().layout.current_control
499
+ if isinstance(current_control, WebViewControl):
500
+ current_control.cursor_position = Point(0, 0)
501
+
502
+ @staticmethod
503
+ @add_cmd(filter=webview_has_focus)
504
+ def _go_to_end_of_webview() -> None:
505
+ """Scroll the display down one page."""
506
+ from euporie.web.widgets.webview import WebViewControl
507
+
508
+ layout = get_app().layout
509
+ current_control = layout.current_control
510
+ window = layout.current_window
511
+ if (
512
+ isinstance(current_control, WebViewControl)
513
+ and window.render_info is not None
514
+ ):
515
+ current_control.cursor_position = Point(
516
+ 0, window.render_info.ui_content.line_count - 1
517
+ )
518
+
519
+ # ################################# Key Bindings ##################################
520
+
521
+ register_bindings(
522
+ {
523
+ "euporie.web.widgets.webview.WebViewControl": {
524
+ "scroll-webview-left": "left",
525
+ "scroll-webview-right": "right",
526
+ "scroll-webview-up": ["up", "k"],
527
+ "scroll-webview-down": ["down", "j"],
528
+ "page-up-webview": "pageup",
529
+ "page-down-webview": "pagedown",
530
+ "go-to-start-of-webview": "home",
531
+ "go-to-end-of-webview": "end",
532
+ "webview-nav-prev": ("escape", "left"),
533
+ "webview-nav-next": ("escape", "right"),
534
+ }
535
+ }
536
+ )
537
+
538
+
539
+ if __name__ == "__main__":
540
+ import sys
541
+
542
+ from prompt_toolkit.application.application import Application
543
+ from prompt_toolkit.key_binding.key_bindings import KeyBindings
544
+ from prompt_toolkit.layout.layout import Layout
545
+ from prompt_toolkit.output.color_depth import ColorDepth
546
+ from prompt_toolkit.styles.style import Style
547
+
548
+ from euporie.core.style import HTML_STYLE
549
+ from euporie.core.widgets.display import DisplayWindow
550
+ from euporie.web.widgets.webview import WebViewControl # noqa F811
551
+
552
+ kb = KeyBindings()
553
+ kb.add("q")(lambda event: event.app.exit())
554
+ layout = Layout(container=DisplayWindow(WebViewControl(UPath(sys.argv[-1]))))
555
+ app: Application = Application(
556
+ layout=layout,
557
+ key_bindings=kb,
558
+ full_screen=True,
559
+ style=Style(HTML_STYLE),
560
+ mouse_support=True,
561
+ color_depth=ColorDepth.DEPTH_24_BIT,
562
+ )
563
+ app.run()
@@ -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;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: euporie
3
- Version: 2.3.2
3
+ Version: 2.4.1
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
@@ -22,7 +22,6 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
22
  Classifier: Topic :: Scientific/Engineering
23
23
  Requires-Python: >=3.8
24
24
  Requires-Dist: aenum~=3.1
25
- Requires-Dist: appdirs~=1.4
26
25
  Requires-Dist: fastjsonschema~=2.15
27
26
  Requires-Dist: flatlatex~=0.15
28
27
  Requires-Dist: fsspec[http]>=2022.8.0
@@ -33,21 +32,23 @@ Requires-Dist: markdown-it-py~=2.1.0
33
32
  Requires-Dist: mdit-py-plugins~=0.3.0
34
33
  Requires-Dist: nbformat~=5.0
35
34
  Requires-Dist: pillow~=9.0
35
+ Requires-Dist: platformdirs~=3.5
36
36
  Requires-Dist: prompt-toolkit~=3.0.36
37
37
  Requires-Dist: pygments~=2.11
38
38
  Requires-Dist: pyperclip~=1.8
39
39
  Requires-Dist: sixelcrop~=0.1.3
40
40
  Requires-Dist: timg~=1.1
41
- Requires-Dist: typing-extensions~=4.2
42
- Requires-Dist: universal-pathlib~=0.0.19
41
+ Requires-Dist: typing-extensions~=4.5
42
+ Requires-Dist: universal-pathlib~=0.0.23
43
43
  Provides-Extra: chafa
44
44
  Requires-Dist: chafa-py>=1.0.2; extra == 'chafa'
45
45
  Provides-Extra: format
46
46
  Requires-Dist: black>=19.3.b0; extra == 'format'
47
47
  Requires-Dist: isort~=5.10.1; extra == 'format'
48
- Requires-Dist: ssort~=0.11.6; extra == 'format'
49
48
  Provides-Extra: hub
50
49
  Requires-Dist: asyncssh~=2.10.1; extra == 'hub'
50
+ Provides-Extra: jupytext
51
+ Requires-Dist: jupytext>=1.14.0; extra == 'jupytext'
51
52
  Description-Content-Type: text/x-rst
52
53
 
53
54
  |logo|