pyqt-code-editor 0.0.53__tar.gz → 0.0.54__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 (83) hide show
  1. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/PKG-INFO +1 -1
  2. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/__init__.py +1 -1
  3. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/app.py +4 -0
  4. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/auto_pair.py +0 -1
  5. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/base.py +4 -2
  6. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/comment.py +4 -0
  7. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/search_replace.py +83 -28
  8. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/COPYING +0 -0
  9. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyproject.toml +0 -0
  10. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/_settings.py +0 -0
  11. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/__init__.py +0 -0
  12. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/code_editors.py +0 -0
  13. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
  14. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
  15. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
  16. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
  17. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/python.py +0 -0
  18. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/text.py +0 -0
  19. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/__init__.py +0 -0
  20. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/editor_panel.py +0 -0
  21. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/find_in_files.py +0 -0
  22. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/jupyter_console.py +0 -0
  23. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/project_explorer.py +0 -0
  24. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/settings_panel.py +0 -0
  25. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/sigmund.py +0 -0
  26. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/workspace_explorer.py +0 -0
  27. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/environment_manager.py +0 -0
  28. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/__init__.py +0 -0
  29. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/auto_indent.py +0 -0
  30. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/check.py +0 -0
  31. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/complete.py +0 -0
  32. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/execute.py +0 -0
  33. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/file_link.py +0 -0
  34. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/highlight_matching_brackets.py +0 -0
  35. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/line_number.py +0 -0
  36. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/merge_undo_actions.py +0 -0
  37. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
  38. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/shortcuts.py +0 -0
  39. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
  40. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/symbols.py +0 -0
  41. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/theme.py +0 -0
  42. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
  43. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/zoom.py +0 -0
  44. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/sigmund-analyst.png +0 -0
  45. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/signal_router.py +0 -0
  46. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
  47. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
  48. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
  49. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
  50. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
  51. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/themes.py +0 -0
  52. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/__init__.py +0 -0
  53. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/__init__.py +0 -0
  54. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
  55. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
  56. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
  57. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
  58. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/watchdog.py +0 -0
  59. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/__init__.py +0 -0
  60. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
  61. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/completion_popup.py +0 -0
  62. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/dock.py +0 -0
  63. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
  64. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
  65. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
  66. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
  67. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
  68. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
  69. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/__init__.py +0 -0
  70. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/__init__.py +0 -0
  71. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/generic.py +0 -0
  72. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/python.py +0 -0
  73. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/manager.py +0 -0
  74. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/process.py +0 -0
  75. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/__init__.py +0 -0
  76. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/codestral.py +0 -0
  77. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/jedi.py +0 -0
  78. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/ruff.py +0 -0
  79. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
  80. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/symbol.py +0 -0
  81. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/tree_sitter.py +0 -0
  82. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/settings.py +0 -0
  83. {pyqt_code_editor-0.0.53 → pyqt_code_editor-0.0.54}/readme.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyqt_code_editor
3
- Version: 0.0.53
3
+ Version: 0.0.54
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.53'
5
+ __version__ = '0.0.54'
@@ -349,6 +349,10 @@ class SigmundAnalyst(QMainWindow):
349
349
  self._jupyter_console.execute_code(code)
350
350
 
351
351
  def _execute_file(self, path):
352
+ working_directory = os.path.dirname(path)
353
+ if os.path.isdir(working_directory):
354
+ self._jupyter_console.change_directory(working_directory)
355
+ self._toggle_dock_widget(self._jupyter_console, show=True)
352
356
  self._jupyter_console.execute_file(path)
353
357
 
354
358
  def _find_in_files(self):
@@ -88,7 +88,6 @@ class AutoPair:
88
88
  if typed_char and len(typed_char) == 1:
89
89
  for pair in self.PAIRS:
90
90
  if typed_char == pair["close_seq"]:
91
- print(typed_char)
92
91
  new_cursor = self.textCursor()
93
92
  new_pos = new_cursor.position()
94
93
  new_doc_text = self.toPlainText()
@@ -106,7 +106,9 @@ class Base:
106
106
  if the worker died or the result queue is empty, keep polling.
