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
@@ -1,176 +1,187 @@
1
1
  """Contain the browser CSS for formatting markdown."""
2
2
 
3
+ from prompt_toolkit.filters.utils import _always
4
+
3
5
  from .html import CssSelector
4
6
 
5
7
  _MARKDOWN_CSS = {
6
- (
7
- (CssSelector(item="h1"),),
8
- (CssSelector(item="h2"),),
9
- (CssSelector(item="h3"),),
10
- (CssSelector(item="h4"),),
11
- (CssSelector(item="h5"),),
12
- (CssSelector(item="h6"),),
13
- ): {
14
- "text_align": "center",
15
- },
16
- ((CssSelector(item="h1"),),): {
17
- "font_weight": "bold",
18
- "text_decoration": "underline",
19
- "border_top_style": "double",
20
- "border_right_style": "double",
21
- "border_bottom_style": "double",
22
- "border_left_style": "double",
23
- "border_top_width": "0.34em",
24
- "border_right_width": "0.34em",
25
- "border_bottom_width": "0.34em",
26
- "border_left_width": "0.34em",
27
- "border_top_color": "ansiyellow",
28
- "border_right_color": "ansiyellow",
29
- "border_bottom_color": "ansiyellow",
30
- "border_left_color": "ansiyellow",
31
- },
32
- ((CssSelector(item="h2"),),): {
33
- "font_weight": "bold",
34
- "border_top_style": "solid",
35
- "border_right_style": "solid",
36
- "border_bottom_style": "solid",
37
- "border_left_style": "solid",
38
- "border_top_width": "0.34em",
39
- "border_right_width": "0.34em",
40
- "border_bottom_width": "0.34em",
41
- "border_left_width": "0.34em",
42
- "border_top_color": "#888888",
43
- "border_right_color": "#888888",
44
- "border_bottom_color": "#888888",
45
- "border_left_color": "#888888",
46
- },
47
- ((CssSelector(item="h3"),),): {
48
- "font_weight": "bold",
49
- "font_style": "italic",
50
- "border_top_style": "solid",
51
- "border_right_style": "solid",
52
- "border_bottom_style": "solid",
53
- "border_left_style": "solid",
54
- "border_top_width": "0.1em",
55
- "border_right_width": "0.1em",
56
- "border_bottom_width": "0.1em",
57
- "border_left_width": "0.1em",
58
- "border_top_color": "#888888",
59
- "border_right_color": "#888888",
60
- "border_bottom_color": "#888888",
61
- "border_left_color": "#888888",
62
- "margin_left": "auto",
63
- "margin_right": "auto",
64
- "padding_left": "1em",
65
- "padding_right": "1em",
66
- },
67
- ((CssSelector(item="h4"),),): {
68
- "text_weight": "bold",
69
- "text_decoration": "underline",
70
- "margin_top": "1rem",
71
- "margin_bottom": "1rem",
72
- },
73
- ((CssSelector(item="h5"),),): {
74
- "font_weight": "bold",
75
- "margin_top": "1rem",
76
- "margin_bottom": "1rem",
77
- },
78
- ((CssSelector(item="h6"),),): {
79
- "font_style": "italic",
80
- "margin_top": "1rem",
81
- "margin_bottom": "1rem",
82
- },
83
- (
8
+ _always: {
9
+ (
10
+ (CssSelector(item="h1"),),
11
+ (CssSelector(item="h2"),),
12
+ (CssSelector(item="h3"),),
13
+ (CssSelector(item="h4"),),
14
+ (CssSelector(item="h5"),),
15
+ (CssSelector(item="h6"),),
16
+ ): {
17
+ "text_align": "center",
18
+ },
19
+ ((CssSelector(item="h1"),),): {
20
+ "font_weight": "bold",
21
+ "text_decoration": "underline",
22
+ "border_top_style": "double",
23
+ "border_right_style": "double",
24
+ "border_bottom_style": "double",
25
+ "border_left_style": "double",
26
+ "border_top_width": "0.34em",
27
+ "border_right_width": "0.34em",
28
+ "border_bottom_width": "0.34em",
29
+ "border_left_width": "0.34em",
30
+ "border_top_color": "ansiyellow",
31
+ "border_right_color": "ansiyellow",
32
+ "border_bottom_color": "ansiyellow",
33
+ "border_left_color": "ansiyellow",
34
+ "padding_bottom": "0",
35
+ },
36
+ ((CssSelector(item="h2"),),): {
37
+ "font_weight": "bold",
38
+ "border_top_style": "solid",
39
+ "border_right_style": "solid",
40
+ "border_bottom_style": "solid",
41
+ "border_left_style": "solid",
42
+ "border_top_width": "0.34em",
43
+ "border_right_width": "0.34em",
44
+ "border_bottom_width": "0.34em",
45
+ "border_left_width": "0.34em",
46
+ "border_top_color": "#888888",
47
+ "border_right_color": "#888888",
48
+ "border_bottom_color": "#888888",
49
+ "border_left_color": "#888888",
50
+ "padding_bottom": "0",
51
+ },
52
+ ((CssSelector(item="h3"),),): {
53
+ "font_weight": "bold",
54
+ "font_style": "italic",
55
+ "border_top_style": "solid",
56
+ "border_right_style": "solid",
57
+ "border_bottom_style": "solid",
58
+ "border_left_style": "solid",
59
+ "border_top_width": "0.1em",
60
+ "border_right_width": "0.1em",
61
+ "border_bottom_width": "0.1em",
62
+ "border_left_width": "0.1em",
63
+ "border_top_color": "#888888",
64
+ "border_right_color": "#888888",
65
+ "border_bottom_color": "#888888",
66
+ "border_left_color": "#888888",
67
+ "margin_left": "auto",
68
+ "margin_right": "auto",
69
+ "padding_top": "0",
70
+ "padding_right": "1em",
71
+ "padding_bottom": "0",
72
+ "padding_left": "1em",
73
+ },
74
+ ((CssSelector(item="h4"),),): {
75
+ "text_weight": "bold",
76
+ "text_decoration": "underline",
77
+ "border_bottom_color": "#888888",
78
+ "margin_top": "1rem",
79
+ "margin_bottom": "1rem",
80
+ },
81
+ ((CssSelector(item="h5"),),): {
82
+ "font_weight": "bold",
83
+ "border_bottom_color": "#888888",
84
+ "margin_top": "1rem",
85
+ "margin_bottom": "1rem",
86
+ },
87
+ ((CssSelector(item="h6"),),): {
88
+ "font_style": "italic",
89
+ "border_bottom_color": "#888888",
90
+ "margin_top": "1rem",
91
+ "margin_bottom": "1rem",
92
+ },
84
93
  (
85
- CssSelector(item="ol"),
86
- CssSelector(item="li"),
87
- CssSelector(item="::marker"),
88
- ),
89
- ): {"color": "ansicyan"},
90
- (
94
+ (
95
+ CssSelector(item="ol"),
96
+ CssSelector(item="li"),
97
+ CssSelector(item="::marker"),
98
+ ),
99
+ ): {"color": "ansicyan"},
91
100
  (
92
- CssSelector(item="ul"),
93
- CssSelector(item="li"),
94
- CssSelector(item="::marker"),
95
- ),
96
- ): {"color": "ansiyellow"},
97
- ((CssSelector(item="blockquote"),),): {
98
- "margin_top": "1em",
99
- "margin_bottom": "1em",
100
- "padding_left": "1em",
101
- "border_left_width": "thick",
102
- "border_left_style": "solid",
103
- "border_left_color": "darkmagenta",
104
- },
105
- ((CssSelector(item=".block"),),): {"display": "block"},
106
- ((CssSelector(item="td"),),): {
107
- "display": "table-cell",
108
- "border_top_style": "solid",
109
- "border_right_style": "solid",
110
- "border_bottom_style": "solid",
111
- "border_left_style": "solid",
112
- "border_top_width": "0.1em",
113
- "border_right_width": "0.1em",
114
- "border_bottom_width": "0.1em",
115
- "border_left_width": "0.1em",
116
- "padding_left": "1em",
117
- "padding_right": "1em",
118
- # "_pt_class": "markdown,table,border",
119
- },
120
- ((CssSelector(item="th"),),): {
121
- "display": "table-cell",
122
- "border_top_style": "solid",
123
- "border_right_style": "solid",
124
- "border_bottom_style": "solid",
125
- "border_left_style": "solid",
126
- "border_top_width": "0.34em",
127
- "border_right_width": "0.34em",
128
- "border_bottom_width": "0.34em",
129
- "border_left_width": "0.34em",
130
- "padding_left": "1em",
131
- "padding_right": "1em",
132
- "font_weight": "bold",
133
- # "_pt_class": "markdown,table,border",
134
- },
135
- ((CssSelector(item="code"),),): {
136
- "display": "inline",
137
- "_pt_class": "markdown,code",
138
- },
139
- ((CssSelector(item=".math"),),): {"text_transform": "latex"},
140
- ((CssSelector(item=".block"),),): {"display": "block"},
141
- ((CssSelector(item=".math.block"),),): {
142
- "text_align": "center",
143
- },
144
- (
101
+ (
102
+ CssSelector(item="ul"),
103
+ CssSelector(item="li"),
104
+ CssSelector(item="::marker"),
105
+ ),
106
+ ): {"color": "ansiyellow"},
107
+ ((CssSelector(item="blockquote"),),): {
108
+ "margin_top": "1em",
109
+ "margin_bottom": "1em",
110
+ "padding_left": "1em",
111
+ "border_left_width": "thick",
112
+ "border_left_style": "solid",
113
+ "border_left_color": "darkmagenta",
114
+ },
115
+ ((CssSelector(item=".block"),),): {"display": "block"},
116
+ ((CssSelector(item="td"),),): {
117
+ "display": "table-cell",
118
+ "border_top_style": "solid",
119
+ "border_right_style": "solid",
120
+ "border_bottom_style": "solid",
121
+ "border_left_style": "solid",
122
+ "border_top_width": "0.1em",
123
+ "border_right_width": "0.1em",
124
+ "border_bottom_width": "0.1em",
125
+ "border_left_width": "0.1em",
126
+ "padding_left": "1em",
127
+ "padding_right": "1em",
128
+ # "_pt_class": "markdown,table,border",
129
+ },
130
+ ((CssSelector(item="th"),),): {
131
+ "display": "table-cell",
132
+ "border_top_style": "solid",
133
+ "border_right_style": "solid",
134
+ "border_bottom_style": "solid",
135
+ "border_left_style": "solid",
136
+ "border_top_width": "0.34em",
137
+ "border_right_width": "0.34em",
138
+ "border_bottom_width": "0.34em",
139
+ "border_left_width": "0.34em",
140
+ "padding_left": "1em",
141
+ "padding_right": "1em",
142
+ "font_weight": "bold",
143
+ # "_pt_class": "markdown,table,border",
144
+ },
145
+ ((CssSelector(item="code"),),): {
146
+ "display": "inline",
147
+ "_pt_class": "markdown,code",
148
+ },
149
+ ((CssSelector(item=".math"),),): {"text_transform": "latex"},
150
+ ((CssSelector(item=".block"),),): {"display": "block"},
151
+ ((CssSelector(item=".math.block"),),): {
152
+ "text_align": "center",
153
+ },
145
154
  (
146
- CssSelector(item="pre"),
147
- CssSelector(comb=">", item="code"),
148
- ),
149
- ): {
150
- "display": "block",
151
- "border_top_style": "solid",
152
- "border_top_width": "1px",
153
- "border_right_style": "solid",
154
- "border_right_width": "1px",
155
- "border_bottom_style": "solid",
156
- "border_bottom_width": "1px",
157
- "border_left_style": "solid",
158
- "border_left_width": "1px",
159
- "_pt_class": "markdown,code,block",
160
- },
161
- (
155
+ (
156
+ CssSelector(item="pre"),
157
+ CssSelector(comb=">", item="code"),
158
+ ),
159
+ ): {
160
+ "display": "block",
161
+ "border_top_style": "solid",
162
+ "border_top_width": "1px",
163
+ "border_right_style": "solid",
164
+ "border_right_width": "1px",
165
+ "border_bottom_style": "solid",
166
+ "border_bottom_width": "1px",
167
+ "border_left_style": "solid",
168
+ "border_left_width": "1px",
169
+ "_pt_class": "markdown,code,block",
170
+ },
162
171
  (
163
- CssSelector(item="pre"),
164
- CssSelector(comb=">", item="code"),
165
- CssSelector(item="*"),
166
- ),
167
- ): {
168
- "pt_class": "markdown,code,block",
169
- },
170
- ((CssSelector(item="img"),), (CssSelector(item="svg"),)): {
171
- "display": "inline-block",
172
- "overflow_x": "hidden",
173
- "overflow_y": "hidden",
174
- "vertical_align": "middle",
175
- },
172
+ (
173
+ CssSelector(item="pre"),
174
+ CssSelector(comb=">", item="code"),
175
+ CssSelector(item="*"),
176
+ ),
177
+ ): {
178
+ "pt_class": "markdown,code,block",
179
+ },
180
+ ((CssSelector(item="img"),), (CssSelector(item="svg"),)): {
181
+ "display": "inline-block",
182
+ "overflow_x": "hidden",
183
+ "overflow_y": "hidden",
184
+ "vertical_align": "middle",
185
+ },
186
+ }
176
187
  }
