euporie 2.8.5__tar.gz → 2.8.7__tar.gz

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 (180) hide show
  1. {euporie-2.8.5 → euporie-2.8.7}/.gitignore +1 -142
  2. {euporie-2.8.5 → euporie-2.8.7}/LICENSE +1 -1
  3. {euporie-2.8.5 → euporie-2.8.7}/PKG-INFO +4 -2
  4. {euporie-2.8.5 → euporie-2.8.7}/README.rst +3 -1
  5. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/app.py +2 -0
  6. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/tabs/console.py +27 -17
  7. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/__init__.py +2 -2
  8. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/__main__.py +2 -2
  9. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/_settings.py +7 -2
  10. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/_commands.py +20 -12
  11. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/_settings.py +34 -4
  12. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/app.py +31 -18
  13. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/bars/command.py +53 -27
  14. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/bars/search.py +43 -2
  15. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/border.py +7 -2
  16. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/comm/base.py +2 -2
  17. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/comm/ipywidgets.py +3 -3
  18. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/commands.py +44 -24
  19. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/completion.py +14 -6
  20. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/datum.py +7 -7
  21. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/data_structures.py +20 -1
  22. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/filters.py +40 -9
  23. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/format.py +2 -3
  24. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/ft/html.py +47 -40
  25. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/graphics.py +199 -31
  26. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/history.py +15 -5
  27. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/inspection.py +16 -9
  28. euporie-2.8.7/euporie/core/kernel/__init__.py +53 -0
  29. euporie-2.8.7/euporie/core/kernel/base.py +571 -0
  30. euporie-2.8.5/euporie/core/kernel/client.py → euporie-2.8.7/euporie/core/kernel/jupyter.py +173 -430
  31. euporie-2.8.5/euporie/core/kernel/manager.py → euporie-2.8.7/euporie/core/kernel/jupyter_manager.py +4 -3
  32. euporie-2.8.7/euporie/core/kernel/local.py +694 -0
  33. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/basic.py +6 -3
  34. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/keys.py +26 -25
  35. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/cache.py +31 -7
  36. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/containers.py +88 -13
  37. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/scroll.py +69 -170
  38. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/log.py +2 -5
  39. euporie-2.8.7/euporie/core/path.py +127 -0
  40. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/style.py +2 -1
  41. euporie-2.8.7/euporie/core/suggest.py +241 -0
  42. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/tabs/__init__.py +12 -4
  43. euporie-2.8.7/euporie/core/tabs/_commands.py +76 -0
  44. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/tabs/_settings.py +16 -0
  45. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/tabs/base.py +89 -9
  46. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/tabs/kernel.py +83 -38
  47. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/tabs/notebook.py +28 -76
  48. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/utils.py +2 -19
  49. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/validation.py +8 -8
  50. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/_settings.py +19 -2
  51. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/cell.py +32 -32
  52. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/cell_outputs.py +10 -1
  53. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/dialog.py +60 -76
  54. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/display.py +2 -2
  55. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/forms.py +71 -59
  56. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/inputs.py +7 -4
  57. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/layout.py +281 -93
  58. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/menu.py +56 -16
  59. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/palette.py +3 -1
  60. euporie-2.8.7/euporie/core/widgets/tree.py +119 -0
  61. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/app.py +35 -16
  62. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/display.py +2 -2
  63. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/edit.py +11 -46
  64. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/json.py +8 -4
  65. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/notebook.py +26 -8
  66. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/tabs/notebook.py +17 -13
  67. euporie-2.8.7/euporie/web/__init__.py +1 -0
  68. euporie-2.8.7/euporie/web/tabs/__init__.py +14 -0
  69. {euporie-2.8.5 → euporie-2.8.7}/euporie/web/tabs/web.py +30 -5
  70. euporie-2.8.7/euporie/web/widgets/__init__.py +1 -0
  71. {euporie-2.8.5 → euporie-2.8.7}/euporie/web/widgets/webview.py +5 -4
  72. {euporie-2.8.5 → euporie-2.8.7}/pyproject.toml +10 -9
  73. euporie-2.8.5/euporie/core/kernel/__init__.py +0 -1
  74. euporie-2.8.5/euporie/core/path.py +0 -79
  75. euporie-2.8.5/euporie/core/suggest.py +0 -160
  76. euporie-2.8.5/euporie/core/widgets/tree.py +0 -109
  77. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/__init__.py +0 -0
  78. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/__main__.py +0 -0
  79. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/_commands.py +0 -0
  80. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/_settings.py +0 -0
  81. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/py.typed +0 -0
  82. {euporie-2.8.5 → euporie-2.8.7}/euporie/console/tabs/__init__.py +0 -0
  83. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/__init__.py +0 -0
  84. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/base.py +0 -0
  85. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/current.py +0 -0
  86. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/cursor.py +0 -0
  87. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/dummy.py +0 -0
  88. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/app/launch.py +0 -0
  89. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/bars/__init__.py +0 -0
  90. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/bars/menu.py +0 -0
  91. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/bars/status.py +0 -0
  92. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/clipboard.py +0 -0
  93. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/comm/__init__.py +0 -0
  94. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/comm/registry.py +0 -0
  95. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/config.py +0 -0
  96. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/__init__.py +0 -0
  97. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/__init__.py +0 -0
  98. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/ansi.py +0 -0
  99. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/base64.py +0 -0
  100. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/common.py +0 -0
  101. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/ft.py +0 -0
  102. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/html.py +0 -0
  103. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/jpeg.py +0 -0
  104. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/markdown.py +0 -0
  105. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/pdf.py +0 -0
  106. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/pil.py +0 -0
  107. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/png.py +0 -0
  108. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/rich.py +0 -0
  109. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/sixel.py +0 -0
  110. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/formats/svg.py +0 -0
  111. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/mime.py +0 -0
  112. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/registry.py +0 -0
  113. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/convert/utils.py +0 -0
  114. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/diagnostics.py +0 -0
  115. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/ft/__init__.py +0 -0
  116. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/ft/ansi.py +0 -0
  117. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/ft/table.py +0 -0
  118. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/ft/utils.py +0 -0
  119. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/io.py +0 -0
  120. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/__init__.py +0 -0
  121. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/__init__.py +0 -0
  122. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/completion.py +0 -0
  123. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/micro.py +0 -0
  124. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/mouse.py +0 -0
  125. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/page_navigation.py +0 -0
  126. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/terminal.py +0 -0
  127. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/bindings/vi.py +0 -0
  128. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/key_processor.py +0 -0
  129. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/micro_state.py +0 -0
  130. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/registry.py +0 -0
  131. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/utils.py +0 -0
  132. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/key_binding/vi_state.py +0 -0
  133. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/__init__.py +0 -0
  134. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/controls.py +0 -0
  135. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/decor.py +0 -0
  136. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/mouse.py +0 -0
  137. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/print.py +0 -0
  138. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/layout/screen.py +0 -0
  139. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/lexers.py +0 -0
  140. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/lsp.py +0 -0
  141. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/margins.py +0 -0
  142. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/processors.py +0 -0
  143. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/py.typed +0 -0
  144. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/pygments.py +0 -0
  145. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/reference.py +0 -0
  146. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/renderer.py +0 -0
  147. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/__init__.py +0 -0
  148. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/decor.py +0 -0
  149. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/file_browser.py +0 -0
  150. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/formatted_text_area.py +0 -0
  151. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/logo.py +0 -0
  152. {euporie-2.8.5 → euporie-2.8.7}/euporie/core/widgets/pager.py +0 -0
  153. {euporie-2.8.5 → euporie-2.8.7}/euporie/data/desktop/euporie-console.desktop +0 -0
  154. {euporie-2.8.5 → euporie-2.8.7}/euporie/data/desktop/euporie-notebook.desktop +0 -0
  155. {euporie-2.8.5 → euporie-2.8.7}/euporie/hub/__init__.py +0 -0
  156. {euporie-2.8.5 → euporie-2.8.7}/euporie/hub/__main__.py +0 -0
  157. {euporie-2.8.5 → euporie-2.8.7}/euporie/hub/app.py +0 -0
  158. {euporie-2.8.5 → euporie-2.8.7}/euporie/hub/py.typed +0 -0
  159. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/__init__.py +0 -0
  160. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/__main__.py +0 -0
  161. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/_commands.py +0 -0
  162. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/_settings.py +0 -0
  163. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/current.py +0 -0
  164. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/enums.py +0 -0
  165. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/filters.py +0 -0
  166. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/py.typed +0 -0
  167. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/__init__.py +0 -0
  168. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/_commands.py +0 -0
  169. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/_settings.py +0 -0
  170. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/tabs/log.py +0 -0
  171. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/widgets/__init__.py +0 -0
  172. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/widgets/_commands.py +0 -0
  173. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/widgets/_settings.py +0 -0
  174. {euporie-2.8.5 → euporie-2.8.7}/euporie/notebook/widgets/side_bar.py +0 -0
  175. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/__init__.py +0 -0
  176. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/__main__.py +0 -0
  177. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/_settings.py +0 -0
  178. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/app.py +0 -0
  179. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/py.typed +0 -0
  180. {euporie-2.8.5 → euporie-2.8.7}/euporie/preview/tabs/__init__.py +0 -0
