pyqt-code-editor 0.0.52__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.
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/PKG-INFO +1 -1
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/__init__.py +1 -1
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/app.py +4 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/auto_pair.py +0 -1
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/base.py +4 -2
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/comment.py +4 -0
- pyqt_code_editor-0.0.54/pyqt_code_editor/mixins/merge_undo_actions.py +54 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/search_replace.py +83 -28
- pyqt_code_editor-0.0.52/pyqt_code_editor/mixins/merge_undo_actions.py +0 -32
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/COPYING +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyproject.toml +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/_settings.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/code_editors.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/python.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/text.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/editor_panel.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/find_in_files.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/jupyter_console.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/project_explorer.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/settings_panel.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/sigmund.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/workspace_explorer.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/environment_manager.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/auto_indent.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/check.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/complete.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/execute.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/file_link.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/highlight_matching_brackets.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/line_number.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/shortcuts.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/symbols.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/theme.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/zoom.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/sigmund-analyst.png +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/signal_router.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/themes.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/watchdog.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/completion_popup.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/dock.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/python.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/manager.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/process.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/__init__.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/codestral.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/jedi.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/ruff.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/symbol.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/tree_sitter.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/settings.py +0 -0
- {pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/readme.md +0 -0
|
@@ -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):
|
|
@@ -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
|
-
|
|
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)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from qtpy.QtCore import Qt
|
|
2
|
+
from qtpy.QtGui import QTextCursor
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MergeUndoActions:
|
|
6
|
+
"""Ensures that all changes made as part of a key press result in a
|
|
7
|
+
single edit block for undo purposes. This class should be the first
|
|
8
|
+
mixin of the list.
|
|
9
|
+
"""
|
|
10
|
+
def keyPressEvent(self, event):
|
|
11
|
+
# Navigation keys that should NOT be wrapped in edit blocks. This
|
|
12
|
+
# avoids cursor glitches when navigating vertically/horizontally.
|
|
13
|
+
navigation_keys = {
|
|
14
|
+
Qt.Key.Key_Up, Qt.Key.Key_Down,
|
|
15
|
+
Qt.Key.Key_Left, Qt.Key.Key_Right,
|
|
16
|
+
Qt.Key.Key_Home, Qt.Key.Key_End,
|
|
17
|
+
Qt.Key.Key_PageUp, Qt.Key.Key_PageDown,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
key = event.key()
|
|
21
|
+
modifiers = event.modifiers()
|
|
22
|
+
|
|
23
|
+
is_navigation = (
|
|
24
|
+
key in navigation_keys
|
|
25
|
+
and not (modifiers & Qt.KeyboardModifier.ShiftModifier)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Treat platform "command" modifier as well (Control on Windows/Linux, Meta on macOS)
|
|
29
|
+
ctrl_or_meta = Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.MetaModifier
|
|
30
|
+
has_ctrl_or_meta = bool(modifiers & ctrl_or_meta)
|
|
31
|
+
has_shift = bool(modifiers & Qt.KeyboardModifier.ShiftModifier)
|
|
32
|
+
|
|
33
|
+
# Common Undo/Redo shortcuts:
|
|
34
|
+
# - Undo: Ctrl+Z / Cmd+Z
|
|
35
|
+
# - Redo: Ctrl+Shift+Z / Cmd+Shift+Z / Ctrl+Y / Cmd+Y
|
|
36
|
+
is_undo = has_ctrl_or_meta and key == Qt.Key.Key_Z and not has_shift
|
|
37
|
+
is_redo = has_ctrl_or_meta and (
|
|
38
|
+
(key == Qt.Key.Key_Z and has_shift) or
|
|
39
|
+
key == Qt.Key.Key_Y
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if is_navigation or is_undo or is_redo:
|
|
43
|
+
# Do not wrap these in an edit block
|
|
44
|
+
super().keyPressEvent(event)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
# For editing keys, wrap the operation in a single edit block.
|
|
48
|
+
# Use the widget's actual text cursor, not a fresh document cursor.
|
|
49
|
+
cursor = self.textCursor()
|
|
50
|
+
cursor.beginEditBlock()
|
|
51
|
+
try:
|
|
52
|
+
super().keyPressEvent(event)
|
|
53
|
+
finally:
|
|
54
|
+
cursor.endEditBlock()
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/search_replace.py
RENAMED
|
@@ -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
|
-
|
|
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
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
from qtpy.QtCore import Qt
|
|
2
|
-
from qtpy.QtGui import QTextCursor
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class MergeUndoActions:
|
|
6
|
-
"""Ensures that all changes made as part of a key press result in a
|
|
7
|
-
single edit block for undo purposes. This class should be the first
|
|
8
|
-
mixin of the list.
|
|
9
|
-
"""
|
|
10
|
-
def keyPressEvent(self, event):
|
|
11
|
-
# Navigation keys that should NOT be wrapped in edit blocks. This
|
|
12
|
-
# causes a specific issue with the cursor jumping to the start of a
|
|
13
|
-
# line when navigating vertically.
|
|
14
|
-
navigation_keys = {
|
|
15
|
-
Qt.Key.Key_Up, Qt.Key.Key_Down,
|
|
16
|
-
Qt.Key.Key_Left, Qt.Key.Key_Right,
|
|
17
|
-
Qt.Key.Key_Home, Qt.Key.Key_End,
|
|
18
|
-
Qt.Key.Key_PageUp, Qt.Key.Key_PageDown
|
|
19
|
-
}
|
|
20
|
-
is_navigation = (event.key() in navigation_keys and
|
|
21
|
-
not (event.modifiers() & Qt.KeyboardModifier.ShiftModifier))
|
|
22
|
-
if is_navigation:
|
|
23
|
-
super().keyPressEvent(event)
|
|
24
|
-
return
|
|
25
|
-
# Wrap all other keys (editing operations) in a single edit block to
|
|
26
|
-
# unify changes into a single undo action
|
|
27
|
-
cursor = QTextCursor(self.document())
|
|
28
|
-
cursor.beginEditBlock()
|
|
29
|
-
try:
|
|
30
|
-
super().keyPressEvent(event)
|
|
31
|
-
finally:
|
|
32
|
-
cursor.endEditBlock()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/__init__.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/code_editors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/code_editors/languages/text.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/editor_panel.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/find_in_files.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/jupyter_console.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/project_explorer.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/components/settings_panel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/python_auto_indent.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/mixins/widgets/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/syntax_highlighters/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/utils/languages/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/calltip_widget.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/completion_popup.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_open_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/quick_symbol_dialog.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/search_replace_frame.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/tab_splitter.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/widgets/tabbed_editor.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/__init__.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/generic.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/languages/python.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/__init__.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/codestral.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/jedi.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/ruff.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/sigmund.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/symbol.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.52 → pyqt_code_editor-0.0.54}/pyqt_code_editor/worker/providers/tree_sitter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|