@@ -231,7 +231,7 @@ class DummyCell(Cell):
231
231
 
232
232
 
233
233
  class RowCol:
234
- """Bae class for table rows and columns."""
234
+ """Base class for table rows and columns."""
235
235
 
236
236
  type_: str
237
237
  span_type: str
@@ -513,6 +513,9 @@ def compute_border_width(cell: Cell, render_count: int = 0) -> DiInt:
513
513
  if isinstance(cell, DummyCell):
514
514
  return DiInt(0, 0, 0, 0)
515
515
 
516
+ if isinstance(cell, SpacerCell):
517
+ cell = cell.expands
518
+
516
519
  output = {"top": 1, "right": 1, "bottom": 1, "left": 1}
517
520
 
518
521
  row = cell.row
@@ -668,12 +671,12 @@ def calculate_col_widths(
668
671
  expand(width.preferred)
669
672
  elif current_width > width.preferred:
670
673
  contract(width.preferred)
671
- elif width.max_specified:
674
+ if width.max_specified:
672
675
  if current_width > width.max:
673
676
  contract(width.max)
674
677
  if current_width < width.max and expand_to_width:
675
678
  expand(width.max)
676
- elif width.min_specified and current_width < width.min:
679
+ if width.min_specified and current_width < width.min:
677
680
  expand(width.min)
678
681
 
679
682
  return col_widths
@@ -738,15 +741,17 @@ def compute_lines(
738
741
  align(
739
742
  wrap(
740
743
  [
741
- ("", "\n" * (padding.top or 0)),
744
+ *([("", "\n" * padding.top)] if padding.top else []),
742
745
  *compute_text(cell, render_count),
743
- ("", "\n" * (padding.bottom or 0)),
746
+ *([("", "\n" * padding.bottom)] if padding.bottom else []),
744
747
  ],
745
748
  width=width,
749
+ placeholder="",
746
750
  ),
747
751
  compute_align(cell, render_count),
748
752
  width=width,
749
753
  style=compute_style(cell, render_count),
754
+ placeholder="",
750
755
  )
751
756
  )
752
757
 
@@ -960,7 +965,7 @@ class Table:
960
965
  unfilled = [
961
966
  i
962
967
  for i, row in self._rows.items()
963
- if all([isinstance(cell, SpacerCell) for cell in row._cells.values()])
968
+ if all(isinstance(cell, SpacerCell) for cell in row._cells.values())
964
969
  ]
965
970
 
966
971
  index = min(
@@ -991,7 +996,7 @@ class Table:
991
996
  unfilled = [
992
997
  i
993
998
  for i, col in self._cols.items()
994
- if all([isinstance(cell, SpacerCell) for cell in col._cells.values()])
999
+ if all(isinstance(cell, SpacerCell) for cell in col._cells.values())
995
1000
  ]
996
1001
 
997
1002
  index = min(
@@ -1057,13 +1062,13 @@ class Table:
1057
1062
 
1058
1063
  node_style = " ".join(
1059
1064
  (
1060
- sw_bs.top,
1061
- sw_bs.right,
1062
1065
  ne_bs.bottom,
1066
+ sw_bs.right,
1067
+ sw_bs.top,
1063
1068
  ne_bs.left,
1064
- se_bs.top,
1065
- se_bs.left,
1066
1069
  nw_bs.bottom,
1070
+ se_bs.left,
1071
+ se_bs.top,
1067
1072
  nw_bs.right,
1068
1073
  )
1069
1074
  )
@@ -1111,8 +1116,8 @@ class Table:
1111
1116
  for w, e in zip([DummyCell(), *row.cells], [*row.cells, DummyCell()]):
1112
1117
  border_style = " ".join(
1113
1118
  (
1114
- compute_border_style(w, render_count).right,
1115
1119
  compute_border_style(e, render_count).left,
1120
+ compute_border_style(w, render_count).right,
1116
1121
  )
1117
1122
  )
1118
1123
  border_char = get_vertical_edge(
@@ -1166,7 +1171,8 @@ class Table:
1166
1171
  continue
1167
1172
  output_line.append(borders[i])
1168
1173
 
1169
- padding_style = f"{cell.style} nounderline"
1174
+ cell_style = compute_style(cell, render_count)
1175
+ padding_style = f"{cell_style} nounderline"
1170
1176
  padding = compute_padding(cell, render_count)
1171
1177
  padding_left, padding_right = padding.left, padding.right
1172
1178
 
@@ -1181,7 +1187,7 @@ class Table:
1181
1187
  [
1182
1188
  (padding_style, " " * (padding.left or 0)),
1183
1189
  *(line or []),
1184
- (cell.style, " " * excess),
1190
+ (cell_style, " " * excess),
1185
1191
  (padding_style, " " * (padding.right or 0)),
1186
1192
  ]
1187
1193
  )
@@ -12,6 +12,7 @@ from prompt_toolkit.formatted_text.utils import (
12
12
  split_lines,
13
13
  to_plain_text,
14
14
  )
15
+ from prompt_toolkit.layout.utils import explode_text_fragments
15
16
  from prompt_toolkit.utils import get_cwidth
16
17
  from pygments.lexers import get_lexer_by_name
17
18
  from pygments.util import ClassNotFound
@@ -101,16 +102,16 @@ def fragment_list_to_words(
101
102
  ) -> Iterable[StyleAndTextTuples]:
102
103
  """Split formatted text into a list of word fragments which form words."""
103
104
  word: StyleAndTextTuples = []
104
- for style, string, *_ in fragments:
105
+ for style, string, *rest in fragments:
105
106
  parts = re.split(r"(?<=[\s\-])", string)
106
107
  if len(parts) == 1:
107
- word.append((style, parts[0]))
108
+ word.append(cast("OneStyleAndTextTuple", (style, parts[0], *rest)))
108
109
  else:
109
110
  for part in parts[:-1]:
110
- word.append((style, part))
111
+ word.append(cast("OneStyleAndTextTuple", (style, part, *rest)))
111
112
  yield word[:]
112
113
  word.clear()
113
- word.append((style, parts[-1]))
114
+ word.append(cast("OneStyleAndTextTuple", (style, parts[-1], *rest)))
114
115
  if word:
115
116
  yield word
116
117
 
@@ -198,7 +199,7 @@ def truncate(
198
199
 
199
200
  """
200
201
  lines = split_lines(ft)
201
- if max(fragment_list_width(line) for line in lines) < width:
202
+ if max(fragment_list_width(line) for line in lines) <= width:
202
203
  return ft
203
204
  result: StyleAndTextTuples = []
204
205
  phw = sum(get_cwidth(c) for c in placeholder)
@@ -460,22 +461,32 @@ def pad(
460
461
 
461
462
 
462
463
  def paste(
463
- ft_bottom: StyleAndTextTuples,
464
464
  ft_top: StyleAndTextTuples,
465
+ ft_bottom: StyleAndTextTuples,
465
466
  row: int = 0,
466
467
  col: int = 0,
468
+ transparent: bool = False,
467
469
  ) -> StyleAndTextTuples:
468
470
  """Pate formatted text on top of other formatted text."""
469
471
  ft: StyleAndTextTuples = []
470
-
471
472
  top_lines = dict(enumerate(split_lines(ft_top), start=row))
472
473
  for y, line_b in enumerate(split_lines(ft_bottom)):
473
474
  if y in top_lines:
474
- # x = 0
475
475
  line_t = top_lines[y]
476
476
  line_t_width = fragment_list_width(line_t)
477
477
  ft += substring(line_b, 0, col)
478
- ft += substring(line_t)
478
+ if transparent:
479
+ chars_t = explode_text_fragments(line_t)
480
+ chars_b = explode_text_fragments(
481
+ substring(line_b, col, col + line_t_width)
482
+ )
483
+ for char_t, char_b in zip(chars_t, chars_b):
484
+ if char_t[0] == "" and char_t[1] == " ":
485
+ ft.append(char_b)
486
+ else:
487
+ ft.append(char_t)
488
+ else:
489
+ ft += line_t
479
490
  ft += substring(line_b, col + line_t_width)
480
491
  else:
481
492
  ft += line_b
@@ -529,8 +540,8 @@ def concat(
529
540
  ft_b = [*ft_b, ("", lines_below * "\n")]
530
541
 
531
542
  ft = paste(
532
- pad(ft_a, style=style),
533
543
  ft_b,
544
+ pad(ft_a, style=style),
534
545
  row=0,
535
546
  col=cols_a,
536
547
  )
euporie/core/io.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """Define custom inputs and outputs, and related methods."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import logging
4
6
  import re
5
7
  from typing import TYPE_CHECKING
@@ -9,7 +11,7 @@ from prompt_toolkit.input.base import DummyInput, _dummy_context_manager
9
11
  from prompt_toolkit.output.vt100 import Vt100_Output as PtkVt100_Output
10
12
 
11
13
  if TYPE_CHECKING:
12
- from typing import Any, Callable, ContextManager, Union
14
+ from typing import Any, Callable, ContextManager
13
15
 
14
16
  from prompt_toolkit.keys import Keys
15
17
 
@@ -30,7 +32,7 @@ _response_prefix_re = re.compile(
30
32
 
31
33
 
32
34
  class _IsPrefixOfLongerMatchCache(vt100_parser._IsPrefixOfLongerMatchCache):
33
- def __missing__(self, prefix: "str") -> "bool":
35
+ def __missing__(self, prefix: str) -> bool:
34
36
  """Check if the response might match an OSC or APC code, or DA response."""
35
37
  result = super().__missing__(prefix)
36
38
  if not result:
@@ -47,12 +49,12 @@ vt100_parser._IS_PREFIX_OF_LONGER_MATCH_CACHE = _IsPrefixOfLongerMatchCache()
47
49
  class Vt100Parser(vt100_parser.Vt100Parser):
48
50
  """A Vt100Parser which checks input against additional key patterns."""
49
51
 
50
- def __init__(self, *args: "Any", **kwargs: "Any") -> "None":
52
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
51
53
  """Create a new VT100 parser."""
52
54
  super().__init__(*args, **kwargs)
53
- self.queries: "dict[Keys, re.Pattern]" = {}
55
+ self.queries: dict[Keys, re.Pattern] = {}
54
56
 
55
- def _get_match(self, prefix: "str") -> "Union[None, Keys, tuple[Keys, ...]]":
57
+ def _get_match(self, prefix: str) -> None | Keys | tuple[Keys, ...]:
56
58
  """Check for additional key matches first."""
57
59
  for key, pattern in self.queries.items():
58
60
  if pattern.match(prefix):
@@ -64,9 +66,7 @@ class Vt100Parser(vt100_parser.Vt100Parser):
64
66
  class IgnoredInput(DummyInput):
65
67
  """An input which ignores input but does not immediately close the app."""
66
68
 
67
- def attach(
68
- self, input_ready_callback: "Callable[[], None]"
69
- ) -> "ContextManager[None]":
69
+ def attach(self, input_ready_callback: Callable[[], None]) -> ContextManager[None]:
70
70
  """Do not call the callback, so the input is never closed."""
71
71
  return _dummy_context_manager()
72
72
 
@@ -74,26 +74,26 @@ class IgnoredInput(DummyInput):
74
74
  class Vt100_Output(PtkVt100_Output):
75
75
  """A Vt100 output which enables SGR pixel mouse positioning."""
76
76
 
77
- def enable_mouse_support(self) -> "None":
77
+ def enable_mouse_support(self) -> None:
78
78
  """Additionally enable SGR-pixel mouse positioning."""
79
79
  super().enable_mouse_support()
80
80
  self.write_raw("\x1b[?1016h")
81
81
 
82
- def disable_mouse_support(self) -> "None":
82
+ def disable_mouse_support(self) -> None:
83
83
  """Additionally disable SGR-pixel mouse positioning."""
84
84
  super().disable_mouse_support()
85
85
  self.write_raw("\x1b[?1016l")
86
86
 
87
- def enable_extended_keys(self) -> "None":
87
+ def enable_extended_keys(self) -> None:
88
88
  """Request extended keys."""
89
89
  # xterm
90
90
  self.write_raw("\x1b[>4;1m")
91
91
  # kitty
92
- self.write_raw("\x1b[>1u")
92
+ self.write_raw("\x1b[=1u")
93
93
 
94
- def disable_extended_keys(self) -> "None":
94
+ def disable_extended_keys(self) -> None:
95
95
  """Disable extended keys."""
96
96
  # xterm
97
97
  self.write_raw("\x1b[>4;0m")
98
98
  # kitty
99
- self.write_raw("\x1b[<u")
99
+ self.write_raw("\x1b[=0u")