pyqt-code-editor 0.0.32__tar.gz → 0.0.34__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.32 → pyqt_code_editor-0.0.34}/PKG-INFO +1 -1
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/__init__.py +1 -1
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/editor_panel.py +2 -1
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/project_explorer.py +180 -57
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/sigmund.py +10 -2
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/ruff.py +9 -3
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/COPYING +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyproject.toml +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/_settings.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/app.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/code_editors.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/languages/markdown.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/languages/python.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/languages/text.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/find_in_files.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/jupyter_console.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/settings_panel.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/workspace_explorer.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/environment_manager.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/auto_indent.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/auto_pair.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/base.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/check.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/comment.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/complete.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/execute.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/file_link.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/line_number.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/python_auto_indent.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/search_replace.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/shortcuts.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/symbols.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/theme.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/zoom.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/sigmund-analyst.png +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/signal_router.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/themes.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/utils/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/utils/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/watchdog.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/completion_popup.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/dock.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/tabbed_editor.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/languages/python.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/manager.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/process.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/__init__.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/codestral.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/jedi.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/symbol.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/settings.py +0 -0
- {pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/readme.md +0 -0
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/editor_panel.py
RENAMED
|
@@ -98,7 +98,8 @@ class EditorPanel(QWidget):
|
|
|
98
98
|
self._active_tab_widget = self._active_editor.parent().parent()
|
|
99
99
|
|
|
100
100
|
def open_file(self, path=None, line_number=None):
|
|
101
|
-
path
|
|
101
|
+
if path is not None:
|
|
102
|
+
path = os.path.normpath(path)
|
|
102
103
|
if path is not None:
|
|
103
104
|
# Don't allow the same file to be opened multiple times
|
|
104
105
|
for editor in self.central_splitter.editors():
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/project_explorer.py
RENAMED
|
@@ -11,12 +11,13 @@ from qtpy.QtWidgets import (
|
|
|
11
11
|
QInputDialog,
|
|
12
12
|
QWidget,
|
|
13
13
|
QVBoxLayout,
|
|
14
|
-
QCheckBox
|
|
14
|
+
QCheckBox,
|
|
15
|
+
QShortcut
|
|
15
16
|
)
|
|
16
17
|
from qtpy.QtCore import Qt, QDir, QModelIndex, QSortFilterProxyModel, QUrl, \
|
|
17
18
|
Signal
|
|
18
19
|
from qtpy.QtWidgets import QFileSystemModel
|
|
19
|
-
from qtpy.QtGui import QDesktopServices
|
|
20
|
+
from qtpy.QtGui import QDesktopServices, QKeySequence
|
|
20
21
|
from pathspec import PathSpec
|
|
21
22
|
from pathspec.patterns.gitwildmatch import GitWildMatchPattern
|
|
22
23
|
from .. import settings, themes
|
|
@@ -24,8 +25,7 @@ from .. import settings, themes
|
|
|
24
25
|
logger = logging.getLogger(__name__)
|
|
25
26
|
|
|
26
27
|
class LazyQFileSystemModel(QFileSystemModel):
|
|
27
|
-
"""
|
|
28
|
-
A QFileSystemModel that only fetches children for 'expanded' paths.
|
|
28
|
+
"""A QFileSystemModel that only fetches children for 'expanded' paths.
|
|
29
29
|
This prevents eagerly creating inotify watchers for large, collapsed directories.
|
|
30
30
|
"""
|
|
31
31
|
def __init__(self, parent=None):
|
|
@@ -71,8 +71,7 @@ class LazyQFileSystemModel(QFileSystemModel):
|
|
|
71
71
|
return super().fetchMore(index)
|
|
72
72
|
|
|
73
73
|
class GitignoreFilterProxyModel(QSortFilterProxyModel):
|
|
74
|
-
"""
|
|
75
|
-
A QSortFilterProxyModel that hides paths ignored by .gitignore (when enabled).
|
|
74
|
+
"""A QSortFilterProxyModel that hides paths ignored by .gitignore (when enabled).
|
|
76
75
|
Parses .gitignore with pathspec. Also forwards hasChildren/fetchMore to the source
|
|
77
76
|
model so that folders may be expanded.
|
|
78
77
|
"""
|
|
@@ -83,8 +82,7 @@ class GitignoreFilterProxyModel(QSortFilterProxyModel):
|
|
|
83
82
|
self.pathspec = None
|
|
84
83
|
|
|
85
84
|
def set_root_folder(self, folder):
|
|
86
|
-
"""
|
|
87
|
-
Load .gitignore (if present) from 'folder' and parse it into a PathSpec.
|
|
85
|
+
"""Load .gitignore (if present) from 'folder' and parse it into a PathSpec.
|
|
88
86
|
Keep the folder path for computing relative paths.
|
|
89
87
|
"""
|
|
90
88
|
self.root_folder = folder
|
|
@@ -115,14 +113,14 @@ class GitignoreFilterProxyModel(QSortFilterProxyModel):
|
|
|
115
113
|
abs_path = source_model.filePath(index)
|
|
116
114
|
# Make sure our designated root folder is never hidden
|
|
117
115
|
if abs_path == self.root_folder:
|
|
118
|
-
return True
|
|
116
|
+
return True
|
|
119
117
|
if not self.root_folder:
|
|
120
118
|
return True
|
|
121
|
-
# Always keep directories. We need to be able to look inside them
|
|
119
|
+
# Always keep directories. We need to be able to look inside them
|
|
122
120
|
# because their children might be re-included by negated patterns in
|
|
123
121
|
# .gitignore.
|
|
124
122
|
if source_model.isDir(index):
|
|
125
|
-
return True
|
|
123
|
+
return True
|
|
126
124
|
# Ignore explicitly ignored folders
|
|
127
125
|
path_parts = Path(abs_path).parts
|
|
128
126
|
if any(ignored in path_parts for ignored in settings.ignored_folders):
|
|
@@ -130,7 +128,7 @@ class GitignoreFilterProxyModel(QSortFilterProxyModel):
|
|
|
130
128
|
# Compute relative path from the repository root
|
|
131
129
|
rel_path = os.path.relpath(abs_path, self.root_folder)
|
|
132
130
|
# If matched by pathspec => it is ignored => filter out
|
|
133
|
-
return not self.pathspec.match_file(rel_path)
|
|
131
|
+
return not self.pathspec.match_file(rel_path)
|
|
134
132
|
|
|
135
133
|
# ----------------------
|
|
136
134
|
# Overridden methods to ensure directories can still expand
|
|
@@ -152,7 +150,7 @@ class GitignoreFilterProxyModel(QSortFilterProxyModel):
|
|
|
152
150
|
self.sourceModel().fetchMore(source_index)
|
|
153
151
|
|
|
154
152
|
class ProjectExplorer(QDockWidget):
|
|
155
|
-
|
|
153
|
+
|
|
156
154
|
closed = Signal(object)
|
|
157
155
|
|
|
158
156
|
def __init__(self, editor_panel, root_path=None, parent=None):
|
|
@@ -188,7 +186,7 @@ class ProjectExplorer(QDockWidget):
|
|
|
188
186
|
|
|
189
187
|
# Optional: Hide columns other than the file name
|
|
190
188
|
self._set_single_column_view(True)
|
|
191
|
-
|
|
189
|
+
|
|
192
190
|
# Only if .gitignore exists in the root, create the checkbox
|
|
193
191
|
gitignore_path = os.path.join(self._display_root, '.gitignore')
|
|
194
192
|
if os.path.isfile(gitignore_path):
|
|
@@ -207,23 +205,92 @@ class ProjectExplorer(QDockWidget):
|
|
|
207
205
|
self._tree_view.customContextMenuRequested.connect(self._show_context_menu)
|
|
208
206
|
self._tree_view.doubleClicked.connect(self._on_double_click)
|
|
209
207
|
|
|
210
|
-
# Set our container widget as the dock widget
|
|
208
|
+
# Set our container widget as the dock widget's main widget
|
|
211
209
|
self.setWidget(container_widget)
|
|
212
|
-
|
|
210
|
+
|
|
211
|
+
# Set up global keyboard shortcuts
|
|
212
|
+
self._setup_shortcuts()
|
|
213
|
+
|
|
214
|
+
def _setup_shortcuts(self):
|
|
215
|
+
"""Set up global keyboard shortcuts for common actions."""
|
|
216
|
+
QShortcut(QKeySequence("F2"), self, activated=lambda: self._handle_rename_shortcut())
|
|
217
|
+
QShortcut(QKeySequence.Delete, self, activated=lambda: self._handle_delete_shortcut())
|
|
218
|
+
QShortcut(QKeySequence.Cut, self, activated=lambda: self._handle_cut_shortcut())
|
|
219
|
+
QShortcut(QKeySequence.Copy, self, activated=lambda: self._handle_copy_shortcut())
|
|
220
|
+
QShortcut(QKeySequence.Paste, self, activated=lambda: self._handle_paste_shortcut())
|
|
221
|
+
|
|
222
|
+
def _handle_rename_shortcut(self):
|
|
223
|
+
"""Handle F2 shortcut for rename."""
|
|
224
|
+
proxy_index = self._tree_view.selectionModel().currentIndex()
|
|
225
|
+
if proxy_index.isValid():
|
|
226
|
+
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
227
|
+
if source_index.isValid():
|
|
228
|
+
path = self._model.filePath(source_index)
|
|
229
|
+
self._rename_file_or_folder(path)
|
|
230
|
+
|
|
231
|
+
def _handle_delete_shortcut(self):
|
|
232
|
+
"""Handle Delete shortcut for delete."""
|
|
233
|
+
proxy_index = self._tree_view.selectionModel().currentIndex()
|
|
234
|
+
if proxy_index.isValid():
|
|
235
|
+
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
236
|
+
if source_index.isValid():
|
|
237
|
+
path = self._model.filePath(source_index)
|
|
238
|
+
self._delete_file_or_folder(path)
|
|
239
|
+
|
|
240
|
+
def _handle_cut_shortcut(self):
|
|
241
|
+
"""Handle Ctrl+X shortcut for cut."""
|
|
242
|
+
proxy_index = self._tree_view.selectionModel().currentIndex()
|
|
243
|
+
if proxy_index.isValid():
|
|
244
|
+
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
245
|
+
if source_index.isValid():
|
|
246
|
+
path = self._model.filePath(source_index)
|
|
247
|
+
self._clipboard_operation = 'cut'
|
|
248
|
+
self._clipboard_source_path = path
|
|
249
|
+
|
|
250
|
+
def _handle_copy_shortcut(self):
|
|
251
|
+
"""Handle Ctrl+C shortcut for copy."""
|
|
252
|
+
proxy_index = self._tree_view.selectionModel().currentIndex()
|
|
253
|
+
if proxy_index.isValid():
|
|
254
|
+
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
255
|
+
if source_index.isValid():
|
|
256
|
+
path = self._model.filePath(source_index)
|
|
257
|
+
self._clipboard_operation = 'copy'
|
|
258
|
+
self._clipboard_source_path = path
|
|
259
|
+
|
|
260
|
+
def _handle_paste_shortcut(self):
|
|
261
|
+
"""Handle Ctrl+V shortcut for paste."""
|
|
262
|
+
if not self._clipboard_source_path:
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
if not self._tree_view.selectionModel().hasSelection():
|
|
266
|
+
# Paste into root if nothing is selected
|
|
267
|
+
root_path = self._model.rootPath()
|
|
268
|
+
if os.path.isdir(root_path):
|
|
269
|
+
self._paste_file_or_folder(root_path)
|
|
270
|
+
else:
|
|
271
|
+
proxy_index = self._tree_view.selectionModel().currentIndex()
|
|
272
|
+
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
273
|
+
if source_index.isValid():
|
|
274
|
+
path = self._model.filePath(source_index)
|
|
275
|
+
if os.path.isdir(path):
|
|
276
|
+
self._paste_file_or_folder(path)
|
|
277
|
+
else:
|
|
278
|
+
# If a file is selected, use its directory
|
|
279
|
+
self._paste_file_or_folder(os.path.dirname(path))
|
|
280
|
+
|
|
213
281
|
def list_files(self) -> list[str]:
|
|
214
|
-
"""
|
|
215
|
-
Returns a list of all non-ignored files under the display root,
|
|
282
|
+
"""Returns a list of all non-ignored files under the display root,
|
|
216
283
|
applying the same .gitignore-based logic.
|
|
217
284
|
If there are more than max_files files, returns empty list.
|
|
218
285
|
"""
|
|
219
286
|
if not self._display_root:
|
|
220
287
|
return []
|
|
221
|
-
|
|
288
|
+
|
|
222
289
|
results = []
|
|
223
290
|
gitignore_enabled = self._filter_proxy.gitignore_enabled
|
|
224
291
|
pathspec = self._filter_proxy.pathspec
|
|
225
292
|
# Recursively walk the filesystem from _display_root
|
|
226
|
-
for dirpath, dirnames, filenames in os.walk(self._display_root):
|
|
293
|
+
for dirpath, dirnames, filenames in os.walk(self._display_root):
|
|
227
294
|
path_parts = Path(dirpath).parts
|
|
228
295
|
if any(ignored in path_parts for ignored in settings.ignored_folders):
|
|
229
296
|
continue
|
|
@@ -233,7 +300,7 @@ class ProjectExplorer(QDockWidget):
|
|
|
233
300
|
# Avoid filtering _display_root, though it shouldn't typically be a file
|
|
234
301
|
if abs_file == self._display_root:
|
|
235
302
|
continue
|
|
236
|
-
|
|
303
|
+
|
|
237
304
|
if gitignore_enabled and pathspec:
|
|
238
305
|
rel_file = os.path.relpath(abs_file, self._display_root)
|
|
239
306
|
# If pathspec matches => "ignored"
|
|
@@ -243,32 +310,31 @@ class ProjectExplorer(QDockWidget):
|
|
|
243
310
|
if len(results) > settings.max_files:
|
|
244
311
|
logger.warning("Too many files in project")
|
|
245
312
|
return []
|
|
246
|
-
|
|
247
|
-
return results
|
|
248
|
-
|
|
313
|
+
|
|
314
|
+
return results
|
|
315
|
+
|
|
249
316
|
@classmethod
|
|
250
317
|
def open_folder(cls, editor_panel, parent=None) -> object | None:
|
|
251
|
-
"""
|
|
252
|
-
Shows a folder picker dialog to open a folder.
|
|
318
|
+
"""Shows a folder picker dialog to open a folder.
|
|
253
319
|
If a folder is selected, a ProjectExplorer instance is created and returned.
|
|
254
320
|
Otherwise None is returned.
|
|
255
321
|
"""
|
|
256
322
|
options = QFileDialog.Options(QFileDialog.ShowDirsOnly)
|
|
257
323
|
if os.environ.get("DONT_USE_NATIVE_FILE_DIALOG", False):
|
|
258
324
|
options |= QFileDialog.Option.DontUseNativeDialog
|
|
259
|
-
logger.info('not using native file dialog')
|
|
325
|
+
logger.info('not using native file dialog')
|
|
260
326
|
selected_dir = QFileDialog.getExistingDirectory(parent,
|
|
261
327
|
"Open Project Folder", "", options=options)
|
|
262
328
|
if not selected_dir:
|
|
263
329
|
return None
|
|
264
330
|
settings.current_folder = selected_dir
|
|
265
|
-
# Add selected folder to project folders but make sure we don't
|
|
331
|
+
# Add selected folder to project folders but make sure we don't
|
|
266
332
|
# duplicate
|
|
267
333
|
settings.project_folders = '::'.join(
|
|
268
334
|
set(str(settings.project_folders).split('::')) | {selected_dir})
|
|
269
335
|
explorer = cls(editor_panel, root_path=selected_dir, parent=parent)
|
|
270
336
|
return explorer
|
|
271
|
-
|
|
337
|
+
|
|
272
338
|
def _toggle_gitignore(self, enabled):
|
|
273
339
|
"""Toggles the .gitignore filter on or off and refreshes the model.
|
|
274
340
|
"""
|
|
@@ -282,11 +348,10 @@ class ProjectExplorer(QDockWidget):
|
|
|
282
348
|
proxy_root_index = self._filter_proxy.mapFromSource(root_idx)
|
|
283
349
|
self._tree_view.setRootIndex(proxy_root_index)
|
|
284
350
|
# Expand the root so it behaves like an expanded folder
|
|
285
|
-
self._tree_view.expand(proxy_root_index)
|
|
351
|
+
self._tree_view.expand(proxy_root_index)
|
|
286
352
|
|
|
287
353
|
def _on_expanded_proxy(self, proxy_index):
|
|
288
|
-
"""
|
|
289
|
-
Convert the proxy index to the source model index and notify LazyQFileSystemModel.
|
|
354
|
+
"""Convert the proxy index to the source model index and notify LazyQFileSystemModel.
|
|
290
355
|
"""
|
|
291
356
|
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
292
357
|
if source_index.isValid():
|
|
@@ -294,8 +359,7 @@ class ProjectExplorer(QDockWidget):
|
|
|
294
359
|
self._model.notify_path_expanded(path)
|
|
295
360
|
|
|
296
361
|
def _on_collapsed_proxy(self, proxy_index):
|
|
297
|
-
"""
|
|
298
|
-
Convert the proxy index to the source model index and notify LazyQFileSystemModel.
|
|
362
|
+
"""Convert the proxy index to the source model index and notify LazyQFileSystemModel.
|
|
299
363
|
"""
|
|
300
364
|
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
301
365
|
if source_index.isValid():
|
|
@@ -329,15 +393,15 @@ class ProjectExplorer(QDockWidget):
|
|
|
329
393
|
"""Build and show a context menu on right-click."""
|
|
330
394
|
proxy_index = self._tree_view.indexAt(pos)
|
|
331
395
|
source_index = self._filter_proxy.mapToSource(proxy_index)
|
|
332
|
-
|
|
396
|
+
|
|
333
397
|
menu = QMenu(self)
|
|
334
|
-
|
|
398
|
+
|
|
335
399
|
if not source_index.isValid():
|
|
336
|
-
# Clicked on empty space
|
|
400
|
+
# Clicked on empty space
|
|
337
401
|
new_file_action = menu.addAction("New File…")
|
|
338
402
|
new_folder_action = menu.addAction("New Folder…")
|
|
339
403
|
chosen_action = menu.exec_(self._tree_view.mapToGlobal(pos))
|
|
340
|
-
|
|
404
|
+
|
|
341
405
|
if chosen_action == new_file_action:
|
|
342
406
|
root_path = self._model.rootPath()
|
|
343
407
|
if os.path.isdir(root_path):
|
|
@@ -346,24 +410,29 @@ class ProjectExplorer(QDockWidget):
|
|
|
346
410
|
root_path = self._model.rootPath()
|
|
347
411
|
if os.path.isdir(root_path):
|
|
348
412
|
self._create_new_folder(root_path)
|
|
349
|
-
|
|
413
|
+
|
|
350
414
|
else:
|
|
351
415
|
path = self._model.filePath(source_index)
|
|
352
416
|
is_file = os.path.isfile(path)
|
|
353
|
-
|
|
417
|
+
|
|
354
418
|
if is_file:
|
|
355
419
|
# Right-clicked on a file
|
|
356
420
|
open_action = menu.addAction("Open")
|
|
357
421
|
open_sys_action = menu.addAction("Open containing folder")
|
|
358
422
|
rename_action = menu.addAction("Rename…")
|
|
423
|
+
rename_action.setShortcut(QKeySequence("F2"))
|
|
359
424
|
delete_action = menu.addAction("Delete")
|
|
360
|
-
|
|
425
|
+
delete_action.setShortcut(QKeySequence.Delete)
|
|
426
|
+
|
|
361
427
|
menu.addSeparator()
|
|
362
428
|
cut_action = menu.addAction("Cut")
|
|
429
|
+
cut_action.setShortcut(QKeySequence.Cut)
|
|
363
430
|
copy_action = menu.addAction("Copy")
|
|
431
|
+
copy_action.setShortcut(QKeySequence.Copy)
|
|
364
432
|
paste_action = menu.addAction("Paste")
|
|
433
|
+
paste_action.setShortcut(QKeySequence.Paste)
|
|
365
434
|
paste_action.setEnabled(self._clipboard_source_path is not None)
|
|
366
|
-
|
|
435
|
+
|
|
367
436
|
chosen_action = menu.exec_(self._tree_view.mapToGlobal(pos))
|
|
368
437
|
if chosen_action == open_action:
|
|
369
438
|
self._editor_panel.open_file(path)
|
|
@@ -386,22 +455,30 @@ class ProjectExplorer(QDockWidget):
|
|
|
386
455
|
self._clipboard_source_path = path
|
|
387
456
|
elif chosen_action == paste_action:
|
|
388
457
|
self._paste_file_or_folder(path)
|
|
389
|
-
|
|
458
|
+
|
|
390
459
|
else:
|
|
391
460
|
# Right-clicked on a folder
|
|
392
461
|
open_action = menu.addAction("Open")
|
|
462
|
+
open_action.setShortcut(QKeySequence.Open)
|
|
393
463
|
open_sys_action = menu.addAction("Open folder")
|
|
394
464
|
new_file_action = menu.addAction("New File…")
|
|
465
|
+
new_file_action.setShortcut(QKeySequence.New)
|
|
395
466
|
new_folder_action = menu.addAction("New Folder…")
|
|
467
|
+
new_folder_action.setShortcut(QKeySequence("Ctrl+Shift+N"))
|
|
396
468
|
rename_action = menu.addAction("Rename…")
|
|
469
|
+
rename_action.setShortcut(QKeySequence("F2"))
|
|
397
470
|
delete_action = menu.addAction("Delete")
|
|
398
|
-
|
|
471
|
+
delete_action.setShortcut(QKeySequence.Delete)
|
|
472
|
+
|
|
399
473
|
menu.addSeparator()
|
|
400
474
|
cut_action = menu.addAction("Cut")
|
|
475
|
+
cut_action.setShortcut(QKeySequence.Cut)
|
|
401
476
|
copy_action = menu.addAction("Copy")
|
|
477
|
+
copy_action.setShortcut(QKeySequence.Copy)
|
|
402
478
|
paste_action = menu.addAction("Paste")
|
|
479
|
+
paste_action.setShortcut(QKeySequence.Paste)
|
|
403
480
|
paste_action.setEnabled(self._clipboard_source_path is not None)
|
|
404
|
-
|
|
481
|
+
|
|
405
482
|
chosen_action = menu.exec_(self._tree_view.mapToGlobal(pos))
|
|
406
483
|
if chosen_action == open_action:
|
|
407
484
|
# Expand in the tree if not already expanded
|
|
@@ -434,13 +511,21 @@ class ProjectExplorer(QDockWidget):
|
|
|
434
511
|
"""Create a new file in the specified folder."""
|
|
435
512
|
if not os.path.isdir(folder):
|
|
436
513
|
return
|
|
437
|
-
|
|
514
|
+
|
|
515
|
+
file_name, ok = QInputDialog.getText(self, "New File", "File name:")
|
|
438
516
|
if not ok or not file_name:
|
|
439
517
|
return
|
|
518
|
+
|
|
519
|
+
# Add default extension if none provided
|
|
520
|
+
if '.' not in file_name:
|
|
521
|
+
file_name += '.txt'
|
|
522
|
+
|
|
523
|
+
file_path = os.path.join(folder, file_name)
|
|
524
|
+
|
|
440
525
|
try:
|
|
441
|
-
with open(
|
|
526
|
+
with open(file_path, 'w', encoding='utf8') as f:
|
|
442
527
|
f.write("")
|
|
443
|
-
logger.info(f"Created file: {
|
|
528
|
+
logger.info(f"Created file: {file_path}")
|
|
444
529
|
except Exception as e:
|
|
445
530
|
QMessageBox.warning(self, "Error", f"Failed to create file:\n{str(e)}")
|
|
446
531
|
|
|
@@ -461,14 +546,30 @@ class ProjectExplorer(QDockWidget):
|
|
|
461
546
|
|
|
462
547
|
def _rename_file_or_folder(self, path):
|
|
463
548
|
"""Rename a file or folder via an input dialog, then rename in filesystem."""
|
|
464
|
-
base_dir = os.path.dirname(path)
|
|
465
549
|
old_name = os.path.basename(path)
|
|
466
|
-
|
|
467
|
-
|
|
550
|
+
parent_dir = os.path.dirname(path)
|
|
551
|
+
|
|
552
|
+
# Show dialog with current name pre-filled
|
|
553
|
+
new_name, ok = QInputDialog.getText(
|
|
554
|
+
self,
|
|
555
|
+
"Rename",
|
|
556
|
+
f"Rename '{old_name}' to:",
|
|
557
|
+
text=old_name
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
if not ok or not new_name or new_name == old_name:
|
|
561
|
+
return
|
|
562
|
+
|
|
563
|
+
new_path = os.path.join(parent_dir, new_name)
|
|
564
|
+
|
|
565
|
+
# Check if target already exists
|
|
566
|
+
if os.path.exists(new_path):
|
|
567
|
+
QMessageBox.warning(self, "Error", f"'{new_name}' already exists.")
|
|
468
568
|
return
|
|
569
|
+
|
|
469
570
|
try:
|
|
470
|
-
os.rename(path,
|
|
471
|
-
logger.info(f"Renamed {
|
|
571
|
+
os.rename(path, new_path)
|
|
572
|
+
logger.info(f"Renamed '{old_name}' to '{new_name}'")
|
|
472
573
|
except Exception as e:
|
|
473
574
|
QMessageBox.warning(self, "Error", f"Failed to rename:\n{str(e)}")
|
|
474
575
|
|
|
@@ -503,6 +604,28 @@ class ProjectExplorer(QDockWidget):
|
|
|
503
604
|
|
|
504
605
|
src = self._clipboard_source_path
|
|
505
606
|
dst = os.path.join(target_path, os.path.basename(src))
|
|
607
|
+
|
|
608
|
+
# Handle the case where source and destination are the same (copy operation only)
|
|
609
|
+
if self._clipboard_operation == 'copy' and os.path.abspath(src) == os.path.abspath(dst):
|
|
610
|
+
base_name = os.path.basename(src)
|
|
611
|
+
if os.path.isfile(src) and '.' in base_name:
|
|
612
|
+
# For files with extensions
|
|
613
|
+
name, ext = os.path.splitext(base_name)
|
|
614
|
+
dst = os.path.join(target_path, f"{name} (Copy){ext}")
|
|
615
|
+
else:
|
|
616
|
+
# For folders or files without extensions
|
|
617
|
+
dst = os.path.join(target_path, f"{base_name} (Copy)")
|
|
618
|
+
|
|
619
|
+
# If that name also exists, add numbers
|
|
620
|
+
counter = 2
|
|
621
|
+
while os.path.exists(dst):
|
|
622
|
+
if os.path.isfile(src) and '.' in base_name:
|
|
623
|
+
name, ext = os.path.splitext(base_name)
|
|
624
|
+
dst = os.path.join(target_path, f"{name} (Copy {counter}){ext}")
|
|
625
|
+
else:
|
|
626
|
+
dst = os.path.join(target_path, f"{base_name} (Copy {counter})")
|
|
627
|
+
counter += 1
|
|
628
|
+
|
|
506
629
|
try:
|
|
507
630
|
if self._clipboard_operation == 'copy':
|
|
508
631
|
if os.path.isdir(src):
|
|
@@ -517,11 +640,11 @@ class ProjectExplorer(QDockWidget):
|
|
|
517
640
|
|
|
518
641
|
self._clipboard_operation = None
|
|
519
642
|
self._clipboard_source_path = None
|
|
520
|
-
|
|
521
|
-
def closeEvent(self, event):
|
|
522
|
-
# Add selected folder to project folders but make sure we don't
|
|
643
|
+
|
|
644
|
+
def closeEvent(self, event):
|
|
645
|
+
# Add selected folder to project folders but make sure we don't
|
|
523
646
|
# duplicate
|
|
524
647
|
settings.project_folders = '::'.join(
|
|
525
648
|
set(str(settings.project_folders).split('::')) - {self._display_root})
|
|
526
649
|
super().closeEvent(event)
|
|
527
|
-
self.closed.emit(self)
|
|
650
|
+
self.closed.emit(self)
|
|
@@ -59,12 +59,20 @@ class EditorWorkspace:
|
|
|
59
59
|
def get(self):
|
|
60
60
|
text_cursor = self._editor.textCursor()
|
|
61
61
|
if text_cursor.hasSelection():
|
|
62
|
+
# Move to the start of the first selected block
|
|
63
|
+
start = text_cursor.selectionStart()
|
|
64
|
+
end = text_cursor.selectionEnd()
|
|
65
|
+
text_cursor.setPosition(start)
|
|
66
|
+
text_cursor.movePosition(text_cursor.StartOfBlock)
|
|
67
|
+
text_cursor.setPosition(end, text_cursor.KeepAnchor)
|
|
68
|
+
text_cursor.movePosition(text_cursor.EndOfBlock, text_cursor.KeepAnchor)
|
|
69
|
+
self._editor.setTextCursor(text_cursor)
|
|
62
70
|
content = self._normalize_line_breaks(text_cursor.selectedText())
|
|
63
71
|
else:
|
|
64
72
|
content = self._editor.toPlainText()
|
|
65
73
|
self._indentation = self._get_indentation(content)
|
|
66
|
-
logger.info(f'content was indented by "{self._indentation}"')
|
|
67
|
-
return content, self._editor.code_editor_language
|
|
74
|
+
logger.info(f'content was indented by "{self._indentation}"')
|
|
75
|
+
return textwrap.dedent(content), self._editor.code_editor_language
|
|
68
76
|
|
|
69
77
|
def set(self, content, language):
|
|
70
78
|
text_cursor = self._editor.textCursor()
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/ruff.py
RENAMED
|
@@ -2,6 +2,7 @@ import subprocess
|
|
|
2
2
|
import tempfile
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
5
|
+
import sys
|
|
5
6
|
import logging
|
|
6
7
|
logger = logging.getLogger(__name__)
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ logger = logging.getLogger(__name__)
|
|
|
9
10
|
def ruff_check(code: str, prefix: str | None = None) -> list[dict]:
|
|
10
11
|
"""
|
|
11
12
|
Lints Python source code using Ruff.
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
Args:
|
|
14
15
|
code (str): The Python source code to lint.
|
|
15
16
|
"""
|
|
@@ -25,9 +26,14 @@ def ruff_check(code: str, prefix: str | None = None) -> list[dict]:
|
|
|
25
26
|
tmp_file_path = tmp_file.name
|
|
26
27
|
print(tmp_file.name)
|
|
27
28
|
cmd = ["ruff", "check", tmp_file_path, "--output-format", "json"]
|
|
29
|
+
|
|
30
|
+
# Set creation flags for Windows to prevent console window from appearing
|
|
31
|
+
creation_flags = subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
|
|
32
|
+
|
|
28
33
|
try:
|
|
29
34
|
process = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
|
30
|
-
stderr=subprocess.PIPE, text=True
|
|
35
|
+
stderr=subprocess.PIPE, text=True,
|
|
36
|
+
creationflags=creation_flags)
|
|
31
37
|
stdout, stderr = process.communicate()
|
|
32
38
|
except Exception as e:
|
|
33
39
|
logger.error(f'failed to invoke ruff: {e}')
|
|
@@ -49,4 +55,4 @@ def ruff_check(code: str, prefix: str | None = None) -> list[dict]:
|
|
|
49
55
|
'code': message['code'],
|
|
50
56
|
'message': message['message'],
|
|
51
57
|
})
|
|
52
|
-
return formatted_result
|
|
58
|
+
return formatted_result
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/__init__.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/code_editors.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/code_editors/languages/text.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/find_in_files.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/components/jupyter_console.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/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
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/python_auto_indent.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/mixins/search_replace.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/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.32 → pyqt_code_editor-0.0.34}/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.32 → pyqt_code_editor-0.0.34}/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.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/calltip_widget.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/completion_popup.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/quick_open_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/quick_symbol_dialog.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/search_replace_frame.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/tab_splitter.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/widgets/tabbed_editor.py
RENAMED
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/languages/__init__.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/languages/generic.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/languages/python.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/__init__.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/codestral.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/jedi.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/sigmund.py
RENAMED
|
File without changes
|
{pyqt_code_editor-0.0.32 → pyqt_code_editor-0.0.34}/pyqt_code_editor/worker/providers/symbol.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|