qppte 0.7.0__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.
- qppte-0.7.0/LICENSE +21 -0
- qppte-0.7.0/PKG-INFO +110 -0
- qppte-0.7.0/README.md +92 -0
- qppte-0.7.0/pyproject.toml +55 -0
- qppte-0.7.0/setup.cfg +4 -0
- qppte-0.7.0/src/qppte/__init__.py +5 -0
- qppte-0.7.0/src/qppte/action_trigger.py +59 -0
- qppte-0.7.0/src/qppte/example_gui.py +105 -0
- qppte-0.7.0/src/qppte/line_number_panel.py +77 -0
- qppte-0.7.0/src/qppte/qppte_tree_sitter.py +57 -0
- qppte-0.7.0/src/qppte/qpythonplaintextedit.py +1167 -0
- qppte-0.7.0/src/qppte/qpythonplaintextsettings.py +197 -0
- qppte-0.7.0/src/qppte/qpythonplaintextwidget.py +23 -0
- qppte-0.7.0/src/qppte/style.py +83 -0
- qppte-0.7.0/src/qppte.egg-info/PKG-INFO +110 -0
- qppte-0.7.0/src/qppte.egg-info/SOURCES.txt +18 -0
- qppte-0.7.0/src/qppte.egg-info/dependency_links.txt +1 -0
- qppte-0.7.0/src/qppte.egg-info/entry_points.txt +2 -0
- qppte-0.7.0/src/qppte.egg-info/requires.txt +3 -0
- qppte-0.7.0/src/qppte.egg-info/top_level.txt +3 -0
qppte-0.7.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dmitri Priimak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
qppte-0.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qppte
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Embeddable QPlainTextEdit with Python syntax highlighting
|
|
5
|
+
Author-email: Dmitri Priimak <priimak@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://brainless-software.github.io/qppte
|
|
7
|
+
Project-URL: Repository, https://github.com/Brainless-Software/qppte
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pyside6>=6.11.1
|
|
15
|
+
Requires-Dist: tree-sitter!=0.26.0,>=0.24
|
|
16
|
+
Requires-Dist: tree-sitter-python>=0.25.0
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
QPPTE - QPythonPlainTextEdit
|
|
20
|
+
============================
|
|
21
|
+
|
|
22
|
+
A poor-main embeddable code editor for python. Written in Python with [PySide6](https://pypi.org/project/PySide6/)
|
|
23
|
+
it is created with intent to be used within larger application where code editor is required. For example
|
|
24
|
+
if you application supports plugins in Python and you want to give users ability to create and edit such plugins, then
|
|
25
|
+
you can make this library a dependency in your project and embed this editor.
|
|
26
|
+
|
|
27
|
+
More information can be found [here](https://brainless-software.github.io/qppte/).
|
|
28
|
+
|
|
29
|
+
A poor-main text editor for editing and viewing Python code to be used with [PySide6](https://pypi.org/project/PySide6/)
|
|
30
|
+
library. It supports basic syntax highlighting using [tree-sitter](https://github.com/tree-sitter/py-tree-sitter) and
|
|
31
|
+
keystrokes common to code editors. Highlighting styles can be customized and expanded. Likewise, keystrokes can be
|
|
32
|
+
customized by the user.
|
|
33
|
+
|
|
34
|
+
When syntax highlighting is enabled we synchronously reparse the whole source code tree and re-highlight entire text.
|
|
35
|
+
This is suboptimal and could result in slowing down of the text update but is adequate for relatively small files.
|
|
36
|
+
|
|
37
|
+
## Adding qptte to your code
|
|
38
|
+
|
|
39
|
+
```shell
|
|
40
|
+
uv add git+https://github.com/priimak/qppte.git@v0.5.0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Running demo GUI
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
uv tool run --from git+https://github.com/priimak/qppte.git@v0.5.0 qppte_demo
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Supported additional editor operations
|
|
50
|
+
|
|
51
|
+
* Line or block indent
|
|
52
|
+
* Press `Tab` to indent a line or a selected block by a Tab width number of spaces. Tab is always converted into
|
|
53
|
+
space (default 4).
|
|
54
|
+
* Undo and Redo
|
|
55
|
+
* `Ctrl-Z` and `Ctrl-R` for undo and redo respectively.
|
|
56
|
+
* Unindent line or block
|
|
57
|
+
* Press `Shift-Tab` to unindent a line or a selected block by a Tab width number of spaces.
|
|
58
|
+
* Delete line or block.
|
|
59
|
+
* `Ctrl-Y` will delete a line or all lines in a selected block.
|
|
60
|
+
* Duplicate line
|
|
61
|
+
* `Ctrl-D` will duplicate current line and move cursor down.
|
|
62
|
+
* Move line up and down
|
|
63
|
+
* `Ctrl-Shift-ArrowUp` and `Ctrl-Shift-ArrowDown` will move current line up and down respectively.
|
|
64
|
+
* Toggle commenting line or block.
|
|
65
|
+
* `Ctrl-/` will toggle commenting out a line or all lines included in the selected block.
|
|
66
|
+
* Increment/decrement font size
|
|
67
|
+
* `Ctrl-+` will increase font size. `Ctrl+_` decrease.
|
|
68
|
+
|
|
69
|
+
## Using QPythonPlainTextEdit in your code.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from qppte import QPythonPlainTextEdit
|
|
73
|
+
from PySide6.QtWidgets import QMainWindow, QVBoxLayout, QWidget
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TextEditorWindow(QMainWindow):
|
|
77
|
+
def __init__(self):
|
|
78
|
+
super().__init__()
|
|
79
|
+
|
|
80
|
+
root_panel = QWidget()
|
|
81
|
+
|
|
82
|
+
layout = QVBoxLayout()
|
|
83
|
+
root_panel.setLayout(layout)
|
|
84
|
+
|
|
85
|
+
code_editor = QPythonPlainTextEdit()
|
|
86
|
+
layout.addWidget(code_editor)
|
|
87
|
+
|
|
88
|
+
self.setCentralWidget(root_panel)
|
|
89
|
+
|
|
90
|
+
...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Methods specific to QPythonPlainTextEdit
|
|
94
|
+
|
|
95
|
+
* `getTabWidth() -> int` - returns number of spaces to be used when Tab is pressed on the keyboard
|
|
96
|
+
* `setTabWidth(tabWidthSpaces: int)` - sets number of spaces to be used when Tab is pressed on the keyboard. Will not
|
|
97
|
+
affect already entered text/code in the editor.
|
|
98
|
+
* `getHighlightStyle() -> str` - returns current name of highlight style.
|
|
99
|
+
* `setHighlightStyle(highlightStyle: str)` - sets highlight style to a new one.
|
|
100
|
+
* `listAvailableHighlightStyles() -> list[str]` - returns list of available highlight styles.
|
|
101
|
+
* `setEnableSyntaxHighlighting(enableSyntaxHighlighting: bool)` - enables or disables syntax highlighting.
|
|
102
|
+
* `recordStateForUndoOperation()` - method to be used when extending `QPythonPlainTextEdit`
|
|
103
|
+
|
|
104
|
+
## Adding and modifying new custom highlight styles.
|
|
105
|
+
|
|
106
|
+
TBD
|
|
107
|
+
|
|
108
|
+
## Changing keyboard shortcuts
|
|
109
|
+
|
|
110
|
+
TBD
|
qppte-0.7.0/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
QPPTE - QPythonPlainTextEdit
|
|
2
|
+
============================
|
|
3
|
+
|
|
4
|
+
A poor-main embeddable code editor for python. Written in Python with [PySide6](https://pypi.org/project/PySide6/)
|
|
5
|
+
it is created with intent to be used within larger application where code editor is required. For example
|
|
6
|
+
if you application supports plugins in Python and you want to give users ability to create and edit such plugins, then
|
|
7
|
+
you can make this library a dependency in your project and embed this editor.
|
|
8
|
+
|
|
9
|
+
More information can be found [here](https://brainless-software.github.io/qppte/).
|
|
10
|
+
|
|
11
|
+
A poor-main text editor for editing and viewing Python code to be used with [PySide6](https://pypi.org/project/PySide6/)
|
|
12
|
+
library. It supports basic syntax highlighting using [tree-sitter](https://github.com/tree-sitter/py-tree-sitter) and
|
|
13
|
+
keystrokes common to code editors. Highlighting styles can be customized and expanded. Likewise, keystrokes can be
|
|
14
|
+
customized by the user.
|
|
15
|
+
|
|
16
|
+
When syntax highlighting is enabled we synchronously reparse the whole source code tree and re-highlight entire text.
|
|
17
|
+
This is suboptimal and could result in slowing down of the text update but is adequate for relatively small files.
|
|
18
|
+
|
|
19
|
+
## Adding qptte to your code
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
uv add git+https://github.com/priimak/qppte.git@v0.5.0
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Running demo GUI
|
|
26
|
+
|
|
27
|
+
```shell
|
|
28
|
+
uv tool run --from git+https://github.com/priimak/qppte.git@v0.5.0 qppte_demo
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Supported additional editor operations
|
|
32
|
+
|
|
33
|
+
* Line or block indent
|
|
34
|
+
* Press `Tab` to indent a line or a selected block by a Tab width number of spaces. Tab is always converted into
|
|
35
|
+
space (default 4).
|
|
36
|
+
* Undo and Redo
|
|
37
|
+
* `Ctrl-Z` and `Ctrl-R` for undo and redo respectively.
|
|
38
|
+
* Unindent line or block
|
|
39
|
+
* Press `Shift-Tab` to unindent a line or a selected block by a Tab width number of spaces.
|
|
40
|
+
* Delete line or block.
|
|
41
|
+
* `Ctrl-Y` will delete a line or all lines in a selected block.
|
|
42
|
+
* Duplicate line
|
|
43
|
+
* `Ctrl-D` will duplicate current line and move cursor down.
|
|
44
|
+
* Move line up and down
|
|
45
|
+
* `Ctrl-Shift-ArrowUp` and `Ctrl-Shift-ArrowDown` will move current line up and down respectively.
|
|
46
|
+
* Toggle commenting line or block.
|
|
47
|
+
* `Ctrl-/` will toggle commenting out a line or all lines included in the selected block.
|
|
48
|
+
* Increment/decrement font size
|
|
49
|
+
* `Ctrl-+` will increase font size. `Ctrl+_` decrease.
|
|
50
|
+
|
|
51
|
+
## Using QPythonPlainTextEdit in your code.
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from qppte import QPythonPlainTextEdit
|
|
55
|
+
from PySide6.QtWidgets import QMainWindow, QVBoxLayout, QWidget
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TextEditorWindow(QMainWindow):
|
|
59
|
+
def __init__(self):
|
|
60
|
+
super().__init__()
|
|
61
|
+
|
|
62
|
+
root_panel = QWidget()
|
|
63
|
+
|
|
64
|
+
layout = QVBoxLayout()
|
|
65
|
+
root_panel.setLayout(layout)
|
|
66
|
+
|
|
67
|
+
code_editor = QPythonPlainTextEdit()
|
|
68
|
+
layout.addWidget(code_editor)
|
|
69
|
+
|
|
70
|
+
self.setCentralWidget(root_panel)
|
|
71
|
+
|
|
72
|
+
...
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Methods specific to QPythonPlainTextEdit
|
|
76
|
+
|
|
77
|
+
* `getTabWidth() -> int` - returns number of spaces to be used when Tab is pressed on the keyboard
|
|
78
|
+
* `setTabWidth(tabWidthSpaces: int)` - sets number of spaces to be used when Tab is pressed on the keyboard. Will not
|
|
79
|
+
affect already entered text/code in the editor.
|
|
80
|
+
* `getHighlightStyle() -> str` - returns current name of highlight style.
|
|
81
|
+
* `setHighlightStyle(highlightStyle: str)` - sets highlight style to a new one.
|
|
82
|
+
* `listAvailableHighlightStyles() -> list[str]` - returns list of available highlight styles.
|
|
83
|
+
* `setEnableSyntaxHighlighting(enableSyntaxHighlighting: bool)` - enables or disables syntax highlighting.
|
|
84
|
+
* `recordStateForUndoOperation()` - method to be used when extending `QPythonPlainTextEdit`
|
|
85
|
+
|
|
86
|
+
## Adding and modifying new custom highlight styles.
|
|
87
|
+
|
|
88
|
+
TBD
|
|
89
|
+
|
|
90
|
+
## Changing keyboard shortcuts
|
|
91
|
+
|
|
92
|
+
TBD
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "qppte"
|
|
3
|
+
version="0.7.0"
|
|
4
|
+
description = "Embeddable QPlainTextEdit with Python syntax highlighting"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Dmitri Priimak", email = "priimak@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pyside6>=6.11.1",
|
|
17
|
+
"tree-sitter>=0.24,!=0.26.0",
|
|
18
|
+
"tree-sitter-python>=0.25.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://brainless-software.github.io/qppte"
|
|
23
|
+
Repository = "https://github.com/Brainless-Software/qppte"
|
|
24
|
+
|
|
25
|
+
[project.scripts]
|
|
26
|
+
qppte_demo = "qppte.example_gui:main"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["setuptools", "wheel", "setuptools-git-versioning"]
|
|
30
|
+
build-backend = "setuptools.build_meta"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools-git-versioning]
|
|
33
|
+
enabled = true
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.ruff]
|
|
39
|
+
line-length = 120
|
|
40
|
+
|
|
41
|
+
[tool.ruff.lint]
|
|
42
|
+
select = [
|
|
43
|
+
"F", # Pyflakes
|
|
44
|
+
"E", # pycodestyle
|
|
45
|
+
"W", # pycodestyle
|
|
46
|
+
"B", # flake8-bugbear
|
|
47
|
+
"I", # isort
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[dependency-groups]
|
|
51
|
+
dev = [
|
|
52
|
+
"mkdocs>=1.6.1",
|
|
53
|
+
"mkdocs-material>=9.7.7",
|
|
54
|
+
"ruff>=0.16.6",
|
|
55
|
+
]
|
qppte-0.7.0/setup.cfg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
from .action_trigger import DEFAULT_ACTION_TRIGGERS, ActionTrigger
|
|
2
|
+
from .qpythonplaintextedit import QPythonPlainTextEdit, Settings
|
|
3
|
+
from .qpythonplaintextsettings import QPythonPlainTextSettings, QPythonPlainTextSettingsDialog
|
|
4
|
+
from .qpythonplaintextwidget import QPythonPlainTextWidget
|
|
5
|
+
from .style import DEFAULT_STYLES, TextCharFormat
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from functools import cache, reduce
|
|
2
|
+
from typing import NamedTuple
|
|
3
|
+
|
|
4
|
+
from PySide6 import QtCore
|
|
5
|
+
from PySide6.QtGui import QKeyEvent, Qt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ActionTrigger(NamedTuple):
|
|
9
|
+
key: int
|
|
10
|
+
modifiers: tuple[int]
|
|
11
|
+
|
|
12
|
+
@cache
|
|
13
|
+
def get_modifiers(self) -> int:
|
|
14
|
+
return reduce(lambda acc, m: acc | m, self.modifiers, QtCore.Qt.KeyboardModifier.NoModifier)
|
|
15
|
+
|
|
16
|
+
def match(self, event: QKeyEvent) -> bool:
|
|
17
|
+
if event.key() == self.key and (self.modifiers == [] or self.get_modifiers() == event.modifiers()):
|
|
18
|
+
return True
|
|
19
|
+
else:
|
|
20
|
+
return False
|
|
21
|
+
|
|
22
|
+
@cache
|
|
23
|
+
def getQKeyCombination(self) -> QtCore.QKeyCombination:
|
|
24
|
+
return QtCore.QKeyCombination(self.get_modifiers(), self.key)
|
|
25
|
+
|
|
26
|
+
@cache
|
|
27
|
+
def getQKeyEvent(self) -> QKeyEvent:
|
|
28
|
+
c = self.getQKeyCombination()
|
|
29
|
+
return QKeyEvent(QtCore.QEvent.Type.KeyPress, c.key(), c.keyboardModifiers())
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
DEFAULT_ACTION_TRIGGERS: dict[str, ActionTrigger] = {
|
|
33
|
+
"indent_block": ActionTrigger(Qt.Key.Key_Tab, tuple()),
|
|
34
|
+
"clear_selection": ActionTrigger(Qt.Key.Key_Escape, tuple()),
|
|
35
|
+
"backspace": ActionTrigger(Qt.Key.Key_Backspace, tuple()),
|
|
36
|
+
"new_line_enter": ActionTrigger(Qt.Key.Key_Enter, tuple()),
|
|
37
|
+
"new_line_return": ActionTrigger(Qt.Key.Key_Return, tuple()),
|
|
38
|
+
"select_all": ActionTrigger(Qt.Key.Key_A, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
39
|
+
"cut": ActionTrigger(Qt.Key.Key_X, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
40
|
+
"copy": ActionTrigger(Qt.Key.Key_C, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
41
|
+
"paste": ActionTrigger(Qt.Key.Key_V, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
42
|
+
"undo": ActionTrigger(Qt.Key.Key_Z, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
43
|
+
"redo": ActionTrigger(Qt.Key.Key_R, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
44
|
+
"unindent": ActionTrigger(Qt.Key.Key_Backtab, (QtCore.Qt.KeyboardModifier.ShiftModifier,)),
|
|
45
|
+
"delete_lines": ActionTrigger(Qt.Key.Key_Y, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
46
|
+
"goto_line": ActionTrigger(Qt.Key.Key_G, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
47
|
+
"move_line_up": ActionTrigger(
|
|
48
|
+
Qt.Key.Key_Up, (QtCore.Qt.KeyboardModifier.ControlModifier, QtCore.Qt.KeyboardModifier.ShiftModifier)
|
|
49
|
+
),
|
|
50
|
+
"move_line_down": ActionTrigger(
|
|
51
|
+
Qt.Key.Key_Down, (QtCore.Qt.KeyboardModifier.ControlModifier, QtCore.Qt.KeyboardModifier.ShiftModifier)
|
|
52
|
+
),
|
|
53
|
+
"duplicate_line": ActionTrigger(Qt.Key.Key_D, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
54
|
+
"toggle_comment_block": ActionTrigger(Qt.Key.Key_Slash, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
55
|
+
"join_two_lines": ActionTrigger(
|
|
56
|
+
Qt.Key.Key_J, (QtCore.Qt.KeyboardModifier.ControlModifier, QtCore.Qt.KeyboardModifier.ShiftModifier)
|
|
57
|
+
),
|
|
58
|
+
"search": ActionTrigger(Qt.Key.Key_F, (QtCore.Qt.KeyboardModifier.ControlModifier,)),
|
|
59
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from PySide6.QtGui import QAction
|
|
5
|
+
from PySide6.QtWidgets import (
|
|
6
|
+
QApplication,
|
|
7
|
+
QFileDialog,
|
|
8
|
+
QMainWindow,
|
|
9
|
+
QMenu,
|
|
10
|
+
QMenuBar,
|
|
11
|
+
QVBoxLayout,
|
|
12
|
+
QWidget,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from qppte.qpythonplaintextsettings import QPythonPlainTextSettingsDialog
|
|
16
|
+
from qppte.qpythonplaintextwidget import QPythonPlainTextWidget
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TextEditorWindow(QMainWindow):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.setWindowTitle("QPythonPlainTextEdit Demo")
|
|
23
|
+
self.setGeometry(100, 100, 800, 600)
|
|
24
|
+
|
|
25
|
+
text_widget = QPythonPlainTextWidget()
|
|
26
|
+
text_widget.editor.setHighlightStyle("Light")
|
|
27
|
+
text_widget.editor.enableLineNumbers(True)
|
|
28
|
+
search_action = QAction("Search", self)
|
|
29
|
+
search_action.setShortcut(text_widget.editor.actionTriggers["search"].getQKeyCombination())
|
|
30
|
+
search_action.triggered.connect(text_widget.editor.startSearch)
|
|
31
|
+
self.addAction(search_action)
|
|
32
|
+
|
|
33
|
+
root_panel = QWidget()
|
|
34
|
+
layout = QVBoxLayout()
|
|
35
|
+
root_panel.setLayout(layout)
|
|
36
|
+
layout.addWidget(text_widget)
|
|
37
|
+
|
|
38
|
+
self.setCentralWidget(root_panel)
|
|
39
|
+
|
|
40
|
+
self.sample_text = """\"\"\" Module docstring \"\"\"
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def foo() -> None:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
@property(x=7)
|
|
47
|
+
def moo() -> None:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
for i in [1, 3, 5, 7, 11]:
|
|
51
|
+
print(f"i -> {i}")
|
|
52
|
+
|
|
53
|
+
class A:
|
|
54
|
+
\"\"\" Class A \"\"\"
|
|
55
|
+
def __init__(self):
|
|
56
|
+
self.x = 1 # initializing x to 1
|
|
57
|
+
|
|
58
|
+
def __repr__(self, /) -> str:
|
|
59
|
+
\"\"\" String representation of class A \"\"\"
|
|
60
|
+
return f"Instance of class {self.__class__.__name__}({self.a})"
|
|
61
|
+
"""
|
|
62
|
+
text_widget.editor.setPlainText(self.sample_text)
|
|
63
|
+
|
|
64
|
+
menu_bar = QMenuBar()
|
|
65
|
+
|
|
66
|
+
def open_file():
|
|
67
|
+
file_name, _ = QFileDialog.getOpenFileName(
|
|
68
|
+
self, caption="Import project from file", dir=str(Path.home()), filter="*.py"
|
|
69
|
+
)
|
|
70
|
+
if file_name != "":
|
|
71
|
+
text_widget.editor.setPlainText(Path(file_name).read_text())
|
|
72
|
+
|
|
73
|
+
file_menu = QMenu("&File", menu_bar)
|
|
74
|
+
file_menu.addAction("&Open", open_file)
|
|
75
|
+
file_menu.addSeparator()
|
|
76
|
+
file_menu.addAction(
|
|
77
|
+
"&Settings",
|
|
78
|
+
lambda: QPythonPlainTextSettingsDialog(self, text_widget.editor.settings, text_widget.editor).exec(),
|
|
79
|
+
)
|
|
80
|
+
file_menu.addSeparator()
|
|
81
|
+
file_menu.addAction("&Quit", self.close)
|
|
82
|
+
menu_bar.addMenu(file_menu)
|
|
83
|
+
|
|
84
|
+
edit_menu = QMenu("&Edit", menu_bar)
|
|
85
|
+
edit_menu.addAction(
|
|
86
|
+
"&Undo", lambda: text_widget.editor.keyPressEvent(text_widget.editor.actionTriggers["undo"].getQKeyEvent())
|
|
87
|
+
).setShortcut(text_widget.editor.actionTriggers["undo"].getQKeyCombination())
|
|
88
|
+
edit_menu.addAction(
|
|
89
|
+
"&Redo", lambda: text_widget.editor.keyPressEvent(text_widget.editor.actionTriggers["redo"].getQKeyEvent())
|
|
90
|
+
).setShortcut(text_widget.editor.actionTriggers["redo"].getQKeyCombination())
|
|
91
|
+
edit_menu.addSeparator()
|
|
92
|
+
menu_bar.addMenu(edit_menu)
|
|
93
|
+
|
|
94
|
+
self.setMenuBar(menu_bar)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def main():
|
|
98
|
+
app = QApplication(sys.argv)
|
|
99
|
+
window = TextEditorWindow()
|
|
100
|
+
window.show()
|
|
101
|
+
sys.exit(app.exec())
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if __name__ == "__main__":
|
|
105
|
+
main()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from PySide6.QtCore import QLine, QRect, QRectF
|
|
2
|
+
from PySide6.QtGui import QColor, QFontMetrics, QPainter, QPaintEvent, QResizeEvent, Qt
|
|
3
|
+
from PySide6.QtWidgets import QWidget
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LineNumberPanel(QWidget):
|
|
7
|
+
___current_line_number: int | None = None
|
|
8
|
+
|
|
9
|
+
def __init__(self, editor_parent):
|
|
10
|
+
super().__init__(editor_parent)
|
|
11
|
+
self.editor_parent = editor_parent
|
|
12
|
+
self.setContentsMargins(0, 0, 0, 0)
|
|
13
|
+
self.editor_parent.signalCursorMovedLine.connect(self.on_cursor_moved_line)
|
|
14
|
+
|
|
15
|
+
def sizeHint(self):
|
|
16
|
+
return self.editor_parent.sizeHint()
|
|
17
|
+
|
|
18
|
+
def resizeEvent(self, event: QResizeEvent) -> None:
|
|
19
|
+
super().resizeEvent(event)
|
|
20
|
+
r: QRect = self.editor_parent.geometry()
|
|
21
|
+
r.setLeft(1)
|
|
22
|
+
r.setTop(1)
|
|
23
|
+
r.setWidth(self.editor_parent._lineNumberPanelWidth)
|
|
24
|
+
r.setHeight(self.editor_parent.viewport().geometry().height())
|
|
25
|
+
self.setGeometry(r)
|
|
26
|
+
|
|
27
|
+
def paintEvent(self, event: QPaintEvent) -> None:
|
|
28
|
+
super().paintEvent(event)
|
|
29
|
+
painter = QPainter(self)
|
|
30
|
+
event_rect = event.rect()
|
|
31
|
+
painter.fillRect(event_rect, self.editor_parent.getBackgroundColor())
|
|
32
|
+
|
|
33
|
+
painter.setPen(self.editor_parent.getLineNumberColor())
|
|
34
|
+
block = self.editor_parent.firstVisibleBlock()
|
|
35
|
+
bounding_rect_of_block: QRectF = self.editor_parent.blockBoundingGeometry(block)
|
|
36
|
+
translated_bounding_rect_of_block: QRectF = bounding_rect_of_block.translated(
|
|
37
|
+
self.editor_parent.contentOffset()
|
|
38
|
+
)
|
|
39
|
+
top = translated_bounding_rect_of_block.top()
|
|
40
|
+
bottom = top + self.editor_parent.blockBoundingRect(block).height()
|
|
41
|
+
|
|
42
|
+
height = QFontMetrics(self.font()).height()
|
|
43
|
+
while block.isValid() and (top <= event_rect.bottom()):
|
|
44
|
+
block_number = block.blockNumber()
|
|
45
|
+
if block.isVisible() and (bottom >= event_rect.top()):
|
|
46
|
+
if block_number == self.___current_line_number:
|
|
47
|
+
painter.fillRect(
|
|
48
|
+
QRectF(0, top, self.width(), height), self.editor_parent.getCurrentLineBackgroundColor()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
painter.drawText(
|
|
52
|
+
5,
|
|
53
|
+
int(top),
|
|
54
|
+
self.width(),
|
|
55
|
+
height,
|
|
56
|
+
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
|
|
57
|
+
f"{block_number + 1} ",
|
|
58
|
+
)
|
|
59
|
+
block = block.next()
|
|
60
|
+
top = bottom
|
|
61
|
+
bottom = top + self.editor_parent.blockBoundingRect(block).height()
|
|
62
|
+
|
|
63
|
+
line_color = QColor(self.editor_parent.getLineNumberColor())
|
|
64
|
+
line_color.setAlpha(80)
|
|
65
|
+
painter.setPen(line_color)
|
|
66
|
+
painter.drawLine(
|
|
67
|
+
QLine(
|
|
68
|
+
self.editor_parent._lineNumberPanelWidth - 2,
|
|
69
|
+
0,
|
|
70
|
+
self.editor_parent._lineNumberPanelWidth - 2,
|
|
71
|
+
event_rect.height(),
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def on_cursor_moved_line(self, line_number: int):
|
|
76
|
+
self.___current_line_number = line_number
|
|
77
|
+
self.update()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import tree_sitter_python
|
|
2
|
+
from tree_sitter import Language, Parser, Query
|
|
3
|
+
|
|
4
|
+
PY_LANGUAGE = Language(tree_sitter_python.language())
|
|
5
|
+
PYTHON_PARSER = Parser(PY_LANGUAGE)
|
|
6
|
+
HIGHLIGHTER_QUERY = Query(
|
|
7
|
+
PY_LANGUAGE,
|
|
8
|
+
"""
|
|
9
|
+
(function_definition
|
|
10
|
+
name: (identifier) @function_definition)
|
|
11
|
+
|
|
12
|
+
(function_definition (identifier) @special_function (#any-of? @special_function
|
|
13
|
+
"__init__" "__new__" "__setattr__"
|
|
14
|
+
"__delattr__" "__eq__" "__ne__"
|
|
15
|
+
"__str__" "__hash__" "__format__"
|
|
16
|
+
"__getattribute__" "__sizeof__" "__dir__"
|
|
17
|
+
"__repr__"))
|
|
18
|
+
("." (identifier) @special_function (#any-of? @special_function
|
|
19
|
+
"__init__" "__new__" "__setattr__" "__delattr__" "__eq__"
|
|
20
|
+
"__ne__" "__str__" "__hash__" "__format__" "__getattribute__"
|
|
21
|
+
"__sizeof__" "__dir__" "__repr__"))
|
|
22
|
+
|
|
23
|
+
(type) @type
|
|
24
|
+
|
|
25
|
+
(class_definition
|
|
26
|
+
name: (identifier) @class_definition_name)
|
|
27
|
+
|
|
28
|
+
(call (identifier) @function_call)
|
|
29
|
+
(decorator "@" (identifier)) @decorator
|
|
30
|
+
(decorator "@" (call (identifier) @decorator))
|
|
31
|
+
(decorator ("@" @decorator))
|
|
32
|
+
|
|
33
|
+
(string_start) @string
|
|
34
|
+
(string_content) @string
|
|
35
|
+
(string_end) @string
|
|
36
|
+
|
|
37
|
+
["def" "return" "if" "else" "class" "assert" "async" "await" "break" "continue" "del" "elif"
|
|
38
|
+
"else" "except" "finally" "for" "global" "lambda" "pass" "raise" "nonlocal" "return" "try"
|
|
39
|
+
"while" "yield" "as" "with" "import" "from" "match" "case" "in"] @keyword
|
|
40
|
+
|
|
41
|
+
(true) @keyword
|
|
42
|
+
(false) @keyword
|
|
43
|
+
|
|
44
|
+
(integer) @number
|
|
45
|
+
(float) @number
|
|
46
|
+
|
|
47
|
+
(keyword_argument (identifier) @keyword_argument)
|
|
48
|
+
|
|
49
|
+
((identifier) @self (#eq? @self "self"))
|
|
50
|
+
|
|
51
|
+
(comment) @line_comment
|
|
52
|
+
|
|
53
|
+
(function_definition (block . (expression_statement (string) @docstring)))
|
|
54
|
+
(class_definition (block . (expression_statement (string) @docstring)))
|
|
55
|
+
(module . (expression_statement (string) @docstring))
|
|
56
|
+
""",
|
|
57
|
+
)
|