pyqt-code-editor 0.0.54__tar.gz → 0.0.55__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 (88) hide show
  1. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/PKG-INFO +2 -2
  2. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/__init__.py +1 -1
  3. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/_settings.py +1 -0
  4. pyqt_code_editor-0.0.55/pyqt_code_editor/components/sigmund/__init__.py +1 -0
  5. pyqt_code_editor-0.0.55/pyqt_code_editor/components/sigmund/confirm_run_code_dialog.py +59 -0
  6. pyqt_code_editor-0.0.54/pyqt_code_editor/components/sigmund.py → pyqt_code_editor-0.0.55/pyqt_code_editor/components/sigmund/editor_workspace.py +5 -26
  7. pyqt_code_editor-0.0.55/pyqt_code_editor/components/sigmund/sigmund.py +28 -0
  8. pyqt_code_editor-0.0.55/pyqt_code_editor/components/sigmund/sigmund_analyst_chat_widget.py +35 -0
  9. pyqt_code_editor-0.0.55/pyqt_code_editor/components/sigmund/sigmund_analyst_widget.py +191 -0
  10. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/merge_undo_actions.py +1 -6
  11. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/readme.md +1 -1
  12. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/COPYING +0 -0
  13. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyproject.toml +0 -0
  14. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/app.py +0 -0
  15. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/__init__.py +0 -0
  16. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/code_editors.py +0 -0
  17. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  18. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
  19. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
  20. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
  21. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/languages/python.py +0 -0
  22. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/code_editors/languages/text.py +0 -0
  23. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/__init__.py +0 -0
  24. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/editor_panel.py +0 -0
  25. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/find_in_files.py +0 -0
  26. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/jupyter_console.py +0 -0
  27. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/project_explorer.py +0 -0
  28. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/settings_panel.py +0 -0
  29. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/components/workspace_explorer.py +0 -0
  30. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/environment_manager.py +0 -0
  31. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/__init__.py +0 -0
  32. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/auto_indent.py +0 -0
  33. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/auto_pair.py +0 -0
  34. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/base.py +0 -0
  35. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/check.py +0 -0
  36. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/comment.py +0 -0
  37. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/complete.py +0 -0
  38. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/execute.py +0 -0
  39. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/file_link.py +0 -0
  40. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/highlight_matching_brackets.py +0 -0
  41. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/line_number.py +0 -0
  42. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
  43. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/search_replace.py +0 -0
  44. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/shortcuts.py +0 -0
  45. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
  46. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/symbols.py +0 -0
  47. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/theme.py +0 -0
  48. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  49. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/mixins/zoom.py +0 -0
  50. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/sigmund-analyst.png +0 -0
  51. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/signal_router.py +0 -0
  52. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  53. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  54. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
  55. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
  56. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
  57. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/themes.py +0 -0
  58. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/utils/__init__.py +0 -0
  59. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/utils/languages/__init__.py +0 -0
  60. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
  61. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
  62. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
  63. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
  64. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/watchdog.py +0 -0
  65. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/__init__.py +0 -0
  66. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
  67. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/completion_popup.py +0 -0
  68. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/dock.py +0 -0
  69. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
  70. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
  71. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
  72. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
  73. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
  74. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
  75. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/__init__.py +0 -0
  76. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/languages/__init__.py +0 -0
  77. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/languages/generic.py +0 -0
  78. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/languages/python.py +0 -0
  79. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/manager.py +0 -0
  80. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/process.py +0 -0
  81. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/__init__.py +0 -0
  82. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/codestral.py +0 -0
  83. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/jedi.py +0 -0
  84. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/ruff.py +0 -0
  85. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
  86. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/symbol.py +0 -0
  87. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/providers/tree_sitter.py +0 -0
  88. {pyqt_code_editor-0.0.54 → pyqt_code_editor-0.0.55}/pyqt_code_editor/worker/settings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqt_code_editor
3
- Version: 0.0.54
3
+ Version: 0.0.55
4
4
  Summary: Fully featured code-editor widgets for PyQt
5
5
  Keywords: code editor,pyqt,ide
6
6
  Author-email: Sebastiaan Mathôt <s.mathot@cogsci.nl>
@@ -32,7 +32,7 @@ Project-URL: Source, https://github.com/open-cogsci/sigmund-analyst
32
32
 
33
33
  # PyQt Code Editor / Sigmund Analyst
34
34
 
