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
@@ -8,7 +8,7 @@ from abc import ABCMeta, abstractmethod
8
8
  from collections import deque
9
9
  from functools import partial
10
10
  from math import ceil, floor
11
- from typing import TYPE_CHECKING, cast
11
+ from typing import TYPE_CHECKING, Dict, cast
12
12
  from weakref import finalize
13
13
 
14
14
  from prompt_toolkit.buffer import ValidationState
@@ -30,7 +30,7 @@ from prompt_toolkit.key_binding.key_bindings import (
30
30
  KeyBindings,
31
31
  merge_key_bindings,
32
32
  )
33
- from prompt_toolkit.layout.containers import ConditionalContainer, Float, Window
33
+ from prompt_toolkit.layout.containers import ConditionalContainer, Float, VSplit, Window
34
34
  from prompt_toolkit.layout.controls import (
35
35
  BufferControl,
36
36
  FormattedTextControl,
@@ -46,11 +46,11 @@ from prompt_toolkit.utils import Event
46
46
  from prompt_toolkit.validation import Validator
47
47
  from prompt_toolkit.widgets.base import Box, TextArea
48
48
 
49
- from euporie.core.border import InnerEigthGrid
49
+ from euporie.core.border import InsetGrid
50
50
  from euporie.core.current import get_app
51
51
  from euporie.core.data_structures import DiBool
52
52
  from euporie.core.formatted_text.utils import FormattedTextAlign, align
53
- from euporie.core.margins import ScrollbarMargin
53
+ from euporie.core.margins import MarginContainer, ScrollbarMargin
54
54
  from euporie.core.widgets.decor import Border, Shadow
55
55
  from euporie.core.widgets.layout import ConditionalSplit
56
56
 
@@ -95,7 +95,7 @@ class Swatch:
95
95
  width: int = 2,
96
96
  height: int = 1,
97
97
  style: str = "class:swatch",
98
- border: GridStyle = InnerEigthGrid,
98
+ border: GridStyle = InsetGrid,
99
99
  show_borders: DiBool | None = None,
100
100
  ) -> None:
