pyqt-code-editor 0.0.63__tar.gz → 0.0.65__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 (91) hide show
  1. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/PKG-INFO +1 -1
  2. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/__init__.py +1 -1
  3. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/search_replace.py +16 -11
  4. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/manager.py +56 -19
  5. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/COPYING +0 -0
  6. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyproject.toml +0 -0
  7. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/_settings.py +0 -0
  8. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/app.py +0 -0
  9. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/__init__.py +0 -0
  10. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/code_editors.py +0 -0
  11. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  12. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
  13. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
  14. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
  15. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/python.py +0 -0
  16. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/text.py +0 -0
  17. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/__init__.py +0 -0
  18. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/editor_panel.py +0 -0
  19. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/find_in_files.py +0 -0
  20. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/__init__.py +0 -0
  21. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/jupyter_console.py +0 -0
  22. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/jupyter_console_tab.py +0 -0
  23. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/kernel_spec_manager.py +0 -0
  24. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/project_explorer.py +0 -0
  25. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/settings_panel.py +0 -0
  26. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/__init__.py +0 -0
  27. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/confirm_run_code_dialog.py +0 -0
  28. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/editor_workspace.py +0 -0
  29. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/sigmund.py +0 -0
  30. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/sigmund_analyst_chat_widget.py +0 -0
  31. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/sigmund_analyst_widget.py +0 -0
  32. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/workspace_explorer.py +0 -0
  33. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/environment_manager.py +0 -0
  34. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/__init__.py +0 -0
  35. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/auto_indent.py +0 -0
  36. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/auto_pair.py +0 -0
  37. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/base.py +0 -0
  38. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/check.py +0 -0
  39. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/comment.py +0 -0
  40. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/complete.py +0 -0
  41. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/execute.py +0 -0
  42. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/file_link.py +0 -0
  43. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/highlight_matching_brackets.py +0 -0
  44. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/line_number.py +0 -0
  45. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/merge_undo_actions.py +0 -0
  46. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
  47. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/shortcuts.py +0 -0
  48. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
  49. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/symbols.py +0 -0
  50. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/theme.py +0 -0
  51. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  52. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/zoom.py +0 -0
  53. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/sigmund-analyst.png +0 -0
  54. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/signal_router.py +0 -0
  55. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  56. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  57. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
  58. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
  59. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
  60. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/themes.py +0 -0
  61. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/__init__.py +0 -0
  62. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/__init__.py +0 -0
  63. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
  64. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
  65. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
  66. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
  67. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/watchdog.py +0 -0
  68. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/__init__.py +0 -0
  69. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
  70. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/completion_popup.py +0 -0
  71. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/dock.py +0 -0
  72. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
  73. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
  74. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
  75. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
  76. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
  77. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
  78. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/__init__.py +0 -0
  79. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/languages/__init__.py +0 -0
  80. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/languages/generic.py +0 -0
  81. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/languages/python.py +0 -0
  82. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/process.py +0 -0
  83. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/__init__.py +0 -0
  84. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/codestral.py +0 -0
  85. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/jedi.py +0 -0
  86. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/ruff.py +0 -0
  87. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
  88. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/symbol.py +0 -0
  89. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/tree_sitter.py +0 -0
  90. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/settings.py +0 -0
  91. {pyqt_code_editor-0.0.63 → pyqt_code_editor-0.0.65}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqt_code_editor
3
- Version: 0.0.63
3
+ Version: 0.0.65
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>
@@ -2,4 +2,4 @@
2
2
 
3
3
  from ._settings import settings
4
4
 
5
- __version__ = '0.0.63'
5
+ __version__ = '0.0.65'
@@ -150,34 +150,39 @@ class SearchReplace:
150
150
  highlighter.setDocument(self.document())
151
151
 
152
152
  def setupSearchReplace(self):
153
- """
154
- Call this once in your QPlainTextEdit subclass's __init__
155
- """
156
153
  # Create the search/replace frame
157
154
  self._searchFrame = SearchReplaceFrame(self)
158
155
  self._searchFrame.setVisible(False)
159
-
156
+
160
157
  # Connect signals
161
158
  self._searchFrame.findNextRequested.connect(self.findNext)
162
159
  self._searchFrame.findPrevRequested.connect(self.findPrev)