107
107
  """
108
108
  to_remove = []
109
- for pid, queue in self._active_requests.items():
109
+ # Create a list of active requests to avoid the dictionary being
110
+ # changed during iteration
111
+ for pid, queue in list(self._active_requests.items()):
110
112
  # 1) Check if the worker is still alive
111
113
  if not manager.check_worker_alive(pid):
112
114
  logger.warning(f"Worker process {pid} no longer alive. Removing from active requests.")
@@ -143,7 +145,7 @@ class Base:
143
145
  # Cleanup: remove completed or dead requests. Also stop unused workers.
144
146
  # This function periodically prunes worker processes if they are unused.
145
147
  for pid in to_remove:
146
- self._active_requests.pop(pid, None)
148
+ self._active_requests.pop(pid, None)
147
149
  manager.stop_unused_workers()
148
150
 
149
151
  def set_modified(self, modified):
@@ -44,6 +44,7 @@ class Comment:
44
44
  def _toggle_comment(self):
45
45
  """Toggle comment on the current selection or current line."""
46
46
  cursor = self.textCursor()
47
+ cursor.beginEditBlock()
47
48
  if not cursor.hasSelection():
48
49
  # If no selection, just operate on the current line
49
50
  cursor.select(cursor.LineUnderCursor)
@@ -70,6 +71,7 @@ class Comment:
70
71
  self._uncomment_blocks(startBlock, endBlock)
71
72
  else:
72
73
  self._comment_blocks(startBlock, endBlock)
74
+ cursor.endEditBlock()
73
75
 
74
76
  def _comment_blocks(self, start_block: int, end_block: int):
75
77
  """Comment all lines from start_block to end_block."""
@@ -84,6 +86,7 @@ class Comment:
84
86
  insert_position = block.position() + leading_spaces
85
87
  cursor.setPosition(insert_position)
86
88
  cursor.insertText(self.code_editor_comment_string)
89
+ self.setTextCursor(cursor)
87
90
 
88
91
  def _uncomment_blocks(self, start_block: int, end_block: int):
89
92
  """Uncomment all lines from start_block to end_block."""
@@ -102,3 +105,4 @@ class Comment:
102
105
  cursor.setPosition(remove_position + strip_len, cursor.KeepAnchor)
103
106
  if cursor.selectedText() == self.code_editor_comment_string.rstrip('\r\n'):
104
107
  cursor.removeSelectedText()
108
+ self.setTextCursor(cursor)
@@ -8,10 +8,9 @@ from qtpy.QtCore import Qt
8
8
  from .. import settings
9
9
  from ..widgets.search_replace_frame import SearchReplaceFrame
10
10
 
11
-
12
11
  class SearchReplaceHighlighter(QSyntaxHighlighter):
13
12
  """
14
- Simple syntax highlighter that highlights all matches of a given pattern.
13
+ Syntax highlighter that highlights all matches of a given pattern.
15
14
  Updated whenever the pattern or text changes.