35
- Copyright 2025 Sebastiaan Mathôt
35
+ Copyright 2025-2026 Sebastiaan Mathôt
36
36
 
37
37
 
38
38
  ## About
@@ -2,4 +2,4 @@
2
2
 
3
3
  from ._settings import settings
4
4
 
5
- __version__ = '0.0.54'
5
+ __version__ = '0.0.55'
@@ -120,6 +120,7 @@ class Settings(QObject):
120
120
  check_interval_delay = SettingProperty(5000, "Checking")
121
121
 
122
122
  # Sigmund provider
123
+ sigmund_review_actions = SettingProperty(True, "Sigmund")
123
124
  sigmund_max_context = SettingProperty(2000, "Sigmund")
124
125
  sigmund_fim_endpoint = SettingProperty('http://localhost:5000/code_completion/fim', "Sigmund")
125
126
  sigmund_token = SettingProperty(None, "Sigmund")
@@ -0,0 +1 @@
1
+ from .sigmund import Sigmund
@@ -0,0 +1,59 @@
1
+ from qtpy.QtWidgets import QVBoxLayout, QDialogButtonBox, QLabel, \
2
+ QSizePolicy, QDialog, QSplitter
3
+ from qtpy.QtCore import Qt
4
+ import logging
5
+ from sigmund_qtwidget.chat_browser import ChatBrowser
6
+ from pyqt_code_editor.code_editors import create_editor
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ MAX_MESSAGE_HEIGHT = 200
11
+
12
+ class ConfirmRunCodeDialog(QDialog):
13
+ """
14
+ A modal dialog that displays code to be executed and asks the user to confirm or cancel.
15
+ """
16
+
17
+ def __init__(self, parent, code: str, language: str):
18
+ super().__init__(parent)
19
+
20
+ self.setWindowTitle(f"Confirm {language} code execution")
21
+
22
+ layout = QVBoxLayout()
23
+ self.setLayout(layout)
24
+
25
+ # Use ChatBrowser to display the AI message
26
+ self.message_browser = ChatBrowser(self)
27
+ self.message_browser.append_message(
28
+ 'ai', 'Sigmund wants to run the following code:')
29
+ self.message_browser.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
30
+ self.message_browser.setMaximumHeight(MAX_MESSAGE_HEIGHT)
31
+
32
+ # Create code editor for the code to be executed
33
+ self.code_view = create_editor(language=language)
34
+ self.code_view.setPlainText(code)
35
+ self.code_view.setReadOnly(True)
36
+
37
+ # Create a vertical splitter to hold the message browser and the code
38
+ splitter = QSplitter(Qt.Vertical)
39
+ splitter.addWidget(self.message_browser)
40
+ splitter.addWidget(self.code_view)
41
+ layout.addWidget(splitter)
42
+
43
+ # The disclaimer label
44
+ disclaimer_label = QLabel(
45
+ "Carefully review the code before execution. Executing code can have unintended consequences.",
46
+ self
47
+ )
48
+ disclaimer_label.setWordWrap(True)
49
+ disclaimer_label.setObjectName('control-warning')
50
+ disclaimer_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
51
+ layout.addWidget(disclaimer_label)
52
+
53
+ # Dialog buttons
54
+ self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
55
+ self.button_box.accepted.connect(self.accept)
56
+ self.button_box.rejected.connect(self.reject)
57
+ layout.addWidget(self.button_box)
58
+
59
+ self.resize(800, 600)
@@ -1,6 +1,3 @@
1
- from sigmund_qtwidget.sigmund_widget import SigmundWidget
2
- from ..widgets import Dock
3
- from .. import watchdog, settings
4
1
  import textwrap
5
2
  import logging
6
3
  logger = logging.getLogger(__name__)
@@ -76,11 +73,14 @@ class EditorWorkspace:
76
73
 
77
74
  def set(self, content, language):
78
75
  text_cursor = self._editor.textCursor()
76
+ text_cursor.beginEditBlock()
79
77
  if text_cursor.hasSelection():
80
78
  text_cursor.insertText(content)
81
- self._editor.setTextCursor(text_cursor)
82
79
  else:
83
- self._editor.setPlainText(content)
80
+ text_cursor.select(text_cursor.Document)
81
+ text_cursor.insertText(content)
82
+ text_cursor.endEditBlock()
83
+ self._editor.setTextCursor(text_cursor)
84
84
  self._editor.set_modified(True)
85
85
 
86
86
  def has_changed(self, content, language):