@@ -2,9 +2,6 @@
2
2
  /media
3
3
  /examples
4
4
 
5
- # Python version specifier file
6
- .python-version
7
-
8
5
  # AI tools
9
6
  .aider*
10
7
 
@@ -97,7 +94,7 @@ ipython_config.py
97
94
  # pyenv
98
95
  # For a library or package, you might want to ignore these files since the code is
99
96
  # intended to run in multiple environments; otherwise, check them in:
100
- # .python-version
97
+ .python-version
101
98
 
102
99
  # pipenv
103
100
  # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
@@ -151,141 +148,3 @@ dmypy.json
151
148
 
152
149
  # Cython debug symbols
153
150
  cython_debug/
154
- # Byte-compiled / optimized / DLL files
155
- __pycache__/
156
- *.py[cod]
157
- *$py.class
158
-
159
- # C extensions
160
- *.so
161
-
162
- # Distribution / packaging
163
- .Python
164
- build/
165
- develop-eggs/
166
- dist/
167
- downloads/
168
- eggs/
169
- .eggs/
170
- lib/
171
- lib64/
172
- parts/
173
- sdist/
174
- var/
175
- wheels/
176
- share/python-wheels/
177
- *.egg-info/
178
- .installed.cfg
179
- *.egg
180
- MANIFEST
181
-
182
- # PyInstaller
183
- # Usually these files are written by a python script from a template
184
- # before PyInstaller builds the exe, so as to inject date/other infos into it.
185
- *.manifest
186
- *.spec
187
-
188
- # Installer logs
189
- pip-log.txt
190
- pip-delete-this-directory.txt
191
-
192
- # Unit test / coverage reports
193
- htmlcov/
194
- .tox/
195
- .nox/
196
- .coverage
197
- .coverage.*
198
- .cache
199
- nosetests.xml
200
- coverage.xml
201
- *.cover
202
- *.py,cover
203
- .hypothesis/
204
- .pytest_cache/
205
- cover/
206
-
207
- # Translations
208
- *.mo
209
- *.pot
210
-
211
- # Django stuff:
212
- *.log
213
- local_settings.py
214
- db.sqlite3
215
- db.sqlite3-journal
216
-
217
- # Flask stuff:
218
- instance/
219
- .webassets-cache
220
-
221
- # Scrapy stuff:
222
- .scrapy
223
-
224
- # Sphinx documentation
225
- docs/_build/
226
-
227
- # PyBuilder
228
- .pybuilder/
229
- target/
230
-
231
- # Jupyter Notebook
232
- .ipynb_checkpoints
233
-
234
- # IPython
235
- profile_default/
236
- ipython_config.py
237
-
238
- # pyenv
239
- # For a library or package, you might want to ignore these files since the code is
240
- # intended to run in multiple environments; otherwise, check them in:
241
- # .python-version
242
-
243
- # pipenv
244
- # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
245
- # However, in case of collaboration, if having platform-specific dependencies or dependencies
246
- # having no cross-platform support, pipenv may install dependencies that don't work, or not
247
- # install all needed dependencies.
248
- #Pipfile.lock
249
-
250
- # PEP 582; used by e.g. github.com/David-OConnor/pyflow
251
- __pypackages__/
252
-
253
- # Celery stuff
254
- celerybeat-schedule
255
- celerybeat.pid
256
-
257
- # SageMath parsed files
258
- *.sage.py
259
-
260
- # Environments
261
- .env
262
- .venv
263
- env/
264
- venv/
265
- ENV/
266
- env.bak/
267
- venv.bak/
268
-
269
- # Spyder project settings
270
- .spyderproject
271
- .spyproject
272
-
273
- # Rope project settings
274
- .ropeproject
275
-
276
- # mkdocs documentation
277
- /site
278
-
279
- # mypy
280
- .mypy_cache/
281
- .dmypy.json
282
- dmypy.json
283
-
284
- # Pyre type checker
285
- .pyre/
286
-
287
- # pytype static type analyzer
288
- .pytype/
289
-
290
- # Cython debug symbols
291
- cython_debug/
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Josiah Outram Halstead
3
+ Copyright (c) 2025 Josiah Outram Halstead
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: euporie
3
- Version: 2.8.5
3
+ Version: 2.8.7
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
@@ -85,10 +85,12 @@ If you're working with Jupyter notebooks in a terminal only environment, like an
85
85
  Install