16
15
  """
17
16
  def __init__(self, parent=None):
@@ -20,46 +19,87 @@ class SearchReplaceHighlighter(QSyntaxHighlighter):
20
19
  self._use_regex = False
21
20
  self._case_sensitive = False
22
21
  self._whole_word = False
23
-
22
+ self._matches = [] # Stores all matches in the document as (block_number, start, length)
23
+
24
24
  # Highlight style
25
25
  self.highlight_format = QTextCharFormat()
26
26
  self.highlight_format.setBackground(QColor(settings.search_replace_background))
27
27
  self.highlight_format.setForeground(QColor(settings.search_replace_foreground))
28
-
28
+
29
29
  def setSearchOptions(self, pattern, use_regex, case_sensitive, whole_word):
30
30
  self._pattern = pattern
31
31
  self._use_regex = use_regex
32
32
  self._case_sensitive = case_sensitive
33
33
  self._whole_word = whole_word
34
34
  self.rehighlight() # Trigger a re-scan
35
-
36
- def highlightBlock(self, text):
37
- if not self._pattern:
35
+
36
+ def rehighlight(self):
37
+ """Find all matches in the entire document and store them"""
38
+ if not self._pattern or not self.document():
39
+ self._matches = []
40
+ super().rehighlight()
38
41
  return
39
-
42
+
40
43
  # Build the pattern
41
44
  pattern = self._pattern
42
45
  flags = 0 if self._case_sensitive else re.IGNORECASE
43
-
46
+
44
47
  if self._use_regex:
45
- # If whole_word is asked for, add \b on both sides (can be tricky with punctuation).
46
48
  if self._whole_word:
47
49
  pattern = rf"\b{pattern}\b"
48
50
  try:
49
51
  regex = re.compile(pattern, flags)
50
52
  except re.error:
51
- return # invalid regex, just skip
53
+ self._matches = []
54
+ super().rehighlight()
55
+ return
52
56
  else:
53
- # Escape user text if not in regex mode
54
57
  pattern = re.escape(pattern)
55
58
  if self._whole_word:
56
59
  pattern = rf"\b{pattern}\b"
57
60
  regex = re.compile(pattern, flags)
58
-
59
- for match in regex.finditer(text):
61
+
62
+ # Find all matches in the entire document
63
+ self._matches = []
64
+ full_text = self.document().toPlainText()
65
+ for match in regex.finditer(full_text):
60
66
  start = match.start()
61
- length = match.end() - match.start()
62
- self.setFormat(start, length, self.highlight_format)
67
+ end = match.end()
68
+
69
+ # Convert character positions to block/position
70
+ block = self.document().findBlock(start)
71
+ block_number = block.blockNumber()
72
+ block_start = block.position()
73
+ block_end = block_start + block.length() - 1
74
+
75
+ # Handle matches that span multiple blocks
76
+ while start <= block_end and end > block_start:
77
+ match_start_in_block = max(start - block_start, 0)
78
+ match_end_in_block = min(end - block_start, block.length() - 1)
79
+ match_length = match_end_in_block - match_start_in_block
80
+
81
+ if match_length > 0:
82
+ self._matches.append((block_number, match_start_in_block, match_length))
83
+
84
+ # Move to next block if match spans multiple blocks
85
+ if end > block_end:
86
+ block = block.next()
87
+ if not block.isValid():
88
+ break
89
+ block_number = block.blockNumber()
90
+ block_start = block.position()
91
+ block_end = block_start + block.length() - 1
92
+ else:
93
+ break
94
+
95
+ super().rehighlight()
96
+
97
+ def highlightBlock(self, text):
98
+ """Highlight matches that belong to this block"""
99
+ block_number = self.currentBlock().blockNumber()
100
+ for match_block, start, length in self._matches:
101
+ if match_block == block_number:
102
+ self.setFormat(start, length, self.highlight_format)
63
103
 
64
104
 
65
105
  class SearchReplace:
@@ -325,13 +365,14 @@ class SearchReplace:
325
365
  needle = self._searchFrame.findEdit.text()
326
366
  if not needle:
327
367
  return
328
-
368
+
329
369
  replacement = self._searchFrame.replaceEdit.text()
330
-
331
- # Save cursor
370
+
371
+ # Save cursor and get current text
332
372
  saved_cursor = self.textCursor()
373
+ cursor_pos = saved_cursor.position()
333
374
  text = self.toPlainText()
334
-
375
+
335
376
  # Build regex pattern
336
377
  flags = 0 if self._searchFrame.caseBox.isChecked() else re.IGNORECASE
337
378
  patt = needle
@@ -339,20 +380,34 @@ class SearchReplace:
339
380
  patt = re.escape(patt)
340
381
  if self._searchFrame.wholeWordBox.isChecked():
341
382
  patt = rf"\b{patt}\b"
342
-
383
+
343
384
  try:
344
385
  compiled = re.compile(patt, flags)
345
386
  except re.error:
346
387
  return
347
-
388
+
389
+ # Get text before cursor
390
+ text_before = text[:cursor_pos]
391
+
392
+ # Apply replacement to full text
348
393
  new_text, num_replacements = compiled.subn(replacement, text)
349
- self.setPlainText(new_text)
350
-
351
- # After replace all, set the needle to the replacement text
394
+
395
+ # Apply replacement to just the before portion to calculate position change
396
+ new_text_before, _ = compiled.subn(replacement, text_before)
397
+ length_change = len(new_text_before) - len(text_before)
398
+ new_cursor_pos = cursor_pos + length_change
399
+
400
+ # Use a single undoable operation
401
+ cursor = self.textCursor()
402
+ cursor.beginEditBlock()
403
+ cursor.select(QTextCursor.Document)
404
+ cursor.insertText(new_text)
405
+ cursor.setPosition(new_cursor_pos)
406
+ cursor.endEditBlock()
407
+ self.setTextCursor(cursor)
408
+
409
+ # Update UI
352
410
  self._searchFrame.findEdit.setText(replacement)
353
-
354
- # Restore cursor
355
- self.setTextCursor(saved_cursor)
356
411
  self.updateHighlighter()
357
412
  self.set_modified(True)
358
413