@@ -98,24 +98,3 @@ class EditorWorkspace:
98
98
  if content is None:
99
99
  return ''
100
100
  return content
101
-
102
-
103
- class Sigmund(Dock):
104
- def __init__(self, parent, editor_panel):
105
- super().__init__('Sigmund', parent)
106
- self.setObjectName("sigmund")
107
- workspace = EditorWorkspace(editor_panel)
108
- self.sigmund_widget = SigmundWidget(self, application='Sigmund Analyst')
109
- self.sigmund_widget.setStyleSheet(f'font-size: {settings.font_size}pt')
110
- self.sigmund_widget.set_workspace_manager(workspace)
111
- self.setWidget(self.sigmund_widget)
112
-
113
- def setVisible(self, visible):
114
- if visible:
115
- logger.info('starting sigmund connector')
116
- self.sigmund_widget.start_server()
117
- watchdog.register_subprocess(self.sigmund_widget.server_pid)
118
- else:
119
- logger.info('stopping sigmund connector')
120
- self.sigmund_widget.stop_server()
121
- super().setVisible(visible)
@@ -0,0 +1,28 @@
1
+ from ...widgets import Dock
2
+ from ... import watchdog, settings
3
+ from .editor_workspace import EditorWorkspace
4
+ from .sigmund_analyst_widget import SigmundAnalystWidget
5
+ import logging
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class Sigmund(Dock):
10
+ def __init__(self, parent, editor_panel):
11
+ super().__init__('Sigmund', parent)
12
+ self.setObjectName("sigmund")
13
+ workspace = EditorWorkspace(editor_panel)
14
+ self.sigmund_widget = SigmundAnalystWidget(
15
+ self, editor_panel)
16
+ self.sigmund_widget.setStyleSheet(f'font-size: {settings.font_size}pt')
17
+ self.sigmund_widget.set_workspace_manager(workspace)
18
+ self.setWidget(self.sigmund_widget)
19
+
20
+ def setVisible(self, visible):
21
+ if visible:
22
+ logger.info('starting sigmund connector')
23
+ self.sigmund_widget.start_server()
24
+ watchdog.register_subprocess(self.sigmund_widget.server_pid)
25
+ else:
26
+ logger.info('stopping sigmund connector')
27
+ self.sigmund_widget.stop_server()
28
+ super().setVisible(visible)
@@ -0,0 +1,35 @@
1
+ from qtpy.QtWidgets import QCheckBox, QWidget, QVBoxLayout
2
+ from sigmund_qtwidget.chat_widget import ChatWidget
3
+ from ... import settings
4
+
5
+
6
+ class SigmundAnalystChatWidget(ChatWidget):
7
+ """Extended chat widget with Sigmund Analyst-specific options"""
8
+
9
+ def __init__(self, parent=None):
10
+ super().__init__(parent)
11
+ # Create a container for the checkboxes
12
+ options_container = QWidget()
13
+ options_layout = QVBoxLayout(options_container)
14
+ options_layout.setContentsMargins(5, 5, 5, 5)
15
+ options_layout.setSpacing(2)
16
+ # "Review proposed changes" checkbox
17
+ self._review_actions_checkbox = QCheckBox(
18
+ "Review Sigmund's actions (recommended)")
19
+ self._review_actions_checkbox.setChecked(
20
+ settings.sigmund_review_actions)
21
+ self._review_actions_checkbox.stateChanged.connect(
22
+ self._on_review_actions_changed)
23
+ options_layout.addWidget(self._review_actions_checkbox)
24
+ # Insert the options container before the input container
25
+ main_layout = self.layout()
26
+ main_layout.insertWidget(main_layout.count(), options_container)
27
+
28
+ def _on_review_actions_changed(self, state):
29
+ """Store the review changes setting."""
30
+ settings.sigmund_review_actions = bool(state)
31
+
32
+ def append_message(self, msg_type, text, scroll=True):
33
+ if msg_type == 'ai' and '(Suggesting IDE action)' in text:
34
+ text = '⚙️ Sigmund is working …'
35
+ self._chat_browser.append_message(msg_type, text, scroll)
@@ -0,0 +1,191 @@
1
+ import os
2
+ import time
3
+ from sigmund_qtwidget.sigmund_widget import SigmundWidget
4
+ from qtpy.QtCore import QEventLoop, QTimer
5
+ from qtpy.QtWidgets import QMessageBox, QDialog
6
+ from .sigmund_analyst_chat_widget import SigmundAnalystChatWidget
7
+ from .confirm_run_code_dialog import ConfirmRunCodeDialog
8
+ from ... import settings
9
+ import logging
10
+ logger = logging.getLogger(__name__)
11
+
12
+ ACTION_CANCELLED = 'I do not approve this action.'
13
+
14
+
15
+ class SigmundAnalystWidget(SigmundWidget):
16
+ """Extends the default Sigmund widget with Sigmund Analyst-specific
17
+ functionality.
18
+ """
19
+ chat_widget_cls = SigmundAnalystChatWidget
20
+
21
+ def __init__(self, parent, editor_panel):
22
+ super().__init__(parent, application='Sigmund Analyst')
23
+ try:
24
+ self._app = parent.parent()
25
+ self._jupyter_console = self._app._jupyter_console
26
+ # Connect the code_executed signal to a slot
27
+ except AttributeError:
28
+ logger.warning('No Jupyter console found.')
29
+ self._jupyter_console = None
30
+ else:
31
+ self._jupyter_console.execution_complete.connect(
32
+ self._handle_execution_result)
33
+ self._editor_panel = editor_panel
34
+ self._transient_settings = {
35
+ 'tool_ide_open_file': 'true',
36
+ 'tool_ide_execute_code': 'true'
37
+ }
38
+ # Store execution results
39
+ self._execution_result = None
40
+ self._execution_loop = None
41
+
42
+ def _confirm_action(self, action_type, details):
43
+ """Show a confirmation dialog for the proposed action.
44
+
45
+ Args:
46
+ action_type (str): Type of action (e.g., "open file", "execute code")
47
+ details (str): Detailed information about the action
48
+
49
+ Returns:
50
+ bool: True if action is approved, False otherwise
51
+ """
52
+ if not settings.sigmund_review_actions:
53
+ return True
54
+
55
+ if action_type == "open file":
56
+ return QMessageBox.question(
57
+ self,
58
+ "Open file",
59
+ f"Sigmund wants to open: \n\n{details}",
60
+ QMessageBox.Ok | QMessageBox.Cancel,
61
+ QMessageBox.Ok
62
+ ) == QMessageBox.Ok
63
+ dialog = ConfirmRunCodeDialog(
64
+ self,
65
+ action_type,
66
+ details
67
+ )
68
+ return dialog.exec_() == QDialog.Accepted
69
+
70
+ def _handle_execution_result(self, output, result):
71
+ """Slot that handles the code_executed signal."""
72
+ self._execution_result = (output, result)
73
+ if self._execution_loop and self._execution_loop.isRunning():
74
+ time.sleep(0.5) # Wait for errors to appear
75
+ self._execution_loop.quit()
76
+
77
+ def run_command_open_file(self, path):
78
+ if settings.sigmund_review_actions:
79
+ if not QMessageBox.question(self,
80
+ "Confirm open file",
81
+ f"Sigmund wants to open: \n\n{path}",
82
+ QMessageBox.Ok | QMessageBox.Cancel,
83
+ QMessageBox.Ok) == QMessageBox.Ok:
84
+ return ACTION_CANCELLED
85
+ try:
86
+ self._editor_panel.open_file(path)
87
+ except Exception as e:
88
+ return f'Failed to open file: {e}'
89
+ return f'Opened file: {path}'
90
+
91
+ def run_command_execute_code(self, code, language):
92
+ if settings.sigmund_review_actions:
93
+ if not ConfirmRunCodeDialog(self, code, language).exec() \
94
+ == ConfirmRunCodeDialog.Accepted:
95
+ return ACTION_CANCELLED
96
+ if self._jupyter_console is None:
97
+ return 'Code execution not supported.'
98
+ self._app._toggle_dock_widget(self._jupyter_console, show=True)
99
+ self._execution_result = None
100
+ self._execution_loop = QEventLoop()
101
+ QTimer.singleShot(30000, self._execution_loop.quit) # 30 second timeout
102
+ self._jupyter_console.execute_code(code)
103
+ self._execution_loop.exec_()
104
+ self._execution_loop = None
105
+ if self._execution_result is None:
106
+ return 'Code execution timed out or failed to return a result.'
107
+ try:
108
+ console_content = self._jupyter_console.get_current_console().jupyter_widget._control.toPlainText()
109
+ except Exception as e:
110
+ return f'Failed to get results from console content: {e}'
111
+ console_content = ''
112
+ console_content = '\n'.join(console_content.splitlines()[-100:])
113
+ return f'# Console output (may be truncated):\n\n{console_content}'
114
+ @property
115
+ def _editor(self):
116
+ return self._editor_panel.active_editor()
117
+
118
+ def send_user_message(self, text, *args, **kwargs):
119
+ current_path = self._editor.code_editor_file_path
120
+ # If the editor is not linked to a file, simply use the working
121
+ # directory.
122
+ if current_path is None:
123
+ current_path = '[unsaved file]'
124
+ working_directory = os.getcwd()
125
+ else:
126
+ working_directory = os.path.dirname(current_path)
127
+
128
+ # Initialize with default value
129
+ working_directory_contents = "(No directory contents available)"
130
+
131
+ # Get directory contents with priority to top-level items
132
+ top_level = []
133
+ all_items = []
134
+
135
+ # Only wrap file system operations in try-except
136
+ try:
137
+ # Get top-level items (depth=1)
138
+ top_level = [f for f in os.listdir(working_directory)
139
+ if os.path.isfile(os.path.join(working_directory, f))
140
+ or os.path.isdir(os.path.join(working_directory, f))]
141
+
142
+ # Then get deeper items if we haven't reached our limit
143
+ remaining_slots = max(0, 20 - len(top_level))
144
+ if remaining_slots > 0:
145
+ for root, dirs, files in os.walk(working_directory):
146
+ # Skip the top level since we already have it
147
+ if root == working_directory:
148
+ continue
149
+ # Add files first
150
+ for file in files:
151
+ if remaining_slots <= 0:
152
+ break
153
+ rel_path = os.path.relpath(os.path.join(root, file), working_directory)
154
+ all_items.append(f".\\{rel_path}")
155
+ remaining_slots -= 1
156
+ # Then add directories if we still have space
157
+ for dir in dirs:
158
+ if remaining_slots <= 0:
159
+ break
160
+ rel_path = os.path.relpath(os.path.join(root, dir), working_directory)
161
+ all_items.append(f".\\{rel_path}\\")
162
+ remaining_slots -= 1
163
+
164
+ # Combine top level and deeper items
165
+ working_directory_contents = "\n".join(
166
+ sorted([f".\\{item}" if os.path.isfile(os.path.join(working_directory, item))
167
+ else f".\\{item}\\" for item in top_level] +
168
+ all_items)
169
+ )
170
+
171
+ if len(top_level) + len(all_items) > 20:
172
+ working_directory_contents += "\n... (additional files truncated)"
173
+
174
+ except PermissionError:
175
+ working_directory_contents = "(Could not read some directory contents - permission denied)"
176
+ except OSError as e:
177
+ working_directory_contents = f"(Could not read directory contents: {str(e)})"
178
+
179
+ system_prompt = f'''## Working directory
180
+
181
+ The workspace corresponds to the following file: {current_path}
182
+ The working directory is: {working_directory}
183
+
184
+ Overview of working directory:
185
+
186
+ ```
187
+ {working_directory_contents}
188
+ ```
189
+ '''
190
+ self._transient_system_prompt = system_prompt
191
+ super().send_user_message(text, *args, **kwargs)
@@ -20,11 +20,6 @@ class MergeUndoActions:
20
20
  key = event.key()
21
21
  modifiers = event.modifiers()
22
22
 
23
- is_navigation = (
24
- key in navigation_keys
25
- and not (modifiers & Qt.KeyboardModifier.ShiftModifier)
26
- )
27
-
28
23
  # Treat platform "command" modifier as well (Control on Windows/Linux, Meta on macOS)
29
24
  ctrl_or_meta = Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.MetaModifier
30
25
  has_ctrl_or_meta = bool(modifiers & ctrl_or_meta)
@@ -39,7 +34,7 @@ class MergeUndoActions:
39
34
  key == Qt.Key.Key_Y
40
35
  )
41
36
 
42
- if is_navigation or is_undo or is_redo:
37
+ if key in navigation_keys or is_undo or is_redo:
43
38
  # Do not wrap these in an edit block
44
39
  super().keyPressEvent(event)
45
40
  return
@@ -1,6 +1,6 @@
1
1
  # PyQt Code Editor / Sigmund Analyst
2
2
 
3
- Copyright 2025 Sebastiaan Mathôt
3
+ Copyright 2025-2026 Sebastiaan Mathôt
4
4
 
5
5
 
6
6
  ## About