euporie 2.8.4__py3-none-any.whl → 2.8.6__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 (131) 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 +58 -62
  5. euporie/core/__init__.py +1 -1
  6. euporie/core/__main__.py +28 -11
  7. euporie/core/_settings.py +109 -0
  8. euporie/core/app/__init__.py +3 -0
  9. euporie/core/app/_commands.py +95 -0
  10. euporie/core/app/_settings.py +457 -0
  11. euporie/core/{app.py → app/app.py} +212 -576
  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 +205 -0
  19. euporie/core/bars/menu.py +258 -0
  20. euporie/core/{widgets → bars}/search.py +20 -16
  21. euporie/core/{widgets → bars}/status.py +6 -23
  22. euporie/core/clipboard.py +19 -80
  23. euporie/core/comm/base.py +8 -6
  24. euporie/core/comm/ipywidgets.py +16 -7
  25. euporie/core/comm/registry.py +2 -1
  26. euporie/core/commands.py +10 -20
  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 +116 -53
  31. euporie/core/convert/formats/__init__.py +31 -0
  32. euporie/core/convert/formats/ansi.py +9 -23
  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 +4 -6
  39. euporie/core/convert/utils.py +41 -4
  40. euporie/core/diagnostics.py +2 -2
  41. euporie/core/filters.py +98 -40
  42. euporie/core/format.py +2 -3
  43. euporie/core/ft/ansi.py +1 -1
  44. euporie/core/ft/html.py +12 -21
  45. euporie/core/ft/table.py +1 -3
  46. euporie/core/ft/utils.py +4 -1
  47. euporie/core/graphics.py +386 -133
  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} +45 -108
  53. euporie/core/kernel/manager.py +114 -0
  54. euporie/core/key_binding/bindings/__init__.py +1 -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 +1 -6
  58. euporie/core/key_binding/bindings/mouse.py +2 -2
  59. euporie/core/key_binding/bindings/terminal.py +193 -0
  60. euporie/core/key_binding/key_processor.py +43 -2
  61. euporie/core/key_binding/registry.py +2 -0
  62. euporie/core/key_binding/utils.py +22 -2
  63. euporie/core/keys.py +7156 -93
  64. euporie/core/layout/cache.py +3 -3
  65. euporie/core/layout/containers.py +48 -4
  66. euporie/core/layout/decor.py +2 -2
  67. euporie/core/layout/mouse.py +1 -1
  68. euporie/core/layout/print.py +2 -1
  69. euporie/core/layout/scroll.py +39 -34
  70. euporie/core/log.py +76 -64
  71. euporie/core/lsp.py +118 -24
  72. euporie/core/margins.py +1 -1
  73. euporie/core/path.py +62 -13
  74. euporie/core/renderer.py +58 -17
  75. euporie/core/style.py +57 -39
  76. euporie/core/suggest.py +103 -85
  77. euporie/core/tabs/__init__.py +32 -0
  78. euporie/core/tabs/_settings.py +113 -0
  79. euporie/core/tabs/base.py +80 -470
  80. euporie/core/tabs/kernel.py +419 -0
  81. euporie/core/tabs/notebook.py +24 -101
  82. euporie/core/utils.py +92 -15
  83. euporie/core/validation.py +1 -1
  84. euporie/core/widgets/_settings.py +188 -0
  85. euporie/core/widgets/cell.py +19 -50
  86. euporie/core/widgets/cell_outputs.py +25 -36
  87. euporie/core/widgets/decor.py +11 -41
  88. euporie/core/widgets/dialog.py +62 -27
  89. euporie/core/widgets/display.py +12 -15
  90. euporie/core/widgets/file_browser.py +2 -23
  91. euporie/core/widgets/forms.py +8 -5
  92. euporie/core/widgets/inputs.py +13 -70
  93. euporie/core/widgets/layout.py +2 -1
  94. euporie/core/widgets/logo.py +49 -0
  95. euporie/core/widgets/menu.py +10 -8
  96. euporie/core/widgets/pager.py +6 -10
  97. euporie/core/widgets/palette.py +6 -6
  98. euporie/hub/app.py +52 -35
  99. euporie/notebook/_commands.py +24 -0
  100. euporie/notebook/_settings.py +107 -0
  101. euporie/notebook/app.py +49 -171
  102. euporie/notebook/filters.py +1 -1
  103. euporie/notebook/tabs/__init__.py +46 -7
  104. euporie/notebook/tabs/_commands.py +714 -0
  105. euporie/notebook/tabs/_settings.py +32 -0
  106. euporie/notebook/tabs/display.py +4 -4
  107. euporie/notebook/tabs/edit.py +11 -44
  108. euporie/notebook/tabs/json.py +5 -5
  109. euporie/notebook/tabs/log.py +1 -18
  110. euporie/notebook/tabs/notebook.py +11 -660
  111. euporie/notebook/widgets/_commands.py +11 -0
  112. euporie/notebook/widgets/_settings.py +19 -0
  113. euporie/notebook/widgets/side_bar.py +14 -34
  114. euporie/preview/_settings.py +104 -0
  115. euporie/preview/app.py +6 -31
  116. euporie/preview/tabs/notebook.py +6 -72
  117. euporie/web/__init__.py +1 -0
  118. euporie/web/tabs/__init__.py +14 -0
  119. euporie/web/tabs/web.py +11 -6
  120. euporie/web/widgets/__init__.py +1 -0
  121. euporie/web/widgets/webview.py +5 -15
  122. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/METADATA +10 -8
  123. euporie-2.8.6.dist-info/RECORD +175 -0
  124. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/WHEEL +1 -1
  125. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/entry_points.txt +2 -2
  126. {euporie-2.8.4.dist-info → euporie-2.8.6.dist-info}/licenses/LICENSE +1 -1
  127. euporie/core/launch.py +0 -64
  128. euporie/core/terminal.py +0 -522
  129. euporie-2.8.4.dist-info/RECORD +0 -147
  130. {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-console.desktop +0 -0
  131. {euporie-2.8.4.data → euporie-2.8.6.data}/data/share/applications/euporie-notebook.desktop +0 -0
euporie/core/style.py CHANGED
@@ -4,16 +4,19 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  from colorsys import hls_to_rgb, rgb_to_hls
7
- from functools import partial
7
+ from functools import cache, partial
8
8
  from typing import TYPE_CHECKING
9
9
 
10
10
  from prompt_toolkit.cache import SimpleCache
11
11
  from prompt_toolkit.styles.defaults import default_ui_style
12
12
  from prompt_toolkit.styles.style import Style
13
+ from pygments.styles import get_style_by_name as pyg_get_style_by_name
13
14
 
14
15
  if TYPE_CHECKING:
15
16
  from typing import Any
16
17
 
18
+ from pygments.style import Style as PygmentsStyle
19
+
17
20
 
18
21
  log = logging.getLogger(__name__)
19
22
 
@@ -440,8 +443,8 @@ def build_style(
440
443
  # Statusbar
441
444
  # "status": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.less(0.15)}",
442
445
  "status": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
443
- "status-field": f"fg:{cp.fg.more(0.1)} bg:{cp.bg.more(0.1)}",
444
- "status-sep": f"fg:{cp.bg.more(0.05)} bg:{cp.bg.more(0.1)}",
446
+ "status-field": f"bg:{cp.fg.more(0.1)} fg:{cp.bg.more(0.1)} reverse",
447
+ "status-sep": f"bg:{cp.bg.more(0.05)} fg:{cp.bg.more(0.1)} reverse",
445
448
  # Menus & Menu bar
446
449
  "menu": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
447
450
  "menu bar": f"bg:{cp.bg.less(0.15)}",
@@ -450,11 +453,11 @@ def build_style(
450
453
  "menu shortcut disabled": f"fg:{cp.fg.more(0.4).towards(cp.bg, 0.5)}",
451
454
  "menu prefix": f"fg:{cp.fg.more(0.2)}",
452
455
  "menu prefix disabled": f"fg:{cp.fg.more(0.2).towards(cp.bg, 0.5)}",
453
- "menu selection": f"fg:{cp.hl.more(1)} bg:{cp.hl}",
454
- "menu selection shortcut": f"fg:{cp.hl.more(1).more(0.05)} bg:{cp.hl}",
455
- "menu selection prefix": f"fg:{cp.hl.more(1).more(0.05)} bg:{cp.hl}",
456
+ "menu selection": f"bg:{cp.hl.more(1)} fg:{cp.hl} reverse",
457
+ "menu selection shortcut": f"bg:{cp.hl.more(1).more(0.05)} fg:{cp.hl} reverse",
458
+ "menu selection prefix": f"bg:{cp.hl.more(1).more(0.05)} fg:{cp.hl} reverse",
456
459
  "menu border": f"fg:{cp.bg.more(0.15)} bg:{cp.bg.more(0.05)}",
457
- "menu border selection": f"bg:{cp.hl}",
460
+ "menu border selection": f"fg:{cp.bg.more(0.15)} bg:{cp.hl} noreverse",
458
461
  # Tab bar
459
462
  "app-tab-bar": f"bg:{cp.bg.less(0.15)}",
460
463
  "app-tab-bar border": f"fg:{cp.bg.more(0.1)}",
@@ -466,10 +469,10 @@ def build_style(
466
469
  # Tabs
467
470
  "loading": "fg:#888888",
468
471
  # Buffer
469
- "line-number": f"fg:{cp.fg.more(0.5)} bg:{cp.bg.more(0.05)}",
470
- "line-number.current": f"bold orange bg:{cp.bg.more(0.1)}",
471
- "line-number edge": f"fg:{cp.bg.darker(0.1)}",
472
- "line-number.current edge": f"fg:{cp.bg.darker(0.1)}",
472
+ "line-number": f"bg:{cp.fg.more(0.5)} fg:{cp.bg.more(0.05)} reverse",
473
+ "line-number.current": f"bg:orange fg:{cp.bg.more(0.1)} bold",
474
+ "line-number edge": f"bg:{cp.bg.darker(0.1)}",
475
+ "line-number.current edge": f"bg:{cp.bg.darker(0.1)}",
473
476
  "cursor-line": f"bg:{cp.bg.more(0.05)}",
474
477
  "cursor-line search": f"bg:{cp.bg.more(0.02)}",
475
478
  "cursor-line search.current": f"bg:{cp.bg.more(0.02)}",
@@ -479,13 +482,11 @@ def build_style(
479
482
  "matching-bracket.other": "fg:yellow bold",
480
483
  "trailing-whitespace": f"fg:{cp.fg.more(0.66)}",
481
484
  "tab": f"fg:{cp.fg.more(0.66)}",
482
- # Search
485
+ # Search results
483
486
  "search": f"bg:{cp.bg.more(0.05)}",
484
487
  "search.current": f"bg:{cp.bg.more(0.05)}",
485
488
  "incsearch": "bg:ansibrightyellow",
486
489
  "incsearch.current": "bg:ansibrightgreen",
487
- "search-toolbar": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
488
- "search-toolbar.title": f"fg:{cp.fg.more(0.1)} bg:{cp.bg.more(0.1)}",
489
490
  # Inputs
490
491
  "kernel-input": f"fg:default bg:{cp.bg.more(0.02)}",
491
492
  # Cells
@@ -496,22 +497,22 @@ def build_style(
496
497
  "cell border edit": f"fg:{cp.hl.adjust(hue=-0.3333, rel=False)}",
497
498
  "cell input prompt": "fg:blue",
498
499
  "cell output prompt": "fg:red",
499
- "cell show outputs": f"fg:{cp.fg.more(0.5)} bg:{cp.bg.more(0.05)}",
500
- "cell show inputs": f"fg:{cp.fg.more(0.5)} bg:{cp.bg.more(0.05)}",
501
- "cell show inputs border": f"fg:{cp.bg.darker(0.1)}",
502
- "cell show outputs border": f"fg:{cp.bg.darker(0.1)}",
500
+ "cell show outputs": f"bg:{cp.fg.more(0.5)} fg:{cp.bg.more(0.05)} reverse",
501
+ "cell show inputs": f"bg:{cp.fg.more(0.5)} fg:{cp.bg.more(0.05)} reverse",
502
+ "cell show inputs border": f"bg:{cp.bg.darker(0.1)} fg:{cp.bg.more(0.05)} reverse",
503
+ "cell show outputs border": f"bg:{cp.bg.darker(0.1)} fg:{cp.bg.more(0.05)} reverse",
503
504
  # Scrollbars
504
505
  "scrollbar": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.15)}",
505
506
  "scrollbar.background": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.15)}",
506
- "scrollbar.arrow": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.20)}",
507
+ "scrollbar.arrow": f"bg:{cp.bg.more(0.75)} fg:{cp.bg.more(0.20)} reverse",
507
508
  "scrollbar.start": "",
