pyqt-code-editor 1.0.4__tar.gz → 1.0.5__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-1.0.4 → pyqt_code_editor-1.0.5}/PKG-INFO +1 -1
  2. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/__init__.py +1 -1
  3. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/sigmund/sigmund_analyst_widget.py +1 -1
  4. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/base.py +7 -0
  5. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/manager.py +58 -10
  6. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/COPYING +0 -0
  7. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyproject.toml +0 -0
  8. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/_settings.py +0 -0
  9. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/app.py +0 -0
  10. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/__init__.py +0 -0
  11. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/code_editors.py +0 -0
  12. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  13. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
  14. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
  15. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
  16. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/languages/python.py +0 -0
  17. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/code_editors/languages/text.py +0 -0
  18. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/__init__.py +0 -0
  19. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/editor_panel.py +0 -0
  20. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/find_in_files.py +0 -0
  21. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/jupyter_console/__init__.py +0 -0
  22. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/jupyter_console/jupyter_console.py +0 -0
  23. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/jupyter_console/jupyter_console_tab.py +0 -0
  24. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/jupyter_console/kernel_spec_manager.py +0 -0
  25. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/project_explorer.py +0 -0
  26. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/settings_panel.py +0 -0
  27. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/sigmund/__init__.py +0 -0
  28. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/sigmund/confirm_run_code_dialog.py +0 -0
  29. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/sigmund/editor_workspace.py +0 -0
  30. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/sigmund/sigmund.py +0 -0
  31. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/sigmund/sigmund_analyst_chat_widget.py +0 -0
  32. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/components/workspace_explorer.py +0 -0
  33. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/environment_manager.py +0 -0
  34. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/__init__.py +0 -0
  35. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/auto_indent.py +0 -0
  36. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/auto_pair.py +0 -0
  37. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/check.py +0 -0
  38. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/comment.py +0 -0
  39. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/complete.py +0 -0
  40. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/execute.py +0 -0
  41. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/file_link.py +0 -0
  42. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/highlight_matching_brackets.py +0 -0
  43. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/line_number.py +0 -0
  44. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/merge_undo_actions.py +0 -0
  45. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
  46. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/search_replace.py +0 -0
  47. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/shortcuts.py +0 -0
  48. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
  49. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/symbols.py +0 -0
  50. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/theme.py +0 -0
  51. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  52. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/mixins/zoom.py +0 -0
  53. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/sigmund-analyst.png +0 -0
  54. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/signal_router.py +0 -0
  55. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  56. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  57. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
  58. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
  59. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
  60. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/themes.py +0 -0
  61. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/utils/__init__.py +0 -0
  62. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/utils/languages/__init__.py +0 -0
  63. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
  64. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
  65. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
  66. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
  67. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/watchdog.py +0 -0
  68. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/__init__.py +0 -0
  69. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
  70. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/completion_popup.py +0 -0
  71. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/dock.py +0 -0
  72. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
  73. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
  74. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
  75. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
  76. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
  77. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
  78. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/__init__.py +0 -0
  79. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/languages/__init__.py +0 -0
  80. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/languages/generic.py +0 -0
  81. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/languages/python.py +0 -0
  82. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/process.py +0 -0
  83. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/__init__.py +0 -0
  84. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/codestral.py +0 -0
  85. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/jedi.py +0 -0
  86. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/ruff.py +0 -0
  87. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
  88. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/symbol.py +0 -0
  89. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/providers/tree_sitter.py +0 -0
  90. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/pyqt_code_editor/worker/settings.py +0 -0
  91. {pyqt_code_editor-1.0.4 → pyqt_code_editor-1.0.5}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyqt_code_editor
3
- Version: 1.0.4
3
+ Version: 1.0.5
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__ = '1.0.4'
5
+ __version__ = '1.0.5'
@@ -174,7 +174,7 @@ class SigmundAnalystWidget(SigmundWidget):
174
174
  time.sleep(.5)
175
175
  self._execution_result = None
176
176
  self._execution_loop = QEventLoop()
177
- QTimer.singleShot(30000, self._execution_loop.quit) # 30 second timeout
177
+ QTimer.singleShot(5 * 60000, self._execution_loop.quit) # 5min timeout
178
178
  self._jupyter_console.execute_code(code)
179
179
  self._execution_loop.exec_()
180
180
  self._execution_loop = None
@@ -146,6 +146,13 @@ class Base:
146
146
  # This function periodically prunes worker processes if they are unused.
147
147
  for pid in to_remove:
148
148
  self._active_requests.pop(pid, None)
149
+ # Non-blockingly finish cleaning up any workers that were
150
+ # previously asked to stop, and possibly ask further idle
151
+ # workers to stop (throttled internally). Neither call blocks
152
+ # the GUI thread, even on platforms (e.g. Windows) where
153
+ # joining a worker process can take a noticeable amount of
154
+ # time.
155
+ manager.reap_stopping_workers()
149
156
  manager.stop_unused_workers()
150
157
 
151
158
  def set_modified(self, modified):
@@ -7,6 +7,7 @@ logger = logging.getLogger(__name__)
7
7
  STOP_UNUSED_INTERVAL = 10