163
160
  self._searchFrame.replaceOneRequested.connect(self.replaceOne)
164
161
  self._searchFrame.replaceAllRequested.connect(self.replaceAll)
165
-
162
+
166
163
  # Whenever user changes the find text or toggles checkboxes, re-highlight
167
164
  self._searchFrame.findEdit.textChanged.connect(self.updateHighlighter)
168
165
  self._searchFrame.caseBox.toggled.connect(self.updateHighlighter)
169
166
  self._searchFrame.regexBox.toggled.connect(self.updateHighlighter)
170
167
  self._searchFrame.wholeWordBox.toggled.connect(self.updateHighlighter)
171
-
172
- # Shortcuts
173
- findShortcut = QShortcut(QKeySequence.Find, self)
168
+
169
+ # Shortcuts: use platform bindings if available, otherwise fall back
170
+ find_bindings = QKeySequence.keyBindings(QKeySequence.StandardKey.Find)
171
+ if not find_bindings:
172
+ find_bindings = [QKeySequence("Ctrl+F")]
173
+
174
+ replace_bindings = QKeySequence.keyBindings(QKeySequence.StandardKey.Replace)
175
+ if not replace_bindings:
176
+ replace_bindings = [QKeySequence("Ctrl+H")]
177
+
178
+ findShortcut = QShortcut(find_bindings[0], self)
174
179
  findShortcut.setContext(Qt.WidgetWithChildrenShortcut)
175
180
  findShortcut.activated.connect(self.showSearchOnly)
176
-
177
- replShortcut = QShortcut(QKeySequence.Replace, self)
181
+
182
+ replShortcut = QShortcut(replace_bindings[0], self)
178
183
  replShortcut.setContext(Qt.WidgetWithChildrenShortcut)
179
184
  replShortcut.activated.connect(self.showSearchReplace)
180
-
185
+
181
186
  # Hide on Escape if wanted
182
187
  self.escapeAction = QAction(self)
183
188
  self.escapeShortcut = QShortcut(QKeySequence.Cancel, self)
@@ -10,6 +10,33 @@ _workers = {} # pid -> {"process", "request_queue", "result_queue", "is_free"}
10
10
  suspended = False
11
11
 
12
12
 
13
+ def _close_worker_queues(w: dict):
14
+ """Explicitly close a worker's queues to release file descriptors."""
15
+ for q in (w["request_queue"], w["result_queue"]):
16
+ try:
17
+ q.close()
18
+ q.join_thread()
19
+ except Exception as e:
20
+ logger.warning(f"Error closing queue: {e}")
21
+
22
+
23
+ def _cleanup_dead_workers():
24
+ """
25
+ Find and remove any worker processes that have died unexpectedly,
26
+ closing their queues to release file descriptors.
27
+ """
28
+ dead_pids = [pid for pid, w in _workers.items()
29
+ if not w["process"].is_alive()]
30
+ for pid in dead_pids:
31
+ w = _workers.pop(pid)
32
+ logger.info(f"Cleaning up dead worker {pid}")
33
+ _close_worker_queues(w)
34
+ try:
35
+ w["process"].join(timeout=1)
36
+ except Exception as e:
37
+ logger.warning(f"Error joining dead worker {pid}: {e}")
38
+
39
+
13
40
  def send_worker_request(**data) -> (Queue, int):
14
41
  """
15
42
  Send a request to a worker process. If a free worker is available,
@@ -17,7 +44,7 @@ def send_worker_request(**data) -> (Queue, int):
17
44
 
18
45
  The caller can poll the result_queue for responses, and once done,
19
46
  call mark_worker_as_free(pid) to release this worker for future use.
20
-
47
+
21
48
  If workers are suspended, return (None, None).
22
49
  """
23
50
  if suspended:
@@ -50,39 +77,48 @@ def send_worker_request(**data) -> (Queue, int):
50
77
  settings_action = {'action': 'set_settings',
51
78
  'settings': {name: value for name, value in settings}}
52
79
  request_queue.put(settings_action)
53
- # 3. Send the request, return the new workers result queue and pid.
80
+ # 3. Send the request, return the new worker's result queue and pid.
54
81
  request_queue.put(data)
55
82
  return result_queue, pid
56
83
 
57
84
  def mark_worker_as_free(pid: int):