508
509
  # "scrollbar.start": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.25)}",
509
- "scrollbar.button": f"fg:{cp.bg.more(0.75)} bg:{cp.bg.more(0.75)}",
510
- "scrollbar.end": f"fg:{cp.bg.more(0.15)} bg:{cp.bg.more(0.75)}",
510
+ "scrollbar.button": f"bg:{cp.bg.more(0.75)} fg:{cp.bg.more(0.75)} reverse",
511
+ "scrollbar.end": f"bg:{cp.bg.more(0.15)} fg:{cp.bg.more(0.75)} reverse",
511
512
  # Overflow margin
512
513
  "overflow": f"fg:{cp.fg.more(0.5)}",
513
514
  # Dialogs
514
- "dialog dialog-title": f"fg:white bg:{cp.hl.darker(0.25)} bold",
515
+ "dialog dialog-title": f"bg:white fg:{cp.hl.darker(0.25)} bold reverse",
515
516
  "dialog": f"fg:{cp.fg.base} bg:{cp.bg.darker(0.1)}",
516
517
  "dialog text-area": f"bg:{cp.bg.lighter(0.05)}",
517
518
  "dialog input text text-area": f"fg:default bg:{cp.bg.less(0.1)}",
@@ -519,6 +520,17 @@ def build_style(
519
520
  "dialog border": f"fg:{cp.bg.darker(0.1).more(0.1)}",
520
521
  # Horizontals rule
521
522
  "hr": "fg:ansired",
523
+ # Toolbars
524
+ "toolbar": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
525
+ "toolbar.title": f"fg:{cp.fg.more(0.1)} bg:{cp.bg.more(0.1)}",
526
+ # Search bar
527
+ "search-toolbar": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
528
+ # Command bar
529
+ "toolbar menu": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
530
+ "toolbar menu completion": f"fg:{cp.fg.more(0.1)} bg:{cp.bg.more(0.1)}",
531
+ "toolbar menu completion current": f"fg:{cp.hl} bg:{cp.fg} reverse",
532
+ "toolbar menu overflow": f"fg:{cp.fg.more(0.5)}",
533
+ "toolbar menu meta": f"bg:{cp.bg.more(0.25)} bold",
522
534
  # Completions menu
523
535
  "menu completion-keyword": "fg:#d700af",
524
536
  "menu completion-function": "fg:#005faf",
@@ -530,28 +542,28 @@ def build_style(
530
542
  "menu completion-path": "fg:#aa8800",
531
543
  "menu completion-dict-key": "fg:#ddbb00",
532
544
  "menu selection completion-keyword": (
533
- f"fg:{ColorPaletteColor('#d700af').lighter(0.75)}"
545
+ f"bg:{ColorPaletteColor('#d700af').lighter(0.75)} fg:{cp.hl} reverse"
534
546
  ),
535
547
  "menu selection completion-function": (
536
- f"fg:{ColorPaletteColor('#005faf').lighter(0.75)}"
548
+ f"bg:{ColorPaletteColor('#005faf').lighter(0.75)} fg:{cp.hl} reverse"
537
549
  ),
538
550
  "menu selection completion-class": (
539
- f"fg:{ColorPaletteColor('#008700').lighter(0.75)}"
551
+ f"bg:{ColorPaletteColor('#008700').lighter(0.75)} fg:{cp.hl} reverse"
540
552
  ),
541
553
  "menu selection completion-statement": (
542
- f"fg:{ColorPaletteColor('#5f0000').lighter(0.75)}"
554
+ f"bg:{ColorPaletteColor('#5f0000').lighter(0.75)} fg:{cp.hl} reverse"
543
555
  ),
544
556
  "menu selection completion-instance": (
545
- f"fg:{ColorPaletteColor('#d75f00').lighter(0.75)}"
557
+ f"bg:{ColorPaletteColor('#d75f00').lighter(0.75)} fg:{cp.hl} reverse"
546
558
  ),
547
559
  "menu selection completion-module": (
548
- f"fg:{ColorPaletteColor('#d70000').lighter(0.75)}"
560
+ f"bg:{ColorPaletteColor('#d70000').lighter(0.75)} fg:{cp.hl} reverse"
549
561
  ),
550
562
  "menu selection completion-magic": (
551
- f"fg:{ColorPaletteColor('#888888').lighter(0.75)}"
563
+ f"bg:{ColorPaletteColor('#888888').lighter(0.75)} fg:{cp.hl} reverse"
552
564
  ),
553
565
  "menu selection completion-path": (
554
- f"fg:{ColorPaletteColor('#aa8800').lighter(0.75)}"
566
+ f"bg:{ColorPaletteColor('#aa8800').lighter(0.75)} fg:{cp.hl} reverse"
555
567
  ),
556
568
  # Log
557
569
  "log.level.nonset": "fg:grey",
@@ -567,7 +579,7 @@ def build_style(
567
579
  "file-browser face": f"bg:{cp.bg.lighter(0.1)}",
568
580
  "file-browser face row alt-row": f"bg:{cp.bg.lighter(0.1).more(0.01)}",
569
581
  "file-browser face row hovered": f"bg:{cp.bg.more(0.2)}",
570
- "file-browser face row selection": f"bg:{cp.hl}",
582
+ "file-browser face row selection": f"bg:{cp.fg} fg:{cp.hl} reverse",
571
583
  # Shortcuts
572
584
  "shortcuts.group": f"bg:{cp.bg.more(0.4)} bold underline",
573
585
  # "shortcuts.row": f"bg:{cp.bg.base} nobold",
@@ -576,7 +588,7 @@ def build_style(
576
588
  # Palette
577
589
  "palette.item": f"fg:{cp.fg.more(0.05)} bg:{cp.bg.more(0.05)}",
578
590
  "palette.item.alt": f"bg:{cp.bg.more(0.15)}",
579
- "palette.item.selected": f"fg:{cp.hl.more(1)} bg:{cp.hl}",
591
+ "palette.item.selected": f"bg:{cp.hl.more(1)} fg:{cp.hl} reverse",
580
592
  # Pager
581
593
  "pager": f"bg:{cp.bg.more(0.05)}",
582
594
  "pager.border": f"fg:{cp.bg.towards(cp.ansiblack, 0.15)} reverse",
@@ -596,13 +608,13 @@ def build_style(
596
608
  "side_bar border outer": f"bg:{cp.bg}",
597
609
  "side_bar buttons": f"bg:{cp.bg.less(0.15)}",
598
610
  "side_bar buttons hovered": f"fg:{cp.hl}",
599
- "side_bar buttons selection": f"fg:{cp.fg} bg:{cp.hl}",
600
- "side_bar buttons separator": f"fg:{cp.bg.less(0.15)} bg:{cp.bg.less(0.15)}",
611
+ "side_bar buttons separator": f"bg:{cp.bg.less(0.15)} fg:{cp.bg.less(0.15)}",
612
+ "side_bar buttons selection": f"bg:{cp.fg} fg:{cp.hl} reverse",
601
613
  "side_bar buttons separator selection before": (
602
- f"fg:{cp.bg.less(0.15)} bg:{cp.hl}"
614
+ f"bg:{cp.bg.less(0.15)} fg:{cp.hl} reverse"
603
615
  ),
604
616
  "side_bar buttons separator selection after": (
605
- f"fg:{cp.hl} bg:{cp.bg.less(0.15)}"
617
+ f"fg:{cp.hl} bg:{cp.bg.less(0.15)} noreverse"
606
618
  ),
607
619
  # Tabbed split
608
620
  "tabbed-split border": f"fg:{cp.bg.more(0.2)}",
@@ -677,9 +689,9 @@ def build_style(
677
689
  "text-area focused selected": "reverse",
678
690
  # Buttons
679
691
  "input button face": f"fg:default bg:{cp.bg.more(0.05)}",
680
- "input button face hovered": f"fg:default bg:{cp.bg.more(0.2)}",
681
- "input button face selection": f"bg:{cp.bg.more(0.05)}",
682
- "input button face focused": f"fg:default bg:{cp.bg.towards(cp.hl, 0.1)}",
692
+ "input button face hovered": f"fg:{cp.fg} bg:{cp.bg.more(0.2)}",
693
+ "input button face selection": f"bg:{cp.fg} fg:{cp.bg.more(0.05)} reverse",
694
+ "input button face focused": f"bg:{cp.fg} fg:{cp.bg.towards(cp.hl, 0.1)} reverse",
683
695
  # Input widgets
684
696
  "input border top": f"fg:{cp.bg.lighter(0.5)}",
685
697
  "input border left": f"fg:{cp.bg.lighter(0.5)}",
@@ -753,3 +765,9 @@ def build_style(
753
765
  }
754
766
 
755
767
  return Style.from_dict(style_dict)
768
+
769
+
770
+ @cache
771
+ def get_style_by_name(name: str) -> type[PygmentsStyle]:
772
+ """Get Pygments style, caching the result."""
773
+ return pyg_get_style_by_name(name)
euporie/core/suggest.py CHANGED
@@ -3,7 +3,9 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
- from collections import deque
6
+ from collections import defaultdict
7
+ from difflib import SequenceMatcher
8
+ from functools import lru_cache
7
9
  from typing import TYPE_CHECKING
8
10
 
9
11
  from prompt_toolkit.auto_suggest import AutoSuggest, ConditionalAutoSuggest, Suggestion
@@ -15,7 +17,6 @@ if TYPE_CHECKING:
15
17
  from prompt_toolkit.filters import Filter
16
18
  from prompt_toolkit.history import History
17
19
 
18
- from euporie.core.kernel import Kernel
19
20
 
20
21
  log = logging.getLogger(__name__)
21
22
 
@@ -23,99 +24,116 @@ log = logging.getLogger(__name__)
23
24
  class HistoryAutoSuggest(AutoSuggest):
24
25
  """Suggest line completions from a :class:`History` object."""
25
26
 
26
- def __init__(self, history: History, cache_size: int = 100_000) -> None:
27
+ def __init__(self, history: History) -> None:
27
28
  """Set the kernel instance in initialization."""
28
29
  self.history = history
30
+ self.calculate_similarity = lru_cache(maxsize=1024)(self._calculate_similarity)
29
31
 
30
- self.cache_size = cache_size
31
- self.cache_keys: deque[str] = deque()
32
- self.cache: dict[str, Suggestion] = {}
32
+ self.n_texts = 0
33
+ self.n_lines = 0
34
+ self.prefix_dict: dict[str, dict[str, list[dict[str, int]]]] = defaultdict(
35
+ lambda: defaultdict(list)
36
+ )
37
+
38
+ def process_history(self) -> None:
39
+ """Process the entire history and store in prefix_dict."""
40
+ texts = self.history._loaded_strings
41
+ if texts := texts[: len(texts) - self.n_texts]:
42
+ n_lines = self.n_lines
43
+ prefix_dict = self.prefix_dict
44
+ for i, text in enumerate(reversed(texts)):
45
+ for line in text.strip().splitlines():
46
+ n_lines += 1
47
+ line = line.strip()
48
+ for j in range(1, len(line)):
49
+ prefix, suffix = line[:j], line[j:]
50
+ prefix_dict[prefix][suffix].append(
51
+ {"index": -1 - i, "line": n_lines}
52
+ )
53
+ # for k in range(1, len(prefix)):
54
+ # prefix_dict[prefix[-k:]] = prefix_dict[prefix]
55
+ self.n_lines = n_lines
56
+ self.n_texts += len(texts)
57
+
58
+ def _calculate_similarity(self, text_1: str, text_2: str) -> float:
59
+ """Calculate and cache the similarity between two texts."""
60
+ return SequenceMatcher(None, text_1, text_2).quick_ratio()
33
61
 
34
62
  def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
35
63
  """Get a line completion suggestion."""
36
- result: Suggestion | None = None
64
+ self.process_history()
65
+
37
66
  line = document.current_line.lstrip()
38
- if line:
39
- if line in self.cache:
40
- result = self.cache[line]
41
- else:
42
- result = self.lookup_suggestion(line)
43
- if result:
44
- if len(self.cache) > self.cache_size:
45
- key_to_remove = self.cache_keys.popleft()
46
- if key_to_remove in self.cache:
47
- del self.cache[key_to_remove]
48
-
49
- self.cache_keys.append(line)
50
- self.cache[line] = result
51
- return result
52
-
53
- def lookup_suggestion(self, line: str) -> Suggestion | None:
54
- """Find the most recent matching line in the history."""
55
- # Loop history, most recent item first
56
- for text in self.history._loaded_strings:
57
- if line in text:
58
- # Loop over lines of item in reverse order
59
- for hist_line in text.splitlines()[::-1]:
60
- hist_line = hist_line.strip()
61
- if hist_line.startswith(line):
62
- # Return from the match to end from the history line
63
- suggestion = hist_line[len(line) :]
64
- return Suggestion(suggestion)
67
+ if not line:
68
+ return None
69
+
70
+ suffixes = self.prefix_dict[line]
71
+
72
+ texts = self.history._loaded_strings
73
+ n_lines = self.n_lines
74
+
75
+ best_score = 0.0
76
+ best_suffix = ""
77
+
78
+ # Rank candidates
79
+ max_count = max([1, *(len(x) for x in suffixes.values())])
80
+ for suffix, instances in suffixes.items():
81
+ count = len(instances)
82
+ for instance in instances:
83
+ text = texts[instance["index"]]
84
+ context_similarity = self.calculate_similarity(document.text, text)
85
+ score = (
86
+ 0
87
+ # Similarity of prefix to line
88
+ # 0.333 * len(line) / len(match.group("prefix"))
89
+ # NUmber of instances in history
90
+ + 0.3 * count / max_count
91
+ # Recentness
92
+ + 0.3 * instance["line"] / n_lines
93
+ # Similarity of context to document
94
+ + 0.4 * context_similarity
95
+ )
96
+ # log.debug("%s %r", score, suffix)
97
+ if score > 0.95:
98
+ return Suggestion(suffix)
99
+ if score > best_score:
100
+ best_score = score
101
+ best_suffix = suffix
102
+ if best_suffix:
103
+ return Suggestion(best_suffix)
65
104
  return None
66
105
 
67
- '''
68
- def lookup_suggestion(self, line: "str") -> "Optional[Suggestion]":
69
- ""Suggest most commonly used line."""
70
- results = {}
71
- pat = re.compile(f"^\\s*{re.escape(line)}(.*)$", re.MULTILINE)
72
- for text in self.history.get_strings():
73
- if line in text:
74
- for hist_line in text.splitlines()[::-1]:
75
- hist_line = hist_line.strip()
76
- if hist_line.startswith(line):
77
- # Return from the match to end from the history line
78
- suggestion = hist_line[len(line) :]
79
- if suggestion not in results:
80
- results[suggestion] = 1
81
- else:
82
- results[suggestion] += 1
83
- return Suggestion(
84
- {b: a for a, b in results.items()}[max(results.values())]
85
- )
86
- '''
87
-
88
-
89
- class KernelAutoSuggest(AutoSuggest):
90
- """Suggest line completions from kernel history."""
91
-
92
- def __init__(self, kernel: Kernel) -> None:
93
- """Set the kernel instance in initialization."""
94
- self.kernel = kernel
95
-
96
- def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
97
- """Doe nothing."""
98
- return None
99
106
 
100
- async def get_suggestion_async(
101
- self, buff: Buffer, document: Document
102
- ) -> Suggestion | None:
103
- """Return suggestions based on matching kernel history."""
104
- line = document.current_line.strip()
105
- if line:
106
- suggestions = await self.kernel.history_(f"*{line}*")
107
- log.debug("Suggestor got suggestions %s", suggestions)
108
- if suggestions:
109
- _, _, text = suggestions[0]
110
- # Find matching line
111
- for hist_line in text.split("\n"):
112
- hist_line = hist_line.strip()
113
- if hist_line.startswith(line):
114
- # Return from the match to end from the history line
115
- suggestion = hist_line[len(line) :]
116
- log.debug("Suggesting %s", suggestion)
117
- return Suggestion(suggestion)
118
- return None
107
+ # class KernelAutoSuggest(AutoSuggest):
108
+ # """Suggest line completions from kernel history."""
109
+
110
+ # def __init__(self, kernel: Kernel) -> None:
111
+ # """Set the kernel instance in initialization."""
112
+ # self.kernel = kernel
113
+
114
+ # def get_suggestion(self, buffer: Buffer, document: Document) -> Suggestion | None:
115
+ # """Doe nothing."""
116
+ # return None
117
+
118
+ # async def get_suggestion_async(
119
+ # self, buff: Buffer, document: Document
120
+ # ) -> Suggestion | None:
121
+ # """Return suggestions based on matching kernel history."""
122
+ # line = document.current_line.strip()
123
+ # if line:
124
+ # suggestions = await self.kernel.history_(f"*{line}*")
125
+ # log.debug("Suggestor got suggestions %s", suggestions)
126
+ # if suggestions:
127
+ # _, _, text = suggestions[0]
128
+ # # Find matching line
129
+ # for hist_line in text.split("\n"):
130
+ # hist_line = hist_line.strip()
131
+ # if hist_line.startswith(line):
132
+ # # Return from the match to end from the history line
133
+ # suggestion = hist_line[len(line) :]
134
+ # log.debug("Suggesting %s", suggestion)
135
+ # return Suggestion(suggestion)
136
+ # return None
119
137
 
120
138
 
121
139
  class ConditionalAutoSuggestAsync(ConditionalAutoSuggest):
@@ -1 +1,33 @@
1
1
  """Contain various application tab implementations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pkgutil import resolve_name
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from euporie.core.tabs.base import Tab
11
+
12
+
13
+ @dataclass
14
+ class TabRegistryEntry:
15
+ """Class to store tab information."""
16
+
17
+ path: str
18
+ name: str
19
+ mime_types: set = field(default_factory=set)
20
+ file_extensions: dict[str, None] = field(default_factory=dict)
21
+ weight: int = 0
22
+
23
+ @property
24
+ def tab_class(self) -> type[Tab]:
25
+ """Import and return the tab class."""
26
+ return resolve_name(self.path)
27
+
28
+ def __lt__(self, other: TabRegistryEntry) -> bool:
29
+ """Sort by weight."""
30
+ return self.weight < other.weight
31
+
32
+
33
+ _TAB_REGISTRY: list[TabRegistryEntry] = []
@@ -0,0 +1,113 @@
1
+ """Defines tab settings."""
2
+
3
+ from prompt_toolkit.filters import buffer_has_focus
4
+
5
+ from euporie.core.config import add_setting
6
+
7
+ add_setting(
8
+ name="kernel_name",
9
+ group="euporie.core.tabs.kernel",
10
+ flags=["--kernel-name", "--kernel"],
11
+ type_=str,
12
+ help_="The name of the kernel to start by default",
13
+ default="python3",
14
+ description="""
15
+ The name of the kernel selected automatically by the console app or in new
16
+ notebooks. If set to an empty string, the user will be asked which kernel
17
+ to launch.
18
+ """,
19
+ )
20
+
21
+ add_setting(
22
+ name="record_cell_timing",
23
+ group="euporie.core.tabs.kernel",
24
+ title="cell timing recording",
25
+ flags=["--record-cell-timing"],
26
+ type_=bool,
27
+ help_="Should timing data be recorded in cell metadata.",
28
+ default=False,
29
+ schema={
30
+ "type": "boolean",
31
+ },
32
+ description="""
33
+ When set, execution timing data will be recorded in cell metadata.
34
+ """,
35
+ )
36
+
37
+ add_setting(
38
+ name="show_remote_inputs",
39
+ group="euporie.core.tabs.kernel",
40
+ flags=["--show-remote-inputs"],
41
+ type_=bool,
42
+ help_="Display inputs sent to the kernel by other clients",
43
+ default=True,
44
+ description="""
45
+ If set to `True`, all code input sent to the kernel by any client will be
46
+ displayed.
47
+
48
+ If set to `False`, only inputs sent to the kernel by the current instance
49
+ of euporie will be displayed, and all other inputs will be ignored.
50
+
51
+ """,
52
+ )
53
+
54
+ add_setting(
55
+ name="show_remote_outputs",
56
+ group="euporie.core.tabs.kernel",
57
+ flags=["--show-remote-outputs"],
58
+ type_=bool,
59
+ help_="Display kernel outputs triggered by other clients",
60
+ default=True,
61
+ description="""
62
+ If set to `False`, only outputs generated by code input from the current
63
+ instance of euporie will be displayed, and all other outputs will be
64
+ ignored.
65
+
66
+ If set to `True`, all outputs generated by the kernel will be
67
+ displayed.
68
+ """,
69
+ )
70
+
71
+ # euporie.core.tabs.notebook
72
+
73
+ add_setting(
74
+ name="save_widget_state",
75
+ group="euporie.core.tabs.notebook",
76
+ flags=["--save-widget-state"],
77
+ type_=bool,
78
+ help_="Save a notebook's widget state in the notebook metadata",
79
+ default=True,
80
+ description="""
81
+ When set to ``True``, the state of any widgets in the current notebook will
82
+ be saves in the notebook's metadata. This enables widgets to be displayed
83
+ when the notebook is re-opened without having to re-run the notebook.
84
+ """,
85
+ )
86
+
87
+ add_setting(
88
+ name="max_notebook_width",
89
+ group="euporie.core.tabs.notebook",
90
+ flags=["--max-notebook-width"],
91
+ type_=int,
92
+ help_="Maximum width of notebooks",
93
+ default=120,
94
+ schema={
95
+ "minimum": 1,
96
+ },
97
+ description="""
98
+ The maximum width at which to display a notebook.
99
+ """,
100
+ )
101
+
102
+ add_setting(
103
+ name="expand",
104
+ group="euporie.core.tabs.notebook",
105
+ flags=["--expand"],
106
+ type_=bool,
107
+ help_="Use the full width to display notebooks",
108
+ default=False,
109
+ description="""
110
+ Whether the notebook page should expand to fill the available width
111
+ """,
112
+ cmd_filter=~buffer_has_focus,
113
+ )