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.
Files changed (129) hide show
  1. euporie/console/_commands.py +143 -0
  2. euporie/console/_settings.py +58 -0
  3. euporie/console/app.py +25 -71
  4. euporie/console/tabs/console.py +267 -147
  5. euporie/core/__init__.py +1 -9
  6. euporie/core/__main__.py +31 -5
  7. euporie/core/_settings.py +104 -0
  8. euporie/core/app/__init__.py +3 -0
  9. euporie/core/app/_commands.py +70 -0
  10. euporie/core/app/_settings.py +427 -0
  11. euporie/core/{app.py → app/app.py} +214 -572
  12. euporie/core/app/base.py +51 -0
  13. euporie/core/{current.py → app/current.py} +13 -4
  14. euporie/core/app/cursor.py +35 -0
  15. euporie/core/app/dummy.py +12 -0
  16. euporie/core/app/launch.py +28 -0
  17. euporie/core/bars/__init__.py +11 -0
  18. euporie/core/bars/command.py +182 -0
  19. euporie/core/bars/menu.py +258 -0
  20. euporie/core/{widgets → bars}/search.py +154 -57
  21. euporie/core/{widgets → bars}/status.py +9 -26
  22. euporie/core/clipboard.py +19 -80
  23. euporie/core/comm/base.py +8 -6
  24. euporie/core/comm/ipywidgets.py +21 -12
  25. euporie/core/comm/registry.py +2 -1
  26. euporie/core/commands.py +11 -5
  27. euporie/core/completion.py +3 -2
  28. euporie/core/config.py +368 -341
  29. euporie/core/convert/__init__.py +0 -30
  30. euporie/core/convert/datum.py +131 -60
  31. euporie/core/convert/formats/__init__.py +31 -0
  32. euporie/core/convert/formats/ansi.py +46 -30
  33. euporie/core/convert/formats/common.py +11 -23
  34. euporie/core/convert/formats/html.py +45 -40
  35. euporie/core/convert/formats/pil.py +1 -1
  36. euporie/core/convert/formats/png.py +3 -5
  37. euporie/core/convert/formats/sixel.py +3 -3
  38. euporie/core/convert/registry.py +11 -8
  39. euporie/core/convert/utils.py +50 -23
  40. euporie/core/diagnostics.py +2 -2
  41. euporie/core/filters.py +72 -82
  42. euporie/core/format.py +13 -2
  43. euporie/core/ft/ansi.py +1 -1
  44. euporie/core/ft/html.py +36 -36
  45. euporie/core/ft/table.py +1 -3
  46. euporie/core/ft/utils.py +4 -1
  47. euporie/core/graphics.py +216 -124
  48. euporie/core/history.py +2 -2
  49. euporie/core/inspection.py +3 -2
  50. euporie/core/io.py +207 -28
  51. euporie/core/kernel/__init__.py +1 -0
  52. euporie/core/{kernel.py → kernel/client.py} +100 -139
  53. euporie/core/kernel/manager.py +114 -0
  54. euporie/core/key_binding/bindings/__init__.py +2 -8
  55. euporie/core/key_binding/bindings/basic.py +47 -7
  56. euporie/core/key_binding/bindings/completion.py +3 -8
  57. euporie/core/key_binding/bindings/micro.py +5 -7
  58. euporie/core/key_binding/bindings/mouse.py +26 -24
  59. euporie/core/key_binding/bindings/terminal.py +193 -0
  60. euporie/core/key_binding/bindings/vi.py +46 -0
  61. euporie/core/key_binding/key_processor.py +43 -2
  62. euporie/core/key_binding/registry.py +2 -0
  63. euporie/core/key_binding/utils.py +22 -2
  64. euporie/core/keys.py +7156 -92
  65. euporie/core/layout/cache.py +35 -25
  66. euporie/core/layout/containers.py +280 -74
  67. euporie/core/layout/decor.py +5 -5
  68. euporie/core/layout/mouse.py +1 -1
  69. euporie/core/layout/print.py +16 -3
  70. euporie/core/layout/scroll.py +26 -28
  71. euporie/core/log.py +75 -60
  72. euporie/core/lsp.py +118 -24
  73. euporie/core/margins.py +60 -31
  74. euporie/core/path.py +2 -1
  75. euporie/core/renderer.py +58 -17
  76. euporie/core/style.py +60 -40
  77. euporie/core/suggest.py +103 -85
  78. euporie/core/tabs/__init__.py +34 -0
  79. euporie/core/tabs/_settings.py +113 -0
  80. euporie/core/tabs/base.py +11 -435
  81. euporie/core/tabs/kernel.py +420 -0
  82. euporie/core/tabs/notebook.py +20 -54
  83. euporie/core/utils.py +98 -6
  84. euporie/core/validation.py +1 -1
  85. euporie/core/widgets/_settings.py +188 -0
  86. euporie/core/widgets/cell.py +90 -158
  87. euporie/core/widgets/cell_outputs.py +26 -37
  88. euporie/core/widgets/decor.py +11 -41
  89. euporie/core/widgets/dialog.py +55 -44
  90. euporie/core/widgets/display.py +27 -24
  91. euporie/core/widgets/file_browser.py +5 -26
  92. euporie/core/widgets/forms.py +16 -12
  93. euporie/core/widgets/inputs.py +37 -81
  94. euporie/core/widgets/layout.py +7 -6
  95. euporie/core/widgets/logo.py +49 -0
  96. euporie/core/widgets/menu.py +13 -11
  97. euporie/core/widgets/pager.py +9 -11
  98. euporie/core/widgets/palette.py +6 -6
  99. euporie/hub/app.py +52 -31
  100. euporie/notebook/_commands.py +24 -0
  101. euporie/notebook/_settings.py +107 -0
  102. euporie/notebook/app.py +109 -210
  103. euporie/notebook/filters.py +1 -1
  104. euporie/notebook/tabs/__init__.py +46 -7
  105. euporie/notebook/tabs/_commands.py +714 -0
  106. euporie/notebook/tabs/_settings.py +32 -0
  107. euporie/notebook/tabs/display.py +2 -2
  108. euporie/notebook/tabs/edit.py +12 -7
  109. euporie/notebook/tabs/json.py +3 -3
  110. euporie/notebook/tabs/log.py +1 -18
  111. euporie/notebook/tabs/notebook.py +21 -674
  112. euporie/notebook/widgets/_commands.py +11 -0
  113. euporie/notebook/widgets/_settings.py +19 -0
  114. euporie/notebook/widgets/side_bar.py +14 -34
  115. euporie/preview/_settings.py +104 -0
  116. euporie/preview/app.py +8 -30
  117. euporie/preview/tabs/notebook.py +15 -86
  118. euporie/web/tabs/web.py +4 -6
  119. euporie/web/widgets/webview.py +5 -12
  120. {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/METADATA +11 -15
  121. euporie-2.8.5.dist-info/RECORD +172 -0
  122. {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/WHEEL +1 -1
  123. {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/entry_points.txt +2 -2
  124. {euporie-2.8.0.dist-info → euporie-2.8.5.dist-info}/licenses/LICENSE +1 -1
  125. euporie/core/launch.py +0 -59
  126. euporie/core/terminal.py +0 -527
  127. euporie-2.8.0.dist-info/RECORD +0 -146
  128. {euporie-2.8.0.data → euporie-2.8.5.data}/data/share/applications/euporie-console.desktop +0 -0
  129. {euporie-2.8.0.data → euporie-2.8.5.data}/data/share/applications/euporie-notebook.desktop +0 -0
@@ -5,22 +5,26 @@ from __future__ import annotations
5
5
  import logging
6
6
  from typing import TYPE_CHECKING
7
7
 
8
+ from prompt_toolkit.buffer import Buffer
9
+ from prompt_toolkit.document import Document
8
10
  from prompt_toolkit.filters.app import is_searching
11
+ from prompt_toolkit.filters.base import Condition
12
+ from prompt_toolkit.formatted_text.base import to_formatted_text
9
13
  from prompt_toolkit.key_binding.vi_state import InputMode
10
14
  from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl
11
15
  from prompt_toolkit.search import SearchDirection
12
16
  from prompt_toolkit.selection import SelectionState
13
17
  from prompt_toolkit.widgets import SearchToolbar as PtkSearchToolbar
14
18
 
19
+ from euporie.core.app.current import get_app
20
+ from euporie.core.bars import SEARCH_BAR_BUFFER
15
21
  from euporie.core.commands import add_cmd
16
- from euporie.core.current import get_app
17
22
  from euporie.core.key_binding.registry import (
18
23
  load_registered_bindings,
19
24
  register_bindings,
20
25
  )
21
26
 
22
27
  if TYPE_CHECKING:
23
- from prompt_toolkit.buffer import Buffer
24
28
  from prompt_toolkit.filters import FilterOrBool
25
29
  from prompt_toolkit.formatted_text.base import AnyFormattedText
26
30
 
@@ -38,34 +42,41 @@ class SearchBar(PtkSearchToolbar):
38
42
  search_buffer: Buffer | None = None,
39
43
  vi_mode: bool = False,
40
44
  text_if_not_searching: AnyFormattedText = "",
41
- forward_search_prompt: AnyFormattedText = "I-search: ",
42
- backward_search_prompt: AnyFormattedText = "I-search backward: ",
45
+ forward_search_prompt: AnyFormattedText = " Find: ",
46
+ backward_search_prompt: AnyFormattedText = " Find (up): ",
43
47
  ignore_case: FilterOrBool = False,
44
48
  ) -> None:
45
49
  """Create a new search bar instance."""
50
+ if search_buffer is None:
51
+ search_buffer = Buffer(name=SEARCH_BAR_BUFFER)
46
52
  super().__init__(
47
- text_if_not_searching="",
48
- forward_search_prompt=[
49
- ("class:search-toolbar.title", " Find: "),
50
- ("", " "),
51
- ],
52
- backward_search_prompt=[
53
- ("class:search-toolbar.title", " Find (up): "),
54
- ("", " "),
55
- ],
53
+ search_buffer=search_buffer,
54
+ vi_mode=vi_mode,
55
+ text_if_not_searching=text_if_not_searching,
56
+ forward_search_prompt=to_formatted_text(
57
+ forward_search_prompt, "class:status-field"
58
+ ),
59
+ backward_search_prompt=to_formatted_text(
60
+ backward_search_prompt, "class:status-field"
61
+ ),
56
62
  )
57
63
  self.control.key_bindings = load_registered_bindings(
58
- "euporie.core.widgets.search.SearchBar"
64
+ "euporie.core.bars.search.SearchBar",
65
+ config=get_app().config,
66
+ )
67
+ search_state = self.control.searcher_search_state
68
+ search_state.ignore_case = Condition(
69
+ lambda: self.search_buffer.text.islower() or search_state.text.islower()
59
70
  )
60
71
 
61
72
  register_bindings(
62
73
  {
63
- "euporie.core.app.BaseApp": {
74
+ "euporie.core.app.app:BaseApp": {
64
75
  "find": ["c-f", "f3", "f7"],
65
76
  "find-next": "c-g",
66
77
  "find-previous": "c-p",
67
78
  },
68
- "euporie.core.widgets.search.SearchBar": {
79
+ "euporie.core.bars.search.SearchBar": {
69
80
  "accept-search": "enter",
70
81
  "stop-search": "escape",
71
82
  },
@@ -73,28 +84,44 @@ class SearchBar(PtkSearchToolbar):
73
84
  )
74
85
 
75
86
 
76
- def start_global_search(
77
- buffer_control: BufferControl | None = None,
78
- direction: SearchDirection = SearchDirection.FORWARD,
79
- ) -> None:
80
- """Start a search through all searchable `buffer_controls` in the layout."""
87
+ def find_search_control() -> tuple[SearchBufferControl | None, BufferControl | None]:
88
+ """Find the current search buffer and buffer control."""
89
+ current_buffer_control: BufferControl | None = None
90
+ search_buffer_control: SearchBufferControl | None = None
91
+
81
92
  app = get_app()
82
93
  layout = app.layout
83
- current_control = layout.current_control
84
- # Find the search buffer control
85
- if app.search_bar is not None:
94
+ current_control = app.layout.current_control
95
+
96
+ if isinstance(current_control, SearchBufferControl):
97
+ search_buffer_control = current_control
98
+
99
+ if search_buffer_control is None and app.search_bar is not None:
86
100
  search_buffer_control = app.search_bar.control
87
- elif (
88
- isinstance(current_control, BufferControl)
89
- and current_control.search_buffer_control is not None
101
+
102
+ if search_buffer_control is not None and current_buffer_control is None:
103
+ current_buffer_control = layout.search_links.get(search_buffer_control)
104
+
105
+ if current_buffer_control is None and isinstance(current_control, BufferControl):
106
+ current_buffer_control = current_control
107
+
108
+ if (
109
+ search_buffer_control is None
110
+ and current_buffer_control is not None
111
+ and current_buffer_control.search_buffer_control is not None
90
112
  ):
91
- search_buffer_control = current_control.search_buffer_control
92
- else:
93
- return
94
- # Find all searchable controls
113
+ search_buffer_control = current_buffer_control.search_buffer_control
114
+
115
+ return search_buffer_control, current_buffer_control
116
+
117
+
118
+ def find_searchable_controls(
119
+ search_buffer_control: SearchBufferControl, current_control: BufferControl | None
120
+ ) -> list[BufferControl]:
121
+ """Find list of searchable controls and the index of the next control."""
95
122
  searchable_controls: list[BufferControl] = []
96
123
  next_control_index = 0
97
- for control in layout.find_all_controls():
124
+ for control in get_app().layout.find_all_controls():
98
125
  # Find the index of the next searchable control so we can link the search
99
126
  # control to it if the currently focused control is not searchable. This is so
100
127
  # that the next searchable control can be focused when search is completed.
@@ -105,24 +132,40 @@ def start_global_search(
105
132
  isinstance(control, BufferControl)
106
133
  and control.search_buffer_control == search_buffer_control
107
134
  ):
108
- # Set its search direction
109
- control.search_state.direction = direction
110
135
  # Add it to our list
111
136
  searchable_controls.append(control)
137
+ searchable_controls = (
138
+ searchable_controls[next_control_index:]
139
+ + searchable_controls[:next_control_index]
140
+ )
141
+ return searchable_controls
142
+
143
+
144
+ def start_global_search(
145
+ buffer_control: BufferControl | None = None,
146
+ direction: SearchDirection = SearchDirection.FORWARD,
147
+ ) -> None:
148
+ """Start a search through all searchable `buffer_controls` in the layout."""
149
+ search_buffer_control, current_control = find_search_control()
150
+ if search_buffer_control is None:
151
+ return
152
+ searchable_controls = find_searchable_controls(
153
+ search_buffer_control, current_control
154
+ )
112
155
 
113
156
  # Stop the search if we did not find any searchable controls
114
157
  if not searchable_controls:
115
158
  return
116
159
 
117
160
  # If the current control is searchable, link it
161
+ app = get_app()
162
+ layout = app.layout
118
163
  if current_control in searchable_controls:
119
164
  assert isinstance(current_control, BufferControl)
120
165
  layout.search_links[search_buffer_control] = current_control
121
166
  else:
122
167
  # otherwise use the next after the currently selected control
123
- layout.search_links[search_buffer_control] = searchable_controls[
124
- next_control_index % len(searchable_controls)
125
- ]
168
+ layout.search_links[search_buffer_control] = searchable_controls[0]
126
169
  # Make sure to focus the search BufferControl
127
170
  layout.focus(search_buffer_control)
128
171
  # If we're in Vi mode, make sure to go into insert mode.
@@ -137,31 +180,85 @@ def find() -> None:
137
180
 
138
181
  def find_prev_next(direction: SearchDirection) -> None:
139
182
  """Find the previous or next search match."""
140
- app = get_app()
141
- layout = app.layout
142
- control = app.layout.current_control
143
- # Determine search buffer and searched buffer
144
- search_buffer_control = None
145
- if isinstance(control, SearchBufferControl):
146
- search_buffer_control = control
147
- control = layout.search_links[search_buffer_control]
148
- elif isinstance(control, BufferControl):
149
- if control.search_buffer_control is not None:
150
- search_buffer_control = control.search_buffer_control
151
- elif app.search_bar is not None:
152
- search_buffer_control = app.search_bar.control
153
- if isinstance(control, BufferControl) and search_buffer_control is not None:
183
+ if is_searching():
184
+ accept_search()
185
+
186
+ search_buffer_control, current_control = find_search_control()
187
+ if search_buffer_control is None:
188
+ return
189
+ searchable_controls = find_searchable_controls(
190
+ search_buffer_control, current_control
191
+ )
192
+
193
+ if direction == SearchDirection.BACKWARD:
194
+ searchable_controls = searchable_controls[:1] + searchable_controls[1:][::-1]
195
+
196
+ # Search over all searchable buffers
197
+ for i, control in enumerate(searchable_controls):
154
198
  # Update search_state.
155
199
  search_state = control.search_state
156
200
  search_state.direction = direction
157
201
  # Apply search to buffer
158
202
  buffer = control.buffer
159
- buffer.apply_search(search_state, include_current_position=False, count=1)
160
- # Set selection
161
- buffer.selection_state = SelectionState(
162
- buffer.cursor_position + len(search_state.text)
163
- )
164
- buffer.selection_state.enter_shift_mode()
203
+
204
+ search_result: tuple[int, int] | None = None
205
+
206
+ # If we are searching history, use the PTK buffer search implementation
207
+ if buffer.enable_history_search():
208
+ search_result = buffer._search(search_state)
209
+
210
+ # Otherwise, only search the buffer's current "working line"
211
+ else:
212
+ document = buffer.document
213
+ # If have move to the next buffer, set the cursor position for the start of
214
+ # the search to the start or the end of the text, depending on if we are
215
+ # searching forwards or backwards
216
+ if i > 0:
217
+ if direction == SearchDirection.FORWARD:
218
+ document = Document(document.text, 0)
219
+ else:
220
+ document = Document(document.text, len(document.text))
221
+
222
+ text = search_state.text
223
+ ignore_case = search_state.ignore_case()
224
+
225
+ if direction == SearchDirection.FORWARD:
226
+ # Try find at the current input.
227
+ new_index = document.find(
228
+ text,
229
+ # If we have moved to the next buffer, include the current position
230
+ # which will be the start of the document text
231
+ include_current_position=i > 0,
232
+ ignore_case=ignore_case,
233
+ )
234
+ if new_index is not None:
235
+ search_result = (
236
+ buffer.working_index,
237
+ document.cursor_position + new_index,
238
+ )
239
+ else:
240
+ # Try find at the current input.
241
+ new_index = document.find_backwards(text, ignore_case=ignore_case)
242
+ if new_index is not None:
243
+ search_result = (
244
+ buffer.working_index,
245
+ document.cursor_position + new_index,
246
+ )
247
+
248
+ if search_result is not None:
249
+ working_index, cursor_position = search_result
250
+ buffer.working_index = working_index
251
+ buffer.cursor_position = cursor_position
252
+ # Set SelectionState
253
+ buffer.selection_state = SelectionState(
254
+ buffer.cursor_position + len(search_state.text)
255
+ )
256
+ buffer.selection_state.enter_shift_mode()
257
+
258
+ # Trigger a cursor position changed event on this buffer
259
+ buffer._cursor_position_changed()
260
+
261
+ break
165
262
 
166
263
 
167
264
  @add_cmd()
@@ -16,13 +16,13 @@ from prompt_toolkit.layout.containers import (
16
16
  )
17
17
  from prompt_toolkit.layout.controls import FormattedTextControl
18
18
 
19
- from euporie.core.config import add_setting
20
- from euporie.core.current import get_app
21
- from euporie.core.filters import is_searching
19
+ from euporie.core.app.current import get_app
20
+ from euporie.core.filters import has_toolbar
22
21
  from euporie.core.layout.containers import VSplit, Window
23
22
 
24
23
  if TYPE_CHECKING:
25
- from typing import Callable, Sequence
24
+ from collections.abc import Sequence
25
+ from typing import Callable
26
26
 
27
27
  from prompt_toolkit.filters.base import FilterOrBool
28
28
  from prompt_toolkit.formatted_text import StyleAndTextTuples
@@ -77,9 +77,9 @@ class StatusBar:
77
77
  ) -> None:
78
78
  """Create a new status bar instance."""
79
79
  self.default: StatusBarFields = default or ([], [])
80
- self._status_cache: FastDictCache[
81
- tuple[int], list[StyleAndTextTuples]
82
- ] = FastDictCache(self._status, size=1)
80
+ self._status_cache: FastDictCache[tuple[int], list[StyleAndTextTuples]] = (
81
+ FastDictCache(self._status, size=1)
82
+ )
83
83
  self.container = ConditionalContainer(
84
84
  content=VSplit(
85
85
  [
@@ -99,8 +99,8 @@ class StatusBar:
99
99
  ],
100
100
  height=1,
101
101
  ),
102
- filter=get_app().config.filter("show_status_bar")
103
- & ~is_searching
102
+ filter=get_app().config.filters.show_status_bar
103
+ & ~has_toolbar
104
104
  & to_filter(extra_filter),
105
105
  )
106
106
 
@@ -144,20 +144,3 @@ class StatusBar:
144
144
  def __pt_container__(self) -> AnyContainer:
145
145
  """Return the widget's container."""
146
146
  return self.container
147
-
148
- # ################################### Settings ####################################
149
-
150
- add_setting(
151
- name="show_status_bar",
152
- flags=["--show-status-bar"],
153
- type_=bool,
154
- title="status bar",
155
- help_="Show the status bar",
156
- default=True,
157
- schema={
158
- "type": "boolean",
159
- },
160
- description="""
161
- Whether the status bar should be shown at the bottom of the screen.
162
- """,
163
- )
euporie/core/clipboard.py CHANGED
@@ -3,21 +3,15 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- from typing import TYPE_CHECKING
7
6
 
8
7
  from prompt_toolkit.clipboard.base import Clipboard, ClipboardData
9
8
  from prompt_toolkit.clipboard.in_memory import InMemoryClipboard
10
9
  from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard
11
10
  from prompt_toolkit.selection import SelectionType
12
- from pyperclip import determine_clipboard
13
11
 
14
- from euporie.core.config import add_setting
12
+ from euporie.core.app.current import get_app
15
13
  from euporie.core.io import Vt100_Output
16
-
17
- if TYPE_CHECKING:
18
- from euporie.core.app import BaseApp
19
- from euporie.core.config import Setting
20
- from euporie.core.terminal import TerminalQuery
14
+ from euporie.core.key_binding.key_processor import KeyProcessor
21
15
 
22
16
  log = logging.getLogger(__name__)
23
17
 
@@ -25,16 +19,13 @@ log = logging.getLogger(__name__)
25
19
  class Osc52Clipboard(Clipboard):
26
20
  """Clipboard that syncs with the system clipboard using OSC52 escape codes."""
27
21
 
28
- def __init__(self, app: BaseApp) -> None:
22
+ def __init__(self) -> None:
29
23
  """Create a new instance of the clipboard."""
30
- self.app = app
31
- term_clipboard_data = app.term_info.clipboard_data
32
- term_clipboard_data.event += self._update_clipboard
33
- self._data = ClipboardData(text=term_clipboard_data.value)
24
+ self._data = ClipboardData()
34
25
 
35
26
  def set_data(self, data: ClipboardData) -> None:
36
27
  """Set clipboard data."""
37
- output = self.app.output
28
+ output = get_app().output
38
29
  if isinstance(output, Vt100_Output):
39
30
  output.set_clipboard(data.text)
40
31
  output.flush()
@@ -43,16 +34,21 @@ class Osc52Clipboard(Clipboard):
43
34
  def get_data(self) -> ClipboardData:
44
35
  """Retrieve clipboard data."""
45
36
  # Send clipboard query
46
- output = self.app.output
37
+ app = get_app()
38
+ output = app.output
39
+ # Request clipboard contents from terminal
47
40
  if isinstance(output, Vt100_Output):
41
+ from euporie.core.keys import MoreKeys
42
+
48
43
  output.get_clipboard()
49
44
  output.flush()
50
- self.app.term_info.clipboard_data.await_response()
45
+ # Wait for terminal response
46
+ if isinstance(app.key_processor, KeyProcessor):
47
+ app.key_processor.await_key(MoreKeys.ClipboardDataResponse)
51
48
  return self._data
52
49
 
53
- def _update_clipboard(self, query: TerminalQuery) -> None:
50
+ def sync(self, text: str) -> None:
54
51
  """Update the last known clipboard data."""
55
- text = query.value
56
52
  if text != self._data.text:
57
53
  self._data = ClipboardData(
58
54
  text=text,
@@ -60,65 +56,8 @@ class Osc52Clipboard(Clipboard):
60
56
  )
61
57
 
62
58
 
63
- class ConfiguredClipboard(Clipboard):
64
- """Use a clipboard determined by euporie's configuration."""
65
-
66
- _clipboard: Clipboard
67
-
68
- def __init__(self, app: BaseApp) -> None:
69
- """Create a new clipboard instance."""
70
- self.app = app
71
- clipboard_config = app.config.settings["clipboard"]
72
- self.get_clipboard(clipboard_config)
73
- clipboard_config.event += self.get_clipboard
74
-
75
- def get_clipboard(self, setting: Setting) -> None:
76
- """Determine which clipboard to use."""
77
- clipboard: Clipboard | None = None
78
- if setting.value == "external" and determine_clipboard()[0]:
79
- log.debug("Using pyperclip clipboard")
80
- clipboard = PyperclipClipboard()
81
- if not clipboard:
82
- if setting.value == "terminal":
83
- log.debug("Using terminal clipboard")
84
- clipboard = Osc52Clipboard(self.app)
85
- else:
86
- log.debug("Using in-memory clipboard")
87
- clipboard = InMemoryClipboard()
88
- self._clipboard = clipboard
89
-
90
- def set_data(self, data: ClipboardData) -> None:
91
- """Set data to the clipboard."""
92
- self._clipboard.set_data(data)
93
-
94
- def set_text(self, text: str) -> None:
95
- """Shortcut for setting plain text on clipboard."""
96
- self._clipboard.set_text(text)
97
-
98
- def rotate(self) -> None:
99
- """For Emacs mode, rotate the kill ring."""
100
- self._clipboard.rotate()
101
-
102
- def get_data(self) -> ClipboardData:
103
- """Return clipboard data."""
104
- return self._clipboard.get_data()
105
-
106
- # ################################### Settings ####################################w
107
-
108
- add_setting(
109
- name="clipboard",
110
- flags=["--clipboard"],
111
- choices=["external", "internal", "terminal"],
112
- type_=str,
113
- default="external",
114
- help_="The preferred clipboard access method",
115
- description="""
116
- The clipboard access method to use.
117
- - ``external``: Data is saved to the system clipboard using OS native tooling.
118
- - ``internal``: Clipboard data is only stored and usable inside euporie - it is
119
- not saved to the system clipboard.
120
- - ``terminal``: uses OSC52 escape sequences to retrieve and set the clipboard
121
- contents. Requires your terminal emulator to support OSC52. Works over SSH.
122
-
123
- """,
124
- )
59
+ CONFIGURED_CLIPBOARDS = {
60
+ "internal": InMemoryClipboard,
61
+ "external": PyperclipClipboard,
62
+ "terminal": Osc52Clipboard,
63
+ }
euporie/core/comm/base.py CHANGED
@@ -7,17 +7,16 @@ from abc import ABCMeta, abstractmethod
7
7
  from typing import TYPE_CHECKING
8
8
  from weakref import WeakKeyDictionary
9
9
 
10
- from euporie.core.convert.datum import Datum
11
- from euporie.core.current import get_app
12
- from euporie.core.widgets.display import Display
10
+ from euporie.core.app.current import get_app
13
11
 
14
12
  if TYPE_CHECKING:
15
- from typing import Any, Callable, Mapping, Sequence
13
+ from collections.abc import Mapping, Sequence
14
+ from typing import Any, Callable
16
15
 
17
16
  from prompt_toolkit.layout.containers import AnyContainer
18
17
 
19
- from euporie.core.kernel import Kernel
20
- from euporie.core.tabs.base import KernelTab
18
+ from euporie.core.kernel.client import Kernel
19
+ from euporie.core.tabs.kernel import KernelTab
21
20
  from euporie.core.widgets.cell_outputs import OutputParent
22
21
 
23
22
 
@@ -97,6 +96,9 @@ class Comm(metaclass=ABCMeta):
97
96
 
98
97
  def create_view(self, parent: OutputParent) -> CommView:
99
98
  """Create a new :class:`CommView` for this Comm."""
99
+ from euporie.core.convert.datum import Datum
100
+ from euporie.core.widgets.display import Display
101
+
100
102
  return CommView(Display(Datum("[Object cannot be rendered]", format="ansi")))
101
103
 
102
104
  def new_view(self, parent: OutputParent) -> CommView:
@@ -18,12 +18,9 @@ from prompt_toolkit.layout.containers import HSplit, VSplit
18
18
  from prompt_toolkit.layout.processors import BeforeInput
19
19
 
20
20
  from euporie.core.comm.base import Comm, CommView
21
- from euporie.core.convert.datum import Datum
22
21
  from euporie.core.data_structures import DiBool
23
- from euporie.core.kernel import MsgCallbacks
22
+ from euporie.core.kernel.client import MsgCallbacks
24
23
  from euporie.core.layout.decor import FocusedStyle
25
- from euporie.core.widgets.cell_outputs import CellOutputArea
26
- from euporie.core.widgets.display import Display
27
24
  from euporie.core.widgets.forms import (
28
25
  Button,
29
26
  Checkbox,
@@ -46,13 +43,14 @@ from euporie.core.widgets.layout import (
46
43
  )
47
44
 
48
45
  if TYPE_CHECKING:
49
- from typing import Any, Iterable, MutableSequence, Sequence
46
+ from collections.abc import Iterable, MutableSequence, Sequence
47
+ from typing import Any
50
48
 
51
49
  from prompt_toolkit.buffer import Buffer
52
50
  from prompt_toolkit.formatted_text.base import AnyFormattedText
53
51
  from prompt_toolkit.layout.containers import AnyContainer, _Split
54
52
 
55
- from euporie.core.comm.base import KernelTab
53
+ from euporie.core.tabs.kernel import KernelTab
56
54
  from euporie.core.widgets.cell_outputs import OutputParent
57
55
  from euporie.core.widgets.forms import SelectableWidget, ToggleableWidget
58
56
  from euporie.core.widgets.layout import StackedSplit
@@ -122,7 +120,7 @@ def _separate_buffers(
122
120
  cloned_substrate = dict(substate) # clone list/tuple
123
121
  cloned_substrate[k] = vnew
124
122
  else:
125
- raise ValueError("expected state to be a list or dict, not %r" % substate)
123
+ raise ValueError(f"Expected state to be a list or dict, not {substate!r}")
126
124
  return cloned_substrate if cloned_substrate is not None else substate
127
125
 
128
126
 
@@ -213,6 +211,9 @@ class UnimplementedModel(IpyWidgetComm):
213
211
 
214
212
  def create_view(self, parent: OutputParent) -> CommView:
215
213
  """Create a new view."""
214
+ from euporie.core.convert.datum import Datum
215
+ from euporie.core.widgets.display import Display
216
+
216
217
  return CommView(Display(Datum("[Widget not implemented]", format="ansi")))
217
218
 
218
219
 
@@ -242,6 +243,8 @@ class OutputModel(IpyWidgetComm):
242
243
 
243
244
  def create_view(self, parent: OutputParent) -> CommView:
244
245
  """Create a new view of this output ipywidget."""
246
+ from euporie.core.widgets.cell_outputs import CellOutputArea
247
+
245
248
  container = CellOutputArea(
246
249
  self.data.get("state", {}).get("outputs", []), parent
247
250
  )
@@ -250,7 +253,7 @@ class OutputModel(IpyWidgetComm):
250
253
  {"outputs": partial(setattr, container, "json")},
251
254
  )
252
255
 
253
- def add_output(self, json: dict[str, Any]) -> None:
256
+ def add_output(self, json: dict[str, Any], own: bool) -> None:
254
257
  """Add a new output to this widget."""
255
258
  if self.clear_output_wait:
256
259
  self.set_state("outputs", [json])
@@ -285,9 +288,9 @@ class OutputModel(IpyWidgetComm):
285
288
  else:
286
289
  # Restore the message's callbacks
287
290
  if self.original_callbacks:
288
- self.comm_container.kernel.msg_id_callbacks[
289
- self.prev_msg_id
290
- ] = self.original_callbacks
291
+ self.comm_container.kernel.msg_id_callbacks[self.prev_msg_id] = (
292
+ self.original_callbacks
293
+ )
291
294
  self.original_callbacks = MsgCallbacks()
292
295
  else:
293
296
  del self.comm_container.kernel.msg_id_callbacks[self.prev_msg_id]
@@ -1258,6 +1261,9 @@ class HTMLModel(IpyWidgetComm):
1258
1261
 
1259
1262
  def create_view(self, parent: OutputParent) -> CommView:
1260
1263
  """Create a new view of the HTML widget."""
1264
+ from euporie.core.convert.datum import Datum
1265
+ from euporie.core.widgets.display import Display
1266
+
1261
1267
  html = Display(
1262
1268
  Datum(data=self.data["state"].get("value", ""), format="html"),
1263
1269
  dont_extend_width=True,
@@ -1285,6 +1291,9 @@ class ImageModel(IpyWidgetComm):
1285
1291
 
1286
1292
  def create_view(self, parent: OutputParent) -> CommView:
1287
1293
  """Create a new view of the image widget."""
1294
+ from euporie.core.convert.datum import Datum
1295
+ from euporie.core.widgets.display import Display
1296
+
1288
1297
  display = Display(
1289
1298
  Datum(
1290
1299
  data=self.data["state"].get("value", b""),
@@ -1408,7 +1417,7 @@ class ColorPickerModel(TextBoxIpyWidgetComm):
1408
1417
  if value in NAMED_COLORS:
1409
1418
  value = NAMED_COLORS[value]
1410
1419
  elif 4 <= len(value) < 7:
1411
- value = f"#{value[1]*2}{value[2]*2}{value[3]*2}"
1420
+ value = f"#{value[1] * 2}{value[2] * 2}{value[3] * 2}"
1412
1421
  else:
1413
1422
  value = value[:7]
1414
1423
  return value
@@ -8,7 +8,8 @@ from euporie.core.comm.base import UnimplementedComm
8
8
  from euporie.core.comm.ipywidgets import open_comm_ipywidgets
9
9
 
10
10
  if TYPE_CHECKING:
11
- from typing import Any, Callable, Sequence
11
+ from collections.abc import Sequence
12
+ from typing import Any, Callable
12
13
 
13
14
  from euporie.core.comm.base import Comm, KernelTab
14
15