pyqt-code-editor 0.0.64__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.64 → pyqt_code_editor-0.0.65}/PKG-INFO +1 -1
  2. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/__init__.py +1 -1
  3. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/manager.py +56 -19
  4. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/COPYING +0 -0
  5. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyproject.toml +0 -0
  6. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/_settings.py +0 -0
  7. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/app.py +0 -0
  8. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/__init__.py +0 -0
  9. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/code_editors.py +0 -0
  10. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  11. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
  12. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
  13. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
  14. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/python.py +0 -0
  15. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/code_editors/languages/text.py +0 -0
  16. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/__init__.py +0 -0
  17. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/editor_panel.py +0 -0
  18. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/find_in_files.py +0 -0
  19. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/__init__.py +0 -0
  20. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/jupyter_console.py +0 -0
  21. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/jupyter_console_tab.py +0 -0
  22. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/jupyter_console/kernel_spec_manager.py +0 -0
  23. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/project_explorer.py +0 -0
  24. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/settings_panel.py +0 -0
  25. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/__init__.py +0 -0
  26. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/confirm_run_code_dialog.py +0 -0
  27. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/editor_workspace.py +0 -0
  28. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/sigmund.py +0 -0
  29. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/sigmund_analyst_chat_widget.py +0 -0
  30. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/sigmund/sigmund_analyst_widget.py +0 -0
  31. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/components/workspace_explorer.py +0 -0
  32. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/environment_manager.py +0 -0
  33. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/__init__.py +0 -0
  34. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/auto_indent.py +0 -0
  35. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/auto_pair.py +0 -0
  36. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/base.py +0 -0
  37. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/check.py +0 -0
  38. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/comment.py +0 -0
  39. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/complete.py +0 -0
  40. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/execute.py +0 -0
  41. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/file_link.py +0 -0
  42. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/highlight_matching_brackets.py +0 -0
  43. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/line_number.py +0 -0
  44. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/merge_undo_actions.py +0 -0
  45. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
  46. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/search_replace.py +0 -0
  47. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/shortcuts.py +0 -0
  48. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
  49. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/symbols.py +0 -0
  50. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/theme.py +0 -0
  51. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  52. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/mixins/zoom.py +0 -0
  53. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/sigmund-analyst.png +0 -0
  54. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/signal_router.py +0 -0
  55. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  56. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  57. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
  58. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
  59. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
  60. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/themes.py +0 -0
  61. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/__init__.py +0 -0
  62. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/__init__.py +0 -0
  63. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
  64. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
  65. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
  66. {pyqt_code_editor-0.0.64 → 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.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/watchdog.py +0 -0
  68. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/__init__.py +0 -0
  69. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
  70. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/completion_popup.py +0 -0
  71. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/dock.py +0 -0
  72. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
  73. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
  74. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
  75. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
  76. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
  77. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
  78. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/__init__.py +0 -0
  79. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/languages/__init__.py +0 -0
  80. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/languages/generic.py +0 -0
  81. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/languages/python.py +0 -0
  82. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/process.py +0 -0
  83. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/__init__.py +0 -0
  84. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/codestral.py +0 -0
  85. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/jedi.py +0 -0
  86. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/ruff.py +0 -0
  87. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
  88. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/symbol.py +0 -0
  89. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/providers/tree_sitter.py +0 -0
  90. {pyqt_code_editor-0.0.64 → pyqt_code_editor-0.0.65}/pyqt_code_editor/worker/settings.py +0 -0
  91. {pyqt_code_editor-0.0.64 → 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.64
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.64'
5
+ __version__ = '0.0.65'
@@ -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)