pyqt-code-editor 0.0.47__tar.gz → 0.0.50__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.47 → pyqt_code_editor-0.0.50}/PKG-INFO +10 -3
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyproject.toml +10 -3
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/__init__.py +1 -1
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/app.py +4 -0
- pyqt_code_editor-0.0.50/pyqt_code_editor/code_editors/languages/generic.py +13 -0
- pyqt_code_editor-0.0.50/pyqt_code_editor/code_editors/languages/markdown.py +11 -0
- pyqt_code_editor-0.0.50/pyqt_code_editor/code_editors/languages/python.py +16 -0
- pyqt_code_editor-0.0.50/pyqt_code_editor/code_editors/languages/text.py +11 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/editor_panel.py +3 -1
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/settings_panel.py +1 -1
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/workspace_explorer.py +8 -12
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/__init__.py +2 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/auto_indent.py +0 -6
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/auto_pair.py +87 -64
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/base.py +7 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/check.py +0 -4
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/comment.py +0 -6
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/file_link.py +21 -20
- pyqt_code_editor-0.0.50/pyqt_code_editor/mixins/highlight_matching_brackets.py +98 -0
- pyqt_code_editor-0.0.50/pyqt_code_editor/mixins/merge_undo_actions.py +12 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/python_auto_indent.py +0 -9
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/shortcuts.py +1 -7
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/tabbed_editor.py +13 -9
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/languages/generic.py +2 -1
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/languages/python.py +2 -1
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/process.py +17 -1
- pyqt_code_editor-0.0.50/pyqt_code_editor/worker/providers/tree_sitter.py +61 -0
- pyqt_code_editor-0.0.47/pyqt_code_editor/code_editors/languages/generic.py +0 -9
- pyqt_code_editor-0.0.47/pyqt_code_editor/code_editors/languages/markdown.py +0 -11
- pyqt_code_editor-0.0.47/pyqt_code_editor/code_editors/languages/python.py +0 -13
- pyqt_code_editor-0.0.47/pyqt_code_editor/code_editors/languages/text.py +0 -9
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/COPYING +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/_settings.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/code_editors/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/code_editors/code_editors.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/code_editors/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/code_editors/languages/javascript.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/find_in_files.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/jupyter_console.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/project_explorer.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/sigmund.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/environment_manager.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/complete.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/execute.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/line_number.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/search_replace.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/smart_backspace_delete.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/symbols.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/theme.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/widgets/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/mixins/zoom.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/sigmund-analyst.png +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/signal_router.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/syntax_highlighters/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/syntax_highlighters/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/syntax_highlighters/languages/generic.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/syntax_highlighters/languages/python.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/syntax_highlighters/syntax_highlighter.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/themes.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/utils/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/utils/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/utils/languages/python/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/utils/languages/python/_auto_indent.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/utils/languages/python/_extract_cells.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/utils/languages/python/_mask_str_in_code.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/watchdog.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/calltip_widget.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/completion_popup.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/dock.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/quick_open_dialog.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/quick_open_file_dialog.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/quick_symbol_dialog.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/search_replace_frame.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/widgets/tab_splitter.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/languages/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/manager.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/providers/__init__.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/providers/codestral.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/providers/jedi.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/providers/ruff.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/providers/sigmund.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/providers/symbol.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/worker/settings.py +0 -0
- {pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/readme.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pyqt_code_editor
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.50
|
|
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>
|
|
@@ -12,15 +12,22 @@ Requires-Dist: detect-indent
|
|
|
12
12
|
Requires-Dist: jedi
|
|
13
13
|
Requires-Dist: httpx<0.29.0
|
|
14
14
|
Requires-Dist: mistralai
|
|
15
|
+
Requires-Dist: opensesame-extension-sigmund
|
|
15
16
|
Requires-Dist: pathspec
|
|
16
17
|
Requires-Dist: psutil
|
|
17
18
|
Requires-Dist: pygments
|
|
18
19
|
Requires-Dist: qtawesome
|
|
19
20
|
Requires-Dist: qtpy
|
|
20
|
-
Requires-Dist: ruff
|
|
21
|
-
Requires-Dist: opensesame-extension-sigmund
|
|
22
21
|
Requires-Dist: pyqt6
|
|
23
22
|
Requires-Dist: qtconsole
|
|
23
|
+
Requires-Dist: tree-sitter
|
|
24
|
+
Requires-Dist: tree-sitter-javascript
|
|
25
|
+
Requires-Dist: tree-sitter-json
|
|
26
|
+
Requires-Dist: tree-sitter-html
|
|
27
|
+
Requires-Dist: tree-sitter-python
|
|
28
|
+
Requires-Dist: tree-sitter-toml
|
|
29
|
+
Requires-Dist: tree-sitter-yaml
|
|
30
|
+
Requires-Dist: ruff
|
|
24
31
|
Project-URL: Source, https://github.com/open-cogsci/sigmund-analyst
|
|
25
32
|
|
|
26
33
|
# PyQt Code Editor / Sigmund Analyst
|
|
@@ -17,15 +17,22 @@ dependencies = [
|
|
|
17
17
|
"jedi",
|
|
18
18
|
"httpx<0.29.0",
|
|
19
19
|
"mistralai",
|
|
20
|
+
"opensesame-extension-sigmund",
|
|
20
21
|
"pathspec",
|
|
21
22
|
"psutil",
|
|
22
23
|
"pygments",
|
|
23
24
|
"qtawesome",
|
|
24
25
|
"qtpy",
|
|
25
|
-
"ruff",
|
|
26
|
-
"opensesame-extension-sigmund",
|
|
27
26
|
"pyqt6",
|
|
28
|
-
"qtconsole"
|
|
27
|
+
"qtconsole",
|
|
28
|
+
"tree-sitter",
|
|
29
|
+
"tree-sitter-javascript",
|
|
30
|
+
"tree-sitter-json",
|
|
31
|
+
"tree-sitter-html",
|
|
32
|
+
"tree-sitter-python",
|
|
33
|
+
"tree-sitter-toml",
|
|
34
|
+
"tree-sitter-yaml",
|
|
35
|
+
"ruff"
|
|
29
36
|
]
|
|
30
37
|
keywords = ["code editor", "pyqt", "ide"]
|
|
31
38
|
|
|
@@ -37,6 +37,7 @@ class SigmundAnalyst(QMainWindow):
|
|
|
37
37
|
# The editor panel provides splittable editor tabs
|
|
38
38
|
self._editor_panel = EditorPanel()
|
|
39
39
|
self._editor_panel.open_folder_requested.connect(self._open_folder)
|
|
40
|
+
self._editor_panel.open_file_requested.connect(self._open_file)
|
|
40
41
|
self.setCentralWidget(self._editor_panel)
|
|
41
42
|
self._project_explorers = []
|
|
42
43
|
# Track if project explorers are hidden as a group
|
|
@@ -407,6 +408,9 @@ class SigmundAnalyst(QMainWindow):
|
|
|
407
408
|
self._project_explorers_hidden = False
|
|
408
409
|
for explorer in self._project_explorers:
|
|
409
410
|
explorer.setVisible(True)
|
|
411
|
+
|
|
412
|
+
def _open_file(self, path):
|
|
413
|
+
self._editor_panel.open_file(path)
|
|
410
414
|
|
|
411
415
|
def closeEvent(self, event):
|
|
412
416
|
# Check if there are unsaved changes, and ask for confirmation if there
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from qtpy.QtWidgets import QPlainTextEdit
|
|
2
|
+
from pyqt_code_editor.mixins import (MergeUndoActions, Complete, Theme, Zoom,
|
|
3
|
+
LineNumber, Comment, AutoIndent,
|
|
4
|
+
AutoPair, SearchReplace, Base, Check,
|
|
5
|
+
Shortcuts, FileLink, Symbols,
|
|
6
|
+
HighlightMatchingBrackets)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Editor(MergeUndoActions, LineNumber, Zoom, Complete, AutoIndent,
|
|
10
|
+
AutoPair, Comment, SearchReplace, FileLink, Theme, Check,
|
|
11
|
+
Shortcuts, HighlightMatchingBrackets, Symbols, Base,
|
|
12
|
+
QPlainTextEdit):
|
|
13
|
+
pass
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from qtpy.QtWidgets import QPlainTextEdit
|
|
2
|
+
from pyqt_code_editor.mixins import (MergeUndoActions, Complete,
|
|
3
|
+
HighlightSyntax, Zoom, LineNumber,
|
|
4
|
+
Comment, SearchReplace, Base, Check,
|
|
5
|
+
Shortcuts, FileLink, Symbols)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Editor(MergeUndoActions, LineNumber, Zoom, Complete, Comment,
|
|
9
|
+
SearchReplace, FileLink, HighlightSyntax, Check, Shortcuts,
|
|
10
|
+
Symbols, Base, QPlainTextEdit):
|
|
11
|
+
pass
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from qtpy.QtWidgets import QPlainTextEdit
|
|
2
|
+
from pyqt_code_editor.mixins import (Complete, PythonAutoIndent,
|
|
3
|
+
PythonAutoPair, Theme, Zoom, LineNumber,
|
|
4
|
+
Comment, SearchReplace, Base, Check,
|
|
5
|
+
Shortcuts, FileLink, Symbols,
|
|
6
|
+
SmartBackspaceDelete, Execute,
|
|
7
|
+
MergeUndoActions,
|
|
8
|
+
HighlightMatchingBrackets)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Editor(MergeUndoActions, LineNumber, Zoom, SmartBackspaceDelete,
|
|
12
|
+
PythonAutoPair, Complete, Comment, PythonAutoIndent,
|
|
13
|
+
SearchReplace, FileLink, Execute, Check, Theme, Shortcuts,
|
|
14
|
+
HighlightMatchingBrackets, Symbols, Base, QPlainTextEdit):
|
|
15
|
+
|
|
16
|
+
code_editor_language = 'python'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from qtpy.QtWidgets import QPlainTextEdit
|
|
2
|
+
from pyqt_code_editor.mixins import (MergeUndoActions, Complete,
|
|
3
|
+
HighlightSyntax, Zoom, LineNumber,
|
|
4
|
+
SearchReplace, Base, Check, Shortcuts,
|
|
5
|
+
FileLink)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Editor(MergeUndoActions, LineNumber, Zoom, Complete, SearchReplace,
|
|
9
|
+
FileLink, HighlightSyntax, Check, Shortcuts, Base,
|
|
10
|
+
QPlainTextEdit):
|
|
11
|
+
pass
|
{pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/editor_panel.py
RENAMED
|
@@ -11,6 +11,7 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
class EditorPanel(QWidget):
|
|
12
12
|
|
|
13
13
|
open_folder_requested = Signal(str)
|
|
14
|
+
open_file_requested = Signal(str)
|
|
14
15
|
|
|
15
16
|
def __init__(self, parent=None, initial_path=None):
|
|
16
17
|
super().__init__(parent)
|
|
@@ -58,6 +59,7 @@ class EditorPanel(QWidget):
|
|
|
58
59
|
t.lastTabClosed.connect(self.handle_tabbed_editor_empty)
|
|
59
60
|
t.tabCloseRequested.connect(self._relabel_tabs)
|
|
60
61
|
t.open_folder_requested.connect(self.open_folder_requested)
|
|
62
|
+
t.open_file_requested.connect(self.open_file_requested)
|
|
61
63
|
return t
|
|
62
64
|
|
|
63
65
|
def unsaved_changes(self):
|
|
@@ -92,7 +94,7 @@ class EditorPanel(QWidget):
|
|
|
92
94
|
settings.current_folder = os.path.dirname(path)
|
|
93
95
|
self.open_file(path)
|
|
94
96
|
|
|
95
|
-
def _keep_track_of_active_editor(self, editor):
|
|
97
|
+
def _keep_track_of_active_editor(self, editor):
|
|
96
98
|
logger.info(f"active editor = {editor}")
|
|
97
99
|
self._active_editor = editor
|
|
98
100
|
self._active_tab_widget = self._active_editor.parent().parent()
|
{pyqt_code_editor-0.0.47 → pyqt_code_editor-0.0.50}/pyqt_code_editor/components/settings_panel.py
RENAMED
|
@@ -104,7 +104,7 @@ class SettingsWidget(QWidget):
|
|
|
104
104
|
|
|
105
105
|
elif isinstance(value, int):
|
|
106
106
|
widget = QSpinBox()
|
|
107
|
-
widget.setRange(-
|
|
107
|
+
widget.setRange(int(-1e9), int(1e9)) # Reasonable default range
|
|
108
108
|
widget.setValue(value)
|
|
109
109
|
widget.valueChanged.connect(lambda val, name=setting_name:
|
|
110
110
|
self.update_setting(name, val))
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
from qtpy.QtWidgets import (
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
from qtpy.QtCore import Qt, QSortFilterProxyModel, QAbstractTableModel,
|
|
5
|
-
|
|
1
|
+
from qtpy.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTableView,
|
|
2
|
+
QHeaderView, QLineEdit, QAbstractItemView, QMenu,
|
|
3
|
+
QAction)
|
|
4
|
+
from qtpy.QtCore import (Qt, QSortFilterProxyModel, QAbstractTableModel,
|
|
5
|
+
QModelIndex)
|
|
6
|
+
from qtpy.QtGui import QColor, QFont, QBrush
|
|
6
7
|
from ..widgets import Dock
|
|
7
8
|
|
|
8
9
|
|
|
@@ -77,6 +78,7 @@ class WorkspaceModel(QAbstractTableModel):
|
|
|
77
78
|
return self._headers[section]
|
|
78
79
|
return None
|
|
79
80
|
|
|
81
|
+
|
|
80
82
|
class WorkspaceExplorer(Dock):
|
|
81
83
|
"""Dock widget for exploring variables in the Jupyter workspace"""
|
|
82
84
|
|
|
@@ -150,13 +152,7 @@ class WorkspaceExplorer(Dock):
|
|
|
150
152
|
# Add actions specific to the variable
|
|
151
153
|
inspect_action = QAction(f"Inspect '{var_name}'", self)
|
|
152
154
|
delete_action = QAction(f"Delete '{var_name}'", self)
|
|
153
|
-
|
|
154
155
|
menu.addAction(inspect_action)
|
|
155
156
|
menu.addAction(delete_action)
|
|
156
|
-
|
|
157
|
-
# Show the menu
|
|
158
|
-
action = menu.exec_(self.table_view.mapToGlobal(position))
|
|
159
|
-
|
|
160
|
-
# Handle action selection
|
|
161
|
-
# (In a real implementation, would connect to actual functionality)
|
|
157
|
+
menu.exec_(self.table_view.mapToGlobal(position))
|
|
162
158
|
|
|
@@ -118,7 +118,6 @@ class AutoIndent:
|
|
|
118
118
|
"""
|
|
119
119
|
logger.info("indent_code triggered")
|
|
120
120
|
cursor = self.textCursor()
|
|
121
|
-
cursor.beginEditBlock()
|
|
122
121
|
|
|
123
122
|
if self._is_multiline_selection():
|
|
124
123
|
logger.info("Multi-line selection -> indent each line")
|
|
@@ -128,8 +127,6 @@ class AutoIndent:
|
|
|
128
127
|
indent_str = self._get_indent_string()
|
|
129
128
|
cursor.insertText(indent_str)
|
|
130
129
|
|
|
131
|
-
cursor.endEditBlock()
|
|
132
|
-
|
|
133
130
|
def dedent_code(self):
|
|
134
131
|
"""
|
|
135
132
|
Dedent either the selected lines (if multi-line selection)
|
|
@@ -137,7 +134,6 @@ class AutoIndent:
|
|
|
137
134
|
"""
|
|
138
135
|
logger.info("dedent_code triggered")
|
|
139
136
|
cursor = self.textCursor()
|
|
140
|
-
cursor.beginEditBlock()
|
|
141
137
|
|
|
142
138
|
if self._is_multiline_selection():
|
|
143
139
|
logger.info("Multi-line selection -> dedent each line")
|
|
@@ -156,8 +152,6 @@ class AutoIndent:
|
|
|
156
152
|
for _ in range(remove_chars):
|
|
157
153
|
self._delete_forward_if_tab_or_space(cursor)
|
|
158
154
|
|
|
159
|
-
cursor.endEditBlock()
|
|
160
|
-
|
|
161
155
|
def _indent_selection(self):
|
|
162
156
|
"""
|
|
163
157
|
Increase indent for each selected line by one chunk.
|
|
@@ -4,9 +4,8 @@ logger = logging.getLogger(__name__)
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class AutoPair:
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
"""Generic mixin for auto-pairing brackets/quotes/etc.
|
|
8
|
+
|
|
10
9
|
You can override self.PAIRS to add or remove different open/close
|
|
11
10
|
sequences. Each entry is a dict with:
|
|
12
11
|
- open_seq: string that triggers auto-pair (e.g. '(' or '\"\"\"')
|
|
@@ -14,7 +13,7 @@ class AutoPair:
|
|
|
14
13
|
- inbetween_seq: optional string inserted between open_seq and close_seq
|
|
15
14
|
(can be empty). e.g. '\n' for triple quotes.
|
|
16
15
|
"""
|
|
17
|
-
|
|
16
|
+
|
|
18
17
|
PAIRS = [
|
|
19
18
|
{"open_seq": "(", "close_seq": ")", "inbetween_seq": ""},
|
|
20
19
|
{"open_seq": "[", "close_seq": "]", "inbetween_seq": ""},
|
|
@@ -22,155 +21,179 @@ class AutoPair:
|
|
|
22
21
|
{"open_seq": "\"", "close_seq": "\"", "inbetween_seq": ""},
|
|
23
22
|
{"open_seq": "\'", "close_seq": "\'", "inbetween_seq": ""},
|
|
24
23
|
]
|
|
25
|
-
|
|
24
|
+
|
|
26
25
|
def __init__(self, *args, **kwargs):
|
|
27
26
|
super().__init__(*args, **kwargs)
|
|
28
27
|
self.setUndoRedoEnabled(True)
|
|
29
28
|
# For scanning up to the max length of an open_seq
|
|
30
29
|
self._max_open_len = max(len(pair["open_seq"]) for pair in self.PAIRS)
|
|
30
|
+
self._trigger_chars = ''.join([pair['open_seq'] + pair['close_seq']
|
|
31
|
+
for pair in self.PAIRS])
|
|
31
32
|
|
|
32
33
|
def keyPressEvent(self, event):
|
|
34
|
+
# Determine all relevant variables upfront
|
|
35
|
+
typed_char = event.text()
|
|
36
|
+
typed_key = event.key()
|
|
33
37
|
cursor = self.textCursor()
|
|
34
38
|
old_pos = cursor.position()
|
|
35
39
|
selected_text = cursor.selectedText()
|
|
36
|
-
|
|
40
|
+
doc_text = self.toPlainText()
|
|
41
|
+
doc_len = len(doc_text)
|
|
42
|
+
# Get character before cursor
|
|
43
|
+
if old_pos > 0:
|
|
44
|
+
char_before = doc_text[old_pos - 1]
|
|
45
|
+
else:
|
|
46
|
+
char_before = ""
|
|
47
|
+
# Get character after cursor
|
|
48
|
+
if old_pos < doc_len:
|
|
49
|
+
char_after = doc_text[old_pos]
|
|
50
|
+
else:
|
|
51
|
+
char_after = ""
|
|
52
|
+
|
|
53
|
+
# If there is no opening (trigger character or backspace), then we
|
|
54
|
+
# don't need to do anything.
|
|
55
|
+
if typed_char not in self._trigger_chars and typed_key != Qt.Key_Backspace:
|
|
56
|
+
super().keyPressEvent(event)
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
# Don't autopair when there is no selection and the next character is
|
|
60
|
+
# alphanumeric or underscore. This is for example when we're typing the
|
|
61
|
+
# opening bracket of a list that is already completed
|
|
62
|
+
if not selected_text:
|
|
63
|
+
if char_after.isalnum() or char_after == "_":
|
|
64
|
+
super().keyPressEvent(event)
|
|
65
|
+
return
|
|
66
|
+
|
|
37
67
|
# Allows auto-pairing to be disabled for certain positions
|
|
38
68
|
if self._disabled_for_position(old_pos):
|
|
39
69
|
super().keyPressEvent(event)
|
|
40
70
|
return
|
|
41
|
-
|
|
71
|
+
|
|
42
72
|
# 1) Handle backspace for removing empty pairs like (|)
|
|
43
|
-
if
|
|
44
|
-
if self._handle_auto_pair_backspace():
|
|
73
|
+
if typed_key == Qt.Key_Backspace:
|
|
74
|
+
if self._handle_auto_pair_backspace(char_before, char_after, old_pos):
|
|
45
75
|
super().keyPressEvent(event)
|
|
46
76
|
return
|
|
47
|
-
|
|
48
|
-
typed_char = event.text()
|
|
49
|
-
|
|
77
|
+
|
|
50
78
|
# Pass the event up (inserts the typed_char)
|
|
51
79
|
super().keyPressEvent(event)
|
|
52
|
-
|
|
80
|
+
|
|
53
81
|
# 2) Possibly skip duplicate closing bracket/quote
|
|
54
82
|
# when the user manually types it.
|
|
55
83
|
if typed_char and len(typed_char) == 1:
|
|
56
84
|
for pair in self.PAIRS:
|
|
57
85
|
if typed_char == pair["close_seq"]:
|
|
86
|
+
print(typed_char)
|
|
58
87
|
new_cursor = self.textCursor()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
88
|
+
new_pos = new_cursor.position()
|
|
89
|
+
new_doc_text = self.toPlainText()
|
|
90
|
+
new_doc_len = len(new_doc_text)
|
|
91
|
+
if new_pos < new_doc_len and new_doc_text[new_pos] == typed_char:
|
|
62
92
|
# We remove the newly typed character and move cursor forward
|
|
63
|
-
new_cursor.beginEditBlock()
|
|
64
93
|
# Remove the bracket that was just typed
|
|
65
94
|
new_cursor.setPosition(old_pos)
|
|
66
95
|
new_cursor.deleteChar()
|
|
67
96
|
# Move cursor to skip the existing bracket
|
|
68
97
|
new_cursor.setPosition(old_pos + 1)
|
|
69
|
-
new_cursor.endEditBlock()
|
|
70
98
|
self.setTextCursor(new_cursor)
|
|
71
99
|
break
|
|
72
|
-
|
|
100
|
+
|
|
73
101
|
# 3) After insertion, see if the text before the cursor matches an 'open_seq'
|
|
74
102
|
# If it does, insert close_seq + inbetween_seq, then restore cursor.
|
|
75
103
|
new_cursor = self.textCursor()
|
|
76
104
|
new_pos = new_cursor.position()
|
|
77
105
|
text = self.toPlainText()
|
|
78
|
-
|
|
106
|
+
|
|
79
107
|
# We'll scan backward from new_pos for up to _max_open_len chars
|
|
80
108
|
start_search = max(0, new_pos - self._max_open_len)
|
|
81
109
|
just_typed = text[start_search:new_pos]
|
|
82
|
-
|
|
110
|
+
|
|
83
111
|
for pair in self.PAIRS:
|
|
84
112
|
open_seq = pair["open_seq"]
|
|
85
113
|
close_seq = pair["close_seq"]
|
|
86
114
|
inbetween_seq = pair["inbetween_seq"]
|
|
87
|
-
|
|
115
|
+
|
|
88
116
|
if just_typed.endswith(open_seq) and event.text() == text[new_pos - 1: new_pos]:
|
|
89
117
|
self._insert_pair(open_seq, close_seq, inbetween_seq,
|
|
90
|
-
selected_text)
|
|
118
|
+
selected_text, new_pos)
|
|
91
119
|
break
|
|
92
120
|
|
|
93
|
-
|
|
94
|
-
def _handle_auto_pair_backspace(self) -> bool:
|
|
121
|
+
def _handle_auto_pair_backspace(self, char_before, char_after, pos) -> bool:
|
|
95
122
|
"""
|
|
96
123
|
If the cursor is between an exact open_seq and close_seq pair (e.g., '(|)'),
|
|
97
124
|
remove the following close_seq. The preceding open_seq will be removed
|
|
98
125
|
by the standard backspace. Only works for pairs of single characters.
|
|
99
|
-
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
char_before: The character before the cursor
|
|
129
|
+
char_after: The character after the cursor
|
|
130
|
+
pos: The cursor position
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
True if handled, False if not.
|
|
100
134
|
"""
|
|
101
|
-
cursor = self.textCursor()
|
|
102
|
-
pos = cursor.position()
|
|
103
|
-
|
|
104
135
|
# We need at least one character before and after cursor
|
|
105
|
-
if
|
|
136
|
+
if not char_before or not char_after:
|
|
106
137
|
return False
|
|
107
|
-
|
|
108
|
-
# Get the character before the cursor
|
|
109
|
-
cursor.setPosition(pos - 1)
|
|
110
|
-
cursor.setPosition(pos, cursor.KeepAnchor)
|
|
111
|
-
char_before = cursor.selectedText()
|
|
112
|
-
|
|
113
|
-
# Get the character after the cursor
|
|
114
|
-
cursor.setPosition(pos)
|
|
115
|
-
cursor.setPosition(pos + 1, cursor.KeepAnchor)
|
|
116
|
-
char_after = cursor.selectedText()
|
|
117
|
-
|
|
138
|
+
|
|
118
139
|
# Check if they form a pair
|
|
119
140
|
for pair in self.PAIRS:
|
|
120
141
|
open_seq = pair["open_seq"]
|
|
121
142
|
close_seq = pair["close_seq"]
|
|
122
|
-
|
|
143
|
+
|
|
123
144
|
# Only handle single-character pairs
|
|
124
145
|
if len(open_seq) != 1 or len(close_seq) != 1:
|
|
125
146
|
continue
|
|
126
|
-
|
|
147
|
+
|
|
127
148
|
if char_before == open_seq and char_after == close_seq:
|
|
128
149
|
logger.info("deleting closing bracket")
|
|
129
150
|
# Delete only the closing character
|
|
151
|
+
cursor = self.textCursor()
|
|
130
152
|
cursor.setPosition(pos)
|
|
131
153
|
cursor.deleteChar()
|
|
132
|
-
|
|
154
|
+
|
|
133
155
|
# The standard backspace will handle the opening character
|
|
134
156
|
return True
|
|
135
|
-
|
|
136
|
-
return False
|
|
137
157
|
|
|
158
|
+
return False
|
|
138
159
|
|
|
139
|
-
def _insert_pair(self, open_seq, close_seq, inbetween_seq, selected_text):
|
|
160
|
+
def _insert_pair(self, open_seq, close_seq, inbetween_seq, selected_text, cursor_pos):
|
|
140
161
|
"""
|
|
141
162
|
Called when we recognize that the user typed an open_seq in full.
|
|
142
163
|
Insert the close_seq plus any inbetween_seq, and restore the cursor
|
|
143
164
|
to the original position (right after the open_seq).
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
open_seq: The opening sequence that was typed
|
|
168
|
+
close_seq: The closing sequence to insert
|
|
169
|
+
inbetween_seq: Text to insert between open and close
|
|
170
|
+
selected_text: Text that was selected (if any)
|
|
171
|
+
cursor_pos: Current cursor position (after the open_seq was inserted)
|
|
144
172
|
"""
|
|
145
173
|
c = self.textCursor()
|
|
146
|
-
|
|
147
|
-
# We'll remember where the user ended up (right after open_seq).
|
|
148
|
-
old_pos = c.position()
|
|
174
|
+
|
|
149
175
|
if inbetween_seq == '\n':
|
|
150
176
|
# Get indentation level of block after cursor
|
|
151
|
-
block = self.document().findBlock(
|
|
177
|
+
block = self.document().findBlock(cursor_pos)
|
|
152
178
|
block_text = block.text()
|
|
153
179
|
indent = block_text[:len(block_text) - len(block_text.lstrip())]
|
|
154
180
|
inbetween_seq = '\n' + indent
|
|
155
|
-
|
|
156
|
-
c.beginEditBlock()
|
|
157
|
-
|
|
181
|
+
|
|
158
182
|
# Insert in-between text, then the closing
|
|
159
183
|
c.insertText(selected_text + inbetween_seq + close_seq)
|
|
160
|
-
|
|
184
|
+
|
|
161
185
|
# Move the cursor back so that it's right after the open_seq and (if any)
|
|
162
186
|
# the selected text
|
|
163
187
|
c.movePosition(c.Left, c.MoveAnchor, len(close_seq) + len(inbetween_seq))
|
|
164
|
-
|
|
165
|
-
c.endEditBlock()
|
|
188
|
+
|
|
166
189
|
self.setTextCursor(c)
|
|
167
|
-
|
|
190
|
+
|
|
168
191
|
logger.info(
|
|
169
192
|
"Auto-paired '%s' with '%s', inserted in-between '%s'. "
|
|
170
193
|
"Cursor restored to position %d",
|
|
171
|
-
open_seq, close_seq, inbetween_seq,
|
|
194
|
+
open_seq, close_seq, inbetween_seq, cursor_pos
|
|
172
195
|
)
|
|
173
|
-
|
|
196
|
+
|
|
174
197
|
def _disabled_for_position(self, pos):
|
|
175
198
|
return False
|
|
176
199
|
|
|
@@ -186,7 +209,7 @@ class PythonAutoPair(AutoPair):
|
|
|
186
209
|
{"open_seq": "\"", "close_seq": "\"", "inbetween_seq": ""},
|
|
187
210
|
{"open_seq": "\'", "close_seq": "\'", "inbetween_seq": ""},
|
|
188
211
|
]
|
|
189
|
-
|
|
212
|
+
|
|
190
213
|
def _disabled_for_position(self, pos):
|
|
191
214
|
"""
|
|
192
215
|
Returns True if 'pos' (a QTextCursor.position() in self)
|
|
@@ -197,12 +220,12 @@ class PythonAutoPair(AutoPair):
|
|
|
197
220
|
block = self.document().findBlock(pos)
|
|
198
221
|
line_text = block.text()
|
|
199
222
|
column = pos - block.position()
|
|
200
|
-
|
|
223
|
+
|
|
201
224
|
# Find the first '#' in line_text
|
|
202
225
|
hash_index = line_text.find('#')
|
|
203
|
-
|
|
226
|
+
|
|
204
227
|
# If a '#' is found and it's to the left of the cursor, assume comment
|
|
205
228
|
if hash_index != -1 and hash_index < column:
|
|
206
229
|
return True
|
|
207
|
-
|
|
230
|
+
|
|
208
231
|
return False
|
|
@@ -50,6 +50,13 @@ class Base:
|
|
|
50
50
|
for example to avoid certain keypresses from being consumed.
|
|
51
51
|
"""
|
|
52
52
|
return False
|
|
53
|
+
|
|
54
|
+
def setFocus(self):
|
|
55
|
+
"""Allows managing widgets, such as the editor panel, to keep track of
|
|
56
|
+
which editor is active
|
|
57
|
+
"""
|
|
58
|
+
super().setFocus()
|
|
59
|
+
self.received_focus.emit(self)
|
|
53
60
|
|
|
54
61
|
def focusInEvent(self, event):
|
|
55
62
|
"""Allows managing widgets, such as the editor panel, to keep track of
|
|
@@ -23,10 +23,6 @@ class Check:
|
|
|
23
23
|
self._check_debounce_timer.setSingleShot(True)
|
|
24
24
|
self._check_debounce_timer.setInterval(500)
|
|
25
25
|
self._check_debounce_timer.timeout.connect(self._execute_check)
|
|
26
|
-
# Periodic timer: also periodically run a check
|
|
27
|
-
self._check_interval_timer = QTimer(self)
|
|
28
|
-
self._check_interval_timer.setInterval(5000)
|
|
29
|
-
self._check_interval_timer.timeout.connect(self._execute_check)
|
|
30
26
|
|
|
31
27
|
# Connect textChanged signal to a debounced method
|
|
32
28
|
self.textChanged.connect(self._on_code_changed)
|
|
@@ -35,10 +35,8 @@ class Comment:
|
|
|
35
35
|
# Extract the indentation from the current line
|
|
36
36
|
indentation = line_text[:len(line_text) - len(stripped)]
|
|
37
37
|
# Insert newline with same indentation plus comment marker
|
|
38
|
-
cursor.beginEditBlock()
|
|
39
38
|
cursor.insertText(
|
|
40
39
|
'\n' + indentation + self.code_editor_comment_string)
|
|
41
|
-
cursor.endEditBlock()
|
|
42
40
|
event.accept()
|
|
43
41
|
return
|
|
44
42
|
super().keyPressEvent(event)
|
|
@@ -76,7 +74,6 @@ class Comment:
|
|
|
76
74
|
def _comment_blocks(self, start_block: int, end_block: int):
|
|
77
75
|
"""Comment all lines from start_block to end_block."""
|
|
78
76
|
cursor = self.textCursor()
|
|
79
|
-
cursor.beginEditBlock() # group undo steps
|
|
80
77
|
for block_num in range(start_block, end_block + 1):
|
|
81
78
|
block = self.document().findBlockByNumber(block_num)
|
|
82
79
|
if not block.isValid():
|
|
@@ -87,12 +84,10 @@ class Comment:
|
|
|
87
84
|
insert_position = block.position() + leading_spaces
|
|
88
85
|
cursor.setPosition(insert_position)
|
|
89
86
|
cursor.insertText(self.code_editor_comment_string)
|
|
90
|
-
cursor.endEditBlock()
|
|
91
87
|
|
|
92
88
|
def _uncomment_blocks(self, start_block: int, end_block: int):
|
|
93
89
|
"""Uncomment all lines from start_block to end_block."""
|
|
94
90
|
cursor = self.textCursor()
|
|
95
|
-
cursor.beginEditBlock()
|
|
96
91
|
for block_num in range(start_block, end_block + 1):
|
|
97
92
|
block = self.document().findBlockByNumber(block_num)
|
|
98
93
|
if not block.isValid():
|
|
@@ -107,4 +102,3 @@ class Comment:
|
|
|
107
102
|
cursor.setPosition(remove_position + strip_len, cursor.KeepAnchor)
|
|
108
103
|
if cursor.selectedText() == self.code_editor_comment_string.rstrip('\r\n'):
|
|
109
104
|
cursor.removeSelectedText()
|
|
110
|
-
cursor.endEditBlock()
|
|
@@ -42,29 +42,30 @@ class FileLink:
|
|
|
42
42
|
# Check file size
|
|
43
43
|
file_size = path.stat().st_size
|
|
44
44
|
is_large = file_size > settings.max_file_size
|
|
45
|
-
|
|
46
|
-
raw_data = path.read_bytes()
|
|
47
45
|
encoding_unknown = False
|
|
48
46
|
|
|
49
|
-
if
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
detect_result = chardet.detect(raw_data)
|
|
58
|
-
# If detection fails or returns None, or confidence is low
|
|
59
|
-
if (detect_result is None or
|
|
60
|
-
detect_result.get("encoding") is None or
|
|
61
|
-
detect_result.get("confidence", 0) < 0.7):
|
|
47
|
+
if not is_large:
|
|
48
|
+
raw_data = path.read_bytes()
|
|
49
|
+
|
|
50
|
+
if encoding is None:
|
|
51
|
+
# First see if everything is within ASCII range
|
|
52
|
+
try:
|
|
53
|
+
raw_data.decode("utf-8")
|
|
54
|
+
# If no error -> it's strictly ASCII, so decode as UTF-8
|
|
62
55
|
used_encoding = "utf-8"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
56
|
+
except UnicodeDecodeError:
|
|
57
|
+
# Not pure ASCII; let chardet pick
|
|
58
|
+
detect_result = chardet.detect(raw_data)
|
|
59
|
+
# If detection fails or returns None, or confidence is low
|
|
60
|
+
if (detect_result is None or
|
|
61
|
+
detect_result.get("encoding") is None or
|
|
62
|
+
detect_result.get("confidence", 0) < 0.7):
|
|
63
|
+
used_encoding = "utf-8"
|
|
64
|
+
encoding_unknown = True
|
|
65
|
+
else:
|
|
66
|
+
used_encoding = detect_result["encoding"]
|
|
67
|
+
else:
|
|
68
|
+
used_encoding = encoding
|
|
68
69
|
|
|
69
70
|
# Show warning if file is large or encoding is uncertain
|
|
70
71
|
if is_large or encoding_unknown:
|