qppte 0.7.0__py3-none-any.whl
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/__init__.py +5 -0
- qppte/action_trigger.py +59 -0
- qppte/example_gui.py +105 -0
- qppte/line_number_panel.py +77 -0
- qppte/qppte_tree_sitter.py +57 -0
- qppte/qpythonplaintextedit.py +1167 -0
- qppte/qpythonplaintextsettings.py +197 -0
- qppte/qpythonplaintextwidget.py +23 -0
- qppte/style.py +83 -0
- qppte-0.7.0.dist-info/METADATA +110 -0
- qppte-0.7.0.dist-info/RECORD +15 -0
- qppte-0.7.0.dist-info/WHEEL +5 -0
- qppte-0.7.0.dist-info/entry_points.txt +2 -0
- qppte-0.7.0.dist-info/licenses/LICENSE +21 -0
- qppte-0.7.0.dist-info/top_level.txt +1 -0
qppte/__init__.py
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
|
qppte/action_trigger.py
ADDED
|
@@ -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
|
+
}
|
qppte/example_gui.py
ADDED
|
@@ -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
|
+
)
|