58
85
  """
59
86
  Mark a previously-used worker process (identified by pid)
60
- as free for reuse.
87
+ as free for reuse. If the worker has died, clean it up instead.
61
88
  """
62
89
  w = _workers.get(pid)
63
- if w and w["process"].is_alive():
90
+ if w is None:
91
+ return
92
+ if w["process"].is_alive():
64
93
  w["is_free"] = True
65
94
  logger.info(f"Marking worker {pid} as free")
66
95
  else:
67
- logger.info(f"Attempted to free worker {pid}, but it is not alive or not found.")
68
-
96
+ # The worker died while handling a request; clean up its resources
97
+ logger.info(f"Worker {pid} has died; cleaning up.")
98
+ _workers.pop(pid)
99
+ _close_worker_queues(w)
100
+ w["process"].join(timeout=1)
101
+
69
102
  def check_worker_alive(pid: int) -> bool:
70
103
  w = _workers.get(pid)
71
104
  return w and w["process"].is_alive()
72
105
 
73
106
  def stop_unused_workers(max_free: int = 1, force: bool = False):
74
107
  """
75
- Stop free worker processes until there is at most 'max_free' free processes left.
76
- This keeps us from accumulating too many idle worker processes.
108
+ Stop free worker processes until there is at most 'max_free' free processes
109
+ left. This keeps us from accumulating too many idle worker processes. Also
110
+ prunes any workers that have died unexpectedly.
77
111
  """
78
112
  global _last_stop_unused_time
79
- if force or time.time() - _last_stop_unused_time < STOP_UNUSED_INTERVAL:
113
+ if not force and time.time() - _last_stop_unused_time < STOP_UNUSED_INTERVAL:
80
114
  return
81
115
  _last_stop_unused_time = time.time()
116
+ # Prune dead workers first, so they don't count as free workers below
117
+ _cleanup_dead_workers()
82
118
  # Gather a list of free worker PIDs
83
119
  free_pids = [pid for pid, w in _workers.items() if w["is_free"] and w["process"].is_alive()]
84
120
  logger.info(f"stop_unused_workers called: max_free={max_free}, found {len(free_pids)} free workers")
85
-
121
+
86
122
  # Determine how many we need to stop
87
123
  to_stop = len(free_pids) - max_free
88
124
  if to_stop <= 0:
@@ -92,13 +128,13 @@ def stop_unused_workers(max_free: int = 1, force: bool = False):
92
128
  # Stop some free workers until we have exactly max_free left
93
129
  while to_stop > 0 and free_pids:
94
130
  pid = free_pids.pop()
95
- w = _workers[pid]
131
+ w = _workers.pop(pid)
96
132
  logger.info(f"Stopping free worker {pid} because we have too many.")
97
133
  w["request_queue"].put({"action": "quit"})
98
134
  w["process"].join()
99
- del _workers[pid]
135
+ _close_worker_queues(w)
100
136
  to_stop -= 1
101
-
137
+
102
138
  logger.info("Finished stopping unused workers.")
103
139
 
104
140
  def stop_all_workers():
@@ -111,6 +147,7 @@ def stop_all_workers():
111
147
  logger.info(f"Stopping worker {pid}.")
112
148
  w["request_queue"].put({"action": "quit"})
113
149
  w["process"].join()
150
+ _close_worker_queues(w)
114
151
  del _workers[pid]
115
152
  logger.info("All workers stopped.")
116
153
 
@@ -121,8 +158,8 @@ def update_setting(name, value):
121
158
  for pid, w in _workers.items():
122
159
  if w["process"].is_alive():
123
160
  w["request_queue"].put(settings_action)
124
-
125
-
161
+
162
+
126
163
  def suspend():
127
164
  """Stops all worker processes and ignores all requests until resume() is
128
165
  called.
@@ -131,13 +168,13 @@ def suspend():
131
168
  suspended = True
132
169
  logger.info("Suspending worker processes...")
133
170
  stop_all_workers()
134
-
135
-
171
+
172
+
136
173
  def resume():
137
174
  """Resumes accepting requests."""
138
175
  global suspended
139
176
  suspended = False
140
177
  logger.info("Resuming worker processes...")
141
178
 
142
-
143
- settings.setting_changed.connect(update_setting)
179
+
180
+ settings.setting_changed.connect(update_setting)