101
101
  """Create a new instance of the color swatch.
@@ -143,7 +143,7 @@ class Button:
143
143
  disabled: FilterOrBool = False,
144
144
  width: int | None = None,
145
145
  style: str | Callable[[], str] = "class:input",
146
- border: GridStyle | None = InnerEigthGrid,
146
+ border: GridStyle | None = InsetGrid,
147
147
  show_borders: DiBool | None = None,
148
148
  selected: bool = False,
149
149
  key_bindings: KeyBindingsBase | None = None,
@@ -259,7 +259,11 @@ class Button:
259
259
  self.on_mouse_down.fire()
260
260
 
261
261
  if not self.has_focus():
262
- get_app().layout.focus(self)
262
+ app = get_app()
263
+ app.layout.focus(self)
264
+ # Invalidate the app - we don't do it by returning None so the
265
+ # event can bubble
266
+ app.invalidate()
263
267
  # We want this event to bubble if unfocused
264
268
  return NotImplemented
265
269
  else:
@@ -305,7 +309,7 @@ class Button:
305
309
 
306
310
 
307
311
  class ToggleableWidget(metaclass=ABCMeta):
308
- """Bae class for toggleable widgets."""
312
+ """Base class for toggleable widgets."""
309
313
 
310
314
  container: AnyContainer
311
315
  on_click: Event
@@ -325,7 +329,9 @@ class ToggleableWidget(metaclass=ABCMeta):
325
329
  elif mouse_event.event_type == MouseEventType.MOUSE_DOWN:
326
330
  layout = get_app().layout
327
331
  if not layout.has_focus(self):
328
- get_app().layout.focus(self)
332
+ app = get_app()
333
+ app.layout.focus(self)
334
+ app.invalidate()
329
335
  return NotImplemented
330
336
  else:
331
337
  return None
@@ -363,7 +369,7 @@ class ToggleButton(ToggleableWidget):
363
369
  on_click: Callable[[ToggleButton], None] | None = None,
364
370
  width: int | None = None,
365
371
  style: str | Callable[[], str] = "class:input",
366
- border: GridStyle | None = InnerEigthGrid,
372
+ border: GridStyle | None = InsetGrid,
367
373
  show_borders: DiBool | None = None,
368
374
  selected: bool = False,
369
375
  disabled: FilterOrBool = False,
@@ -533,7 +539,7 @@ class Text:
533
539
  style: str = "class:input",
534
540
  height: int = 1,
535
541
  min_height: int = 1,
536
- multiline: bool = False,
542
+ multiline: FilterOrBool = False,
537
543
  expand: FilterOrBool = True,
538
544
  width: int | None = None,
539
545
  completer: Completer | None = None,
@@ -637,18 +643,23 @@ class Text:
637
643
  )
638
644
  self.text_area.window.content = self.text_area.control
639
645
 
640
- if multiline:
641
- self.text_area.window.right_margins = [
642
- *self.text_area.window.right_margins,
643
- ScrollbarMargin(),
644
- ]
645
646
  if on_text_changed:
646
647
  self.text_area.buffer.on_text_changed += on_text_changed
647
648
  if validation:
648
649
  self.text_area.buffer.validate_while_typing = Always()
649
650
  self.container = Border(
650
- self.text_area,
651
- border=InnerEigthGrid,
651
+ VSplit(
652
+ [
653
+ self.text_area,
654
+ ConditionalContainer(
655
+ MarginContainer(
656
+ ScrollbarMargin(), target=self.text_area.window
657
+ ),
658
+ filter=to_filter(multiline),
659
+ ),
660
+ ]
661
+ ),
662
+ border=InsetGrid,
652
663
  style=self.border_style,
653
664
  show_borders=show_borders,
654
665
  )
@@ -905,7 +916,7 @@ class Progress:
905
916
  height=lambda: None if self.vertical() else 1,
906
917
  width=lambda: 1 if self.vertical() else None,
907
918
  ),
908
- border=InnerEigthGrid,
919
+ border=InsetGrid,
909
920
  style=self.add_style("class:progress,border"),
910
921
  ),
911
922
  # width=lambda: 1 if self.vertical() else None,
@@ -937,8 +948,8 @@ class Progress:
937
948
  return self.container
938
949
 
939
950
 
940
- class SizedMask(dict[int, bool]):
941
- """Mak with restricted number of True items."""
951
+ class SizedMask(Dict[int, bool]):
952
+ """Mask with restricted number of True items."""
942
953
 
943
954
  def __init__(self, size: int | None = None) -> None:
944
955
  """Initialize a new sized default dict."""
@@ -974,7 +985,7 @@ class SizedMask(dict[int, bool]):
974
985
 
975
986
 
976
987
  class SelectableWidget(metaclass=ABCMeta):
977
- """Bae class for widgets where one or more items can be selected."""
988
+ """Base class for widgets where one or more items can be selected."""
978
989
 
979
990
  def __init__(
980
991
  self,
@@ -1133,6 +1144,22 @@ class SelectableWidget(metaclass=ABCMeta):
1133
1144
  for i in range(len(self.options)):
1134
1145
  self._selected[i] = i in values
1135
1146
 
1147
+ @property
1148
+ def value(self) -> Any:
1149
+ """Return the selected value."""
1150
+ if self.options:
1151
+ return self.options[self.index or 0]
1152
+ else:
1153
+ return None
1154
+
1155
+ @property
1156
+ def values(self) -> list[Any]:
1157
+ """Return a list of the selected values."""
1158
+ if self.options:
1159
+ return [self.options[i] for i in self.indices]
1160
+ else:
1161
+ return [None for i in self.indices]
1162
+
1136
1163
  def mouse_handler(self, i: int, mouse_event: MouseEvent) -> NotImplementedOrNone:
1137
1164
  """Handle mouse events."""
1138
1165
  if self.disabled():
@@ -1142,7 +1169,9 @@ class SelectableWidget(metaclass=ABCMeta):
1142
1169
  return None
1143
1170
  elif mouse_event.event_type == MouseEventType.MOUSE_DOWN:
1144
1171
  if not self.has_focus():
1172
+ app = get_app()
1145
1173
  get_app().layout.focus(self)
1174
+ app.invalidate()
1146
1175
  # We want this event to bubble if unfocused
1147
1176
  return NotImplemented
1148
1177
  else:
@@ -1201,7 +1230,7 @@ class Select(SelectableWidget):
1201
1230
  style: str | Callable[[], str] = "class:input,select",
1202
1231
  rows: int | None = 3,
1203
1232
  prefix: tuple[str, str] = ("", ""),
1204
- border: GridStyle | None = InnerEigthGrid,
1233
+ border: GridStyle | None = InsetGrid,
1205
1234
  show_borders: DiBool | None = None,
1206
1235
  disabled: FilterOrBool = False,
1207
1236
  dont_extend_width: FilterOrBool = True,
@@ -1252,7 +1281,12 @@ class Select(SelectableWidget):
1252
1281
  def text_fragments(self) -> StyleAndTextTuples:
1253
1282
  """Create a list of formatted text fragments to display."""
1254
1283
  ft: StyleAndTextTuples = []
1255
- max_width = max(fragment_list_width(to_formatted_text(x)) for x in self.labels)
1284
+ if self.labels:
1285
+ max_width = max(
1286
+ fragment_list_width(to_formatted_text(x)) for x in self.labels
1287
+ )
1288
+ else:
1289
+ max_width = 1
1256
1290
  for i, label in enumerate(self.labels):
1257
1291
  label = to_formatted_text(label)
1258
1292
  label = align(label, FormattedTextAlign.LEFT, width=max_width)
@@ -1277,26 +1311,31 @@ class Select(SelectableWidget):
1277
1311
  (f"{fragment_style} {style}", text, handler)
1278
1312
  for fragment_style, text, *_ in ft_option
1279
1313
  ]
1280
- ft.pop()
1314
+ if ft:
1315
+ ft.pop()
1281
1316
  return ft
1282
1317
 
1283
1318
  def load_container(self) -> AnyContainer:
1284
1319
  """Load the widget's container."""