8
8
  _last_stop_unused_time = 0
9
9
  _workers = {} # pid -> {"process", "request_queue", "result_queue", "is_free"}
10
+ _stopping = {} # pid -> same shape as _workers, but already asked to quit
10
11
  suspended = False
11
12
 
12
13
 
@@ -103,11 +104,45 @@ def check_worker_alive(pid: int) -> bool:
103
104
  w = _workers.get(pid)
104
105
  return w and w["process"].is_alive()
105
106
 
107
+ def reap_stopping_workers():
108
+ """
109
+ Finish cleaning up worker processes that were previously asked to quit
110
+ (via stop_unused_workers()), but only once they have actually
111
+ terminated. This is non-blocking: we merely check `is_alive()` for each
112
+ stopping worker, and only `join()` (with a zero timeout, so it returns
113
+ essentially instantly) processes that have already exited. Workers that
114
+ haven't exited yet are left in place and will be picked up on a later
115
+ call.
116
+
117
+ This should be called periodically (e.g. from the same timer that calls
118
+ stop_unused_workers()) so that workers asked to stop are eventually
119
+ cleaned up and their queues closed.
120
+ """
121
+ done_pids = [pid for pid, w in _stopping.items() if not w["process"].is_alive()]
122
+ for pid in done_pids:
123
+ w = _stopping.pop(pid)
124
+ logger.info(f"Reaping stopped worker {pid}.")
125
+ try:
126
+ w["process"].join(timeout=0)
127
+ except Exception as e:
128
+ logger.warning(f"Error joining stopped worker {pid}: {e}")
129
+ _close_worker_queues(w)
130
+
106
131
  def stop_unused_workers(max_free: int = 1, force: bool = False):
107
132
  """
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.
133
+ Request that free worker processes stop until there is at most
134
+ 'max_free' free processes left. This keeps us from accumulating too
135
+ many idle worker processes. Also prunes any workers that have died
136
+ unexpectedly.
137
+
138
+ Important: this function never blocks waiting for a worker to actually
139
+ exit. Workers that we decide to stop are sent a "quit" message and
140
+ moved into an internal `_stopping` bookkeeping dict; they are only
141
+ joined (and their queues closed) once they have actually terminated,
142
+ which is handled by reap_stopping_workers(). This matters because
143
+ joining a process that hasn't exited yet can block for a noticeable
144
+ amount of time, especially on Windows, and this function is typically
145
+ called from a periodic timer on the main/GUI thread.
111
146
  """
112
147
  global _last_stop_unused_time
113
148
  if not force and time.time() - _last_stop_unused_time < STOP_UNUSED_INTERVAL:
@@ -125,21 +160,24 @@ def stop_unused_workers(max_free: int = 1, force: bool = False):
125
160
  logger.info("No free workers to stop.")
126
161
  return
127
162
 
128
- # Stop some free workers until we have exactly max_free left
163
+ # Ask some free workers to stop until we have max_free left. We don't
164
+ # wait for them to exit here; see reap_stopping_workers().
129
165
  while to_stop > 0 and free_pids:
130
166
  pid = free_pids.pop()
131
167
  w = _workers.pop(pid)
132
- logger.info(f"Stopping free worker {pid} because we have too many.")
168
+ logger.info(f"Asking free worker {pid} to stop because we have too many.")
133
169
  w["request_queue"].put({"action": "quit"})
134
- w["process"].join()
135
- _close_worker_queues(w)
170
+ _stopping[pid] = w
136
171
  to_stop -= 1
137
172
 
138
- logger.info("Finished stopping unused workers.")
173
+ logger.info("Finished requesting unused workers to stop.")
139
174
 
140
175
  def stop_all_workers():
141
176
  """
142
- Cleanly shut down all worker processes.
177
+ Cleanly shut down all worker processes. Unlike stop_unused_workers(),
178
+ this function blocks until every worker has actually exited, since it
179
+ is only used when we need a hard guarantee that no worker processes
180
+ remain (e.g. on application shutdown or when suspending).
143
181
  """
144
182
  logger.info(f"Stopping {len(_workers)} worker processes...")
145
183
  for pid, w in list(_workers.items()):
@@ -149,6 +187,16 @@ def stop_all_workers():
149
187
  w["process"].join()
150
188
  _close_worker_queues(w)
151
189
  del _workers[pid]
190
+ # Also finish off any workers that were previously asked to stop (via
191
+ # stop_unused_workers()) but haven't been reaped yet.
192
+ for pid, w in list(_stopping.items()):
193
+ logger.info(f"Joining previously-stopping worker {pid}.")
194
+ try:
195
+ w["process"].join()
196
+ except Exception as e:
197
+ logger.warning(f"Error joining stopping worker {pid}: {e}")
198
+ _close_worker_queues(w)
199
+ del _stopping[pid]
152
200
  logger.info("All workers stopped.")
153
201
 
154
202
 
@@ -177,4 +225,4 @@ def resume():
177
225
  logger.info("Resuming worker processes...")
178
226
 
179
227
 
180
- settings.setting_changed.connect(update_setting)
228
+ settings.setting_changed.connect(update_setting)