86
86
  *******
87
87
 
88
- You can install euporie with `pipx <https://pipxproject.github.io/>`_ (recommended) or ``pip``:
88
+ You can install euporie with `uv <https://docs.astral.sh/uv/>`_ (recommended), or with `pipx <https://pipxproject.github.io/>`_ or ``pip``):
89
89
 
90
90
  .. code-block:: console
91
91
 
92
+ $ uv tool install euporie
93
+ $ # OR
92
94
  $ pipx install euporie
93
95
  $ # OR
94
96
  $ python -m pip install --user euporie
@@ -38,10 +38,12 @@ If you're working with Jupyter notebooks in a terminal only environment, like an
38
38
  Install
39
39
  *******
40
40
 
41
- You can install euporie with `pipx <https://pipxproject.github.io/>`_ (recommended) or ``pip``:
41
+ You can install euporie with `uv <https://docs.astral.sh/uv/>`_ (recommended), or with `pipx <https://pipxproject.github.io/>`_ or ``pip``):
42
42
 
43
43
  .. code-block:: console
44
44
 
45
+ $ uv tool install euporie
46
+ $ # OR
45
47
  $ pipx install euporie
46
48
  $ # OR
47
49
  $ python -m pip install --user euporie
@@ -32,6 +32,7 @@ from euporie.core.filters import has_dialog
32
32
  from euporie.core.layout.mouse import DisableMouseOnScroll