1285
1320
  return Box(
1286
1321
  Border(
1287
- Window(
1288
- FormattedTextControl(
1289
- self.text_fragments,
1290
- focusable=True,
1291
- show_cursor=False,
1292
- key_bindings=self.key_bindings(),
1293
- ),
1294
- height=lambda: self.rows,
1295
- dont_extend_width=self.dont_extend_width,
1296
- dont_extend_height=self.dont_extend_height,
1297
- style=f"class:face"
1298
- f"{' class:disabled' if self.disabled() else ''}",
1299
- right_margins=[ScrollbarMargin(style=self.style)],
1322
+ VSplit(
1323
+ [
1324
+ window := Window(
1325
+ FormattedTextControl(
1326
+ self.text_fragments,
1327
+ focusable=True,
1328
+ show_cursor=False,
1329
+ key_bindings=self.key_bindings(),
1330
+ ),
1331
+ height=lambda: self.rows,
1332
+ dont_extend_width=self.dont_extend_width,
1333
+ dont_extend_height=self.dont_extend_height,
1334
+ style=f"class:face"
1335
+ f"{' class:disabled' if self.disabled() else ''}",
1336
+ ),
1337
+ MarginContainer(ScrollbarMargin(), target=window),
1338
+ ]
1300
1339
  ),
1301
1340
  border=self.border,
1302
1341
  show_borders=self.show_borders,
@@ -1510,7 +1549,7 @@ class ToggleButtons(SelectableWidget):
1510
1549
  max_count: int | None = None,
1511
1550
  on_change: Callable[[SelectableWidget], None] | None = None,
1512
1551
  style: str | Callable[[], str] = "class:input",
1513
- border: GridStyle | None = InnerEigthGrid,
1552
+ border: GridStyle | None = InsetGrid,
1514
1553
  disabled: FilterOrBool = False,
1515
1554
  vertical: FilterOrBool = False,
1516
1555
  ) -> None:
@@ -2055,7 +2094,7 @@ class Slider(SelectableWidget):
2055
2094
  max_count: int | None = None,
2056
2095
  on_change: Callable[[SelectableWidget], None] | None = None,
2057
2096
  style: str | Callable[[], str] = "class:input",
2058
- border: GridStyle = InnerEigthGrid,
2097
+ border: GridStyle = InsetGrid,
2059
2098
  show_borders: DiBool | None = None,
2060
2099
  vertical: FilterOrBool = False,
2061
2100
  show_arrows: FilterOrBool = True,
@@ -79,7 +79,6 @@ class KernelInput(TextArea):
79
79
  tempfile_suffix: str | Callable[[], str] = "",
80
80
  key_bindings: KeyBindingsBase | None = None,
81
81
  enable_history_search: FilterOrBool | None = False,
82
- focusable: FilterOrBool = True,
83
82
  wrap_lines: FilterOrBool = False,
84
83
  complete_while_typing: FilterOrBool = True,
85
84
  autosuggest_while_typing: FilterOrBool = True,
@@ -95,6 +94,7 @@ class KernelInput(TextArea):
95
94
  kwargs.setdefault("history", kernel_tab.history)
96
95
  kwargs.setdefault("search_field", app.search_bar)
97
96
  kwargs.setdefault("focus_on_click", True)
97
+ kwargs.setdefault("preview_search", True)
98
98
  kwargs.setdefault(
99
99
  "input_processors",
100
100
  [
@@ -168,18 +168,28 @@ class KernelInput(TextArea):
168
168
  )
169
169
 
170
170
  # Add configurable line numbers
171
- self.window.left_margins = left_margins or [
172
- ConditionalMargin(
173
- NumberedDiffMargin(),
174
- Condition(lambda: self.kernel_tab.app.config.line_numbers),
175
- )
176
- ]
177
- self.window.right_margins = right_margins or [
178
- OverflowMargin(),
179
- ConditionalMargin(
180
- ScrollbarMargin(display_arrows=False), filter=scrollable(self.window)
181
- ),
182
- ]
171
+ self.window.left_margins = (
172
+ left_margins
173
+ if left_margins is not None
174
+ else [
175
+ ConditionalMargin(
176
+ NumberedDiffMargin(),
177
+ Condition(lambda: self.kernel_tab.app.config.line_numbers),
178
+ )
179
+ ]
180
+ )
181
+
182
+ self.window.right_margins = (
183
+ right_margins
184
+ if right_margins is not None
185
+ else [
186
+ OverflowMargin(),
187
+ ConditionalMargin(
188
+ ScrollbarMargin(display_arrows=False),
189
+ filter=scrollable(self.window),
190
+ ),
191
+ ]
192
+ )
183
193
 
184
194
  self.window.cursorline = self.has_focus
185
195
 
@@ -30,7 +30,7 @@ from prompt_toolkit.mouse_events import MouseButton, MouseEventType
30
30
  from prompt_toolkit.utils import Event
31
31
  from prompt_toolkit.widgets import Box
32
32
 
33
- from euporie.core.border import OuterEigthGrid
33
+ from euporie.core.border import OutsetGrid
34
34
  from euporie.core.data_structures import DiBool
35
35
  from euporie.core.widgets.decor import Border
36
36
 
@@ -340,7 +340,7 @@ class TabBarControl(UIControl):
340
340
 
341
341
 
342
342
  class StackedSplit(metaclass=ABCMeta):
343
- """Bae class for containers with selectable children."""
343
+ """Base class for containers with selectable children."""
344
344
 
345
345
  def __init__(
346
346
  self,
@@ -454,7 +454,7 @@ class TabbedSplit(StackedSplit):
454
454
  on_change: Callable[[StackedSplit], None] | None = None,
455
455
  width: AnyDimension = None,
456
456
  height: AnyDimension = None,
457
- border: GridStyle = OuterEigthGrid,
457
+ border: GridStyle = OutsetGrid,
458
458
  show_borders: DiBool | None = None,
459
459
  ) -> None:
460
460
  """Initialize a new tabbed container."""
@@ -493,6 +493,7 @@ class TabbedSplit(StackedSplit):
493
493
  padding=0,
494
494
  padding_top=1,
495
495
  padding_bottom=1,
496
+ style="class:tabbed-split,page",
496
497
  ),
497
498
  border=self.border,
498
499
  show_borders=self.show_borders,