33
33
  from euporie.core.widgets.dialog import (
34
34
  AboutDialog,
35
+ ConfirmDialog,
35
36
  NoKernelsDialog,
36
37
  SaveAsDialog,
37
38
  SelectKernelDialog,
@@ -112,6 +113,7 @@ class ConsoleApp(BaseApp):
112
113
  self.dialogs["no-kernels"] = NoKernelsDialog(self)
113
114
  self.dialogs["change-kernel"] = SelectKernelDialog(self)
114
115
  self.dialogs["shortcuts"] = ShortcutsDialog(self)
116
+ self.dialogs["confirm"] = ConfirmDialog(self)
115
117
 
116
118
  return FloatContainer(
117
119
  DisableMouseOnScroll(
@@ -40,7 +40,7 @@ from euporie.core.filters import (
40
40
  )
41
41
  from euporie.core.format import LspFormatter
42
42
  from euporie.core.io import edit_in_editor
43
- from euporie.core.kernel.client import MsgCallbacks
43
+ from euporie.core.kernel.base import MsgCallbacks
44
44
  from euporie.core.key_binding.registry import (
45
45
  load_registered_bindings,
46
46
  register_bindings,
@@ -137,10 +137,7 @@ class Console(KernelTab):
137
137
 
138
138
  self.container = self.load_container()
139
139
 
140
- self.kernel.start(cb=self.kernel_started, wait=False)
141
-
142
140
  self.app.before_render += self.render_outputs
143
-
144
141
  self.on_advance = Event(self)
145
142
 
146
143
  async def load_lsps(self) -> None:
@@ -162,9 +159,26 @@ class Console(KernelTab):
162
159
 
163
160
  lsp.on_exit += lsp_unload
164
161
 
162
+ def post_init_kernel(self) -> None:
163
+ """Start the kernel after if has been loaded."""
164
+ # Load container
165
+ super().post_init_kernel()
166
+
167
+ # Start kernel
168
+ if self.kernel._status == "stopped":
169
+ self.kernel.start(cb=self.kernel_started, wait=False)
170
+
165
171
  def kernel_died(self) -> None:
166
- """Call when the kernel dies."""
172
+ """Call if the kernel dies."""
167
173
  log.error("The kernel has died")
174
+ if confirm := self.app.dialogs.get("confirm"):
175
+ confirm.show(
176
+ title="Kernel connection lost",
177
+ message="The kernel appears to have died\n"
178
+ "as it can no longer be reached.\n\n"
179
+ "Do you want to restart the kernel?",
180
+ cb=self.kernel.restart,
181
+ )
168
182
 
169
183
  async def load_history(self) -> None:
170
184
  """Load kernel history."""
@@ -191,9 +205,7 @@ class Console(KernelTab):
191
205
  def validate_input(self, code: str) -> bool:
192
206
  """Determine if the entered code is ready to run."""
193
207
  assert self.kernel is not None
194
- completeness_status = self.kernel.is_complete(code, wait=True).get(
195
- "status", "unknown"
196
- )
208
+ completeness_status = self.kernel.is_complete(code).get("status", "unknown")
197
209
  return not (
198
210
  not code.strip()
199
211
  or completeness_status == "incomplete"
@@ -404,12 +416,7 @@ class Console(KernelTab):
404
416
  if ((json_cells and cell.id != json_cells[0].id) or i > 0) and (
405
417
  (height_known and rows_above_layout > 0) or not height_known
406
418
  ):
407
- children.append(
408
- Window(
409
- height=1,
410
- dont_extend_height=True,
411
- )
412
- )
419
+ children.append(Window(height=1, dont_extend_height=True))
413
420
 
414
421
  # Cell input
415
422
  children.append(
@@ -441,9 +448,12 @@ class Console(KernelTab):
441
448
  if outputs := cell.outputs:
442
449
  # Add space before an output if last rendered cell did not have outputs
443
450
  # or we are rendering a new output
444
- if self.last_rendered is not None and (
445
- not self.last_rendered.outputs
446
- or cell.execution_count != self.last_rendered.execution_count
451
+ if self.last_rendered is None or (
452
+ self.last_rendered is not None
453
+ and (
454
+ not self.last_rendered.outputs
455
+ or cell.execution_count != self.last_rendered.execution_count
456
+ )
447
457
  ):
448
458
  children.append(
449
459
  Window(
@@ -1,10 +1,10 @@
1
1
  """This package defines the euporie application and its components."""
2
2
 
3
3
  __app_name__ = "euporie"
4
- __version__ = "2.8.5"
4
+ __version__ = "2.8.7"
5
5
  __logo__ = "⚈"
6
6
  __strapline__ = "Jupyter in the terminal"
7
7
  __author__ = "Josiah Outram Halstead"
8
8
  __email__ = "josiah@halstead.email"
9
- __copyright__ = f"© 2024, {__author__}"
9
+ __copyright__ = f"© 2025, {__author__}"
10
10
  __license__ = "MIT"
@@ -7,13 +7,13 @@ from importlib.metadata import entry_points
7
7
  from typing import TYPE_CHECKING
8
8
 
9
9
  if TYPE_CHECKING:
10
- from importlib.metadata import EntryPoint, EntryPoints, SelectableGroups
10
+ from importlib.metadata import EntryPoint, EntryPoints
11
11
 
12
12
 
13
13
  @cache
14
14
  def available_apps() -> dict[str, EntryPoint]:
15
15
  """Return a list of loadable euporie apps."""
16
- eps: dict | SelectableGroups | EntryPoints
16
+ eps: dict | EntryPoints
17
17
  try:
18
18
  eps = entry_points(group="euporie.apps")
19
19
  except TypeError:
@@ -1,5 +1,7 @@
1
1
  """Defines core settings."""
2
2
 
3
+ import json
4
+
3
5
  from euporie.core import __version__
4
6
  from euporie.core.config import add_setting
5
7
 
@@ -73,8 +75,11 @@ add_setting(
73
75
  name="log_config",
74
76
  group="euporie.core.log",
75
77
  flags=["--log-config"],
76
- type_=str,
77
- default="{}",
78
+ type_=json.loads,
79
+ default={},
80
+ schema={
81
+ "type": "object",
82
+ },
78
83
  title="additional logging configuration",
79
84
  help_="Additional logging configuration",
80
85
  description="""
@@ -8,7 +8,7 @@ from prompt_toolkit.filters import buffer_has_focus
8
8
 
9
9
  from euporie.core.app.current import get_app
10
10
  from euporie.core.commands import add_cmd
11
- from euporie.core.filters import tab_has_focus
11
+ from euporie.core.filters import tab_has_focus, tab_type_has_focus
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  from prompt_toolkit.key_binding.key_processor import KeyPressEvent
@@ -20,18 +20,12 @@ def _quit() -> None:
20
20
  get_app().exit()
21
21
 
22
22
 
23
- @add_cmd(aliases=["wq", "x"])
24
- def _save_and_quit(event: KeyPressEvent) -> None:
25
- """Save the current tab then quits euporie."""
26
- from upath import UPath
23
+ @add_cmd(aliases=["q!"])
24
+ def _force_quit() -> None:
25
+ """Quit euporie without saving any changes."""
26
+ from prompt_toolkit.application.application import Application
27
27
 
28
- app = get_app()
29
- if (tab := get_app().tab) is not None:
30
- try:
31
- tab._save(UPath(event._arg) if event._arg else None)
32
- except NotImplementedError:
33
- pass
34
- app.exit()
28
+ Application.exit(get_app())
35
29
 
36
30
 
37
31
  @add_cmd(aliases=["bc"], filter=tab_has_focus, menu_title="Close File")
@@ -68,3 +62,17 @@ def _focus_previous() -> None:
68
62
  def _clear_screen() -> None:
69
63
  """Clear the screen."""
70
64
  get_app().renderer.clear()
65
+
66
+
67
+ @add_cmd(hidden=True, aliases=[""])
68
+ def _go_to(event: KeyPressEvent, index: int = 0) -> None:
69
+ """Go to a line or cell by number."""
70
+ index = max(0, index - 1)
71
+ if buffer_has_focus():
72
+ buffer = get_app().current_buffer
73
+ buffer.cursor_position = len("".join(buffer.text.splitlines(True)[:index]))
74
+ elif tab_type_has_focus("euporie.notebook.tabs.notebook:Notebook")():
75
+ from euporie.notebook.tabs.notebook import Notebook
76
+
77
+ if isinstance(nb := get_app().tab, Notebook):
78
+ nb.select(index)
@@ -140,6 +140,8 @@ add_setting(
140
140
 
141
141
  e.g.
142
142
 
143
+ .. code-block:: json
144
+
143
145
  [
144
146
  {"command": ["ruff", "format", "-"], "languages": ["python"]},
145
147
  {"command": ["black", "-"], "languages": ["python"]},
@@ -281,6 +283,30 @@ add_setting(
281
283
  """,
282
284
  )
283
285
 
286
+ add_setting(
287
+ name="custom_styles",
288
+ group="euporie.core.style",
289
+ flags=["--custom-styles"],
290
+ type_=json.loads,
291
+ default={},
292
+ schema={
293
+ "type": "object",
294
+ },
295
+ help_="Additional style settings",
296
+ description="""
297
+ A JSON object mapping style names to prompt-toolkit style values.
298
+
299
+ The style keys used in euporie can be found in :py:func:`euporie.core.style.build_style`.
300
+
301
+ e.g.:
302
+
303
+ .. code-block:: json
304
+
305
+ { "cell input prompt":"fg:purple", "cell output prompt": "fg:green" }
306
+
307
+ """,
308
+ )
309
+
284
310
  add_setting(
285
311
  name="key_bindings",
286
312
  group="euporie.core.app.app",
@@ -288,19 +314,19 @@ add_setting(
288
314
  type_=json.loads,
289
315
  help_="Additional key binding definitions",
290
316
  default={},
291
- description="""
292
- A mapping of component names to mappings of command name to key-binding lists.
293
- """,
294
317
  schema={
295
318
  "type": "object",
296
319
  },
320
+ description="""
321
+ A mapping of component names to mappings of command name to key-binding lists.
322
+ """,
297
323
  )
298
324
 
299
325
  add_setting(
300
326
  name="graphics",
301
327
  group="euporie.core.app.app",
302
328
  flags=["--graphics"],
303
- choices=["none", "sixel", "kitty", "iterm"],
329
+ choices=["none", "sixel", "kitty", "kitty-unicode", "iterm"],
304
330
  type_=str,
305
331
  default=None,
306
332
  help_="The preferred graphics protocol",
@@ -375,6 +401,8 @@ add_setting(
375
401
  description="""
376
402
  Additional language servers can be defined here, e.g.:
377
403
 
404
+ .. code-block:: json
405
+
378
406
  {
379
407
  "ruff": {"command": ["ruff-lsp"], "languages": ["python"]},
380
408
  "pylsp": {"command": ["pylsp"], "languages": ["python"]},
@@ -392,6 +420,8 @@ add_setting(
392
420
  empty dictionary. For example, the following would disable the awk language
393
421
  server:
394
422
 
423
+ .. code-block:: json
424
+
395
425
  {
396
426
  "awk-language-server": {},
397
427
  }
@@ -520,14 +520,17 @@ class BaseApp(ConfigurableApp, Application, ABC):
520
520
  @classmethod
521
521
  def launch(cls) -> None:
522
522
  """Launch the app."""
523
+ from prompt_toolkit.utils import in_main_thread
524
+
523
525
  super().launch()
524
526
  # Run the application
525
527
  with create_app_session(input=cls.load_input(), output=cls.load_output()):
526
528
  # Create an instance of the app and run it
527
529
  app = cls()
528
- # Handle SIGTERM while the app is running
529
- original_sigterm = signal.getsignal(signal.SIGTERM)
530
- signal.signal(signal.SIGTERM, app.cleanup)
530
+ if in_main_thread():
531
+ # Handle SIGTERM while the app is running
532
+ original_sigterm = signal.getsignal(signal.SIGTERM)
533
+ signal.signal(signal.SIGTERM, app.cleanup)
531
534
  # Set and run the app
532
535
  with set_app(app):
533
536
  try:
@@ -535,7 +538,8 @@ class BaseApp(ConfigurableApp, Application, ABC):
535
538
  except (EOFError, KeyboardInterrupt):
536
539
  result = None
537
540
  finally:
538
- signal.signal(signal.SIGTERM, original_sigterm)
541
+ if in_main_thread():
542
+ signal.signal(signal.SIGTERM, original_sigterm)
539
543
  # Shut down any remaining LSP clients at exit
540
544
  app.shutdown_lsps()
541
545
  return result
@@ -582,14 +586,16 @@ class BaseApp(ConfigurableApp, Application, ABC):
582
586
  path_mime = get_mime(path) or "text/plain"
583
587
  log.debug("File %s has mime type: %s", path, path_mime)
584
588
 
585
- tab_options: list[TabRegistryEntry] = []
589
+ # Use a set to automatically handle duplicates
590
+ tab_options: set[TabRegistryEntry] = set()
586
591
  for entry in self.tab_registry:
587
592
  for mime_type in entry.mime_types:
588
593
  if PurePath(path_mime).match(mime_type):
589
- tab_options.append(entry)
594
+ tab_options.add(entry)
590
595
  if path.suffix in entry.file_extensions:
591
- tab_options.append(entry)
596
+ tab_options.add(entry)
592
597
 
598
+ # Sort by weight (TabRegistryEntry.__lt__ handles this)
593
599
  return sorted(tab_options, reverse=True)
594
600
 
595
601
  def get_file_tab(self, path: Path) -> type[Tab] | None:
@@ -791,6 +797,13 @@ class BaseApp(ConfigurableApp, Application, ABC):
791
797
  syntax_theme = "tango" if self.color_palette.bg.is_light else "euporie"
792
798
  return syntax_theme
793
799
 
800
+ base_styles = (
801
+ Style(MIME_STYLE),
802
+ Style(HTML_STYLE),
803
+ Style(LOG_STYLE),
804
+ Style(IPYWIDGET_STYLE),
805
+ )
806
+
794
807
  def create_merged_style(self) -> BaseStyle:
795
808
  """Generate a new merged style for the application.
796
809
 
@@ -801,6 +814,11 @@ class BaseApp(ConfigurableApp, Application, ABC):
801
814
  Return a combined style to use for the application
802
815
 
803
816
  """
817
+ styles: list[BaseStyle] = [
818
+ style_from_pygments_cls(get_style_by_name(self.syntax_theme)),
819
+ *self.base_styles,
820
+ ]
821
+
804
822
  # Get foreground and background colors based on the configured colour scheme
805
823
  theme_colors: dict[str, dict[str, str]] = {
806
824
  "default": {},
@@ -844,7 +862,7 @@ class BaseApp(ConfigurableApp, Application, ABC):
844
862
  )
845
863
 
846
864
  # Build app style
847
- app_style = build_style(cp)
865
+ styles.append(build_style(cp))
848
866
 
849
867
  # Apply style transformations based on the configured color scheme
850
868
  self.style_transformation = merge_style_transformations(
@@ -862,16 +880,11 @@ class BaseApp(ConfigurableApp, Application, ABC):
862
880
  ]
863
881
  )
864
882
 
865
- return merge_styles(
866
- [
867
- style_from_pygments_cls(get_style_by_name(self.syntax_theme)),
868
- Style(MIME_STYLE),
869
- Style(HTML_STYLE),
870
- Style(LOG_STYLE),
871
- Style(IPYWIDGET_STYLE),
872
- app_style,
873
- ]
874
- )
883
+ # Add user style customizations
884
+ if custom_style_dict := self.config.custom_styles:
885
+ styles.append(Style.from_dict(custom_style_dict))
886
+
887
+ return merge_styles(styles)
875
888
 
876
889
  def update_style(self, query: Setting | None = None) -> None:
877
890
  """Update the application's style when the syntax theme is changed."""