QCodeSitter 0.1.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.
- QCodeSitter/__init__.py +38 -0
- QCodeSitter/behaviors/__init__.py +115 -0
- QCodeSitter/behaviors/auto_bracket.py +141 -0
- QCodeSitter/behaviors/code_folding.py +530 -0
- QCodeSitter/behaviors/comment_toggle.py +71 -0
- QCodeSitter/behaviors/highlight_matching_brackets.py +181 -0
- QCodeSitter/behaviors/highlight_matching_selection.py +59 -0
- QCodeSitter/behaviors/line_numbers.py +49 -0
- QCodeSitter/behaviors/multi_cursor_paint.py +131 -0
- QCodeSitter/behaviors/overscroll.py +104 -0
- QCodeSitter/behaviors/smart_indent.py +191 -0
- QCodeSitter/behaviors/syntax_highlighting.py +181 -0
- QCodeSitter/behaviors/tab_completion/__init__.py +257 -0
- QCodeSitter/behaviors/tab_completion/popup.py +123 -0
- QCodeSitter/behaviors/tab_completion/providers.py +66 -0
- QCodeSitter/behaviors/tab_completion/types.py +55 -0
- QCodeSitter/code_editor.py +540 -0
- QCodeSitter/cursors/__init__.py +8 -0
- QCodeSitter/cursors/clipboard.py +51 -0
- QCodeSitter/cursors/iterator.py +141 -0
- QCodeSitter/cursors/line_edits.py +128 -0
- QCodeSitter/cursors/multi.py +240 -0
- QCodeSitter/cursors/state.py +55 -0
- QCodeSitter/cursors/tracked.py +97 -0
- QCodeSitter/defaults.py +93 -0
- QCodeSitter/editing_keys.py +150 -0
- QCodeSitter/editor_options.py +246 -0
- QCodeSitter/gui_colors.py +51 -0
- QCodeSitter/gutters.py +141 -0
- QCodeSitter/integrations/__init__.py +1 -0
- QCodeSitter/integrations/preditor_interface.py +390 -0
- QCodeSitter/languages/__init__.py +99 -0
- QCodeSitter/languages/base.py +60 -0
- QCodeSitter/languages/python.py +146 -0
- QCodeSitter/languages/python_highlights.py +444 -0
- QCodeSitter/py.typed +0 -0
- QCodeSitter/queries/__init__.py +73 -0
- QCodeSitter/queries/predicates.py +172 -0
- QCodeSitter/syntax_specs.py +65 -0
- QCodeSitter/themes.py +271 -0
- QCodeSitter/tracked_document.py +107 -0
- QCodeSitter/tree_manager.py +180 -0
- QCodeSitter/utils.py +84 -0
- qcodesitter-0.1.0.dist-info/METADATA +255 -0
- qcodesitter-0.1.0.dist-info/RECORD +49 -0
- qcodesitter-0.1.0.dist-info/WHEEL +5 -0
- qcodesitter-0.1.0.dist-info/entry_points.txt +2 -0
- qcodesitter-0.1.0.dist-info/licenses/LICENSE.txt +7 -0
- qcodesitter-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Collection
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from Qt import QtCore, QtGui, QtWidgets
|
|
7
|
+
|
|
8
|
+
from ...gui_colors import GuiColors, gui_color
|
|
9
|
+
from .types import Completion
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from ...code_editor import CodeEditor
|
|
13
|
+
|
|
14
|
+
ROLE = QtCore.Qt.ItemDataRole.UserRole
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CompletionPopup(QtWidgets.QListWidget):
|
|
18
|
+
"""A list of completions shown under the cursor, without taking focus"""
|
|
19
|
+
|
|
20
|
+
MIN_WIDTH = 200
|
|
21
|
+
MAX_WIDTH = 500
|
|
22
|
+
MAX_HEIGHT = 300
|
|
23
|
+
# Only measure this many items when sizing the popup to its contents
|
|
24
|
+
MEASURED_ITEMS = 20
|
|
25
|
+
|
|
26
|
+
def __init__(self, parent: CodeEditor) -> None:
|
|
27
|
+
super().__init__(parent)
|
|
28
|
+
self.editor = parent
|
|
29
|
+
self.all_completions: list[Completion] = []
|
|
30
|
+
self.current_prefix: str = ""
|
|
31
|
+
|
|
32
|
+
# A Tool window instead of a Popup lets the editor keep receiving key events
|
|
33
|
+
self.setWindowFlags(
|
|
34
|
+
QtCore.Qt.WindowType.Tool
|
|
35
|
+
| QtCore.Qt.WindowType.FramelessWindowHint
|
|
36
|
+
| QtCore.Qt.WindowType.WindowStaysOnTopHint
|
|
37
|
+
)
|
|
38
|
+
self.setAttribute(QtCore.Qt.WidgetAttribute.WA_ShowWithoutActivating)
|
|
39
|
+
self.setFocusPolicy(QtCore.Qt.FocusPolicy.NoFocus)
|
|
40
|
+
self.setAutoFillBackground(True)
|
|
41
|
+
|
|
42
|
+
self.setMinimumWidth(self.MIN_WIDTH)
|
|
43
|
+
self.setMaximumWidth(self.MAX_WIDTH)
|
|
44
|
+
self.setMaximumHeight(self.MAX_HEIGHT)
|
|
45
|
+
self.setVerticalScrollMode(
|
|
46
|
+
QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel
|
|
47
|
+
)
|
|
48
|
+
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
49
|
+
|
|
50
|
+
def set_colors(self, colors: GuiColors) -> None:
|
|
51
|
+
"""Color the popup with the "menu", "fg" and "selection" gui colors"""
|
|
52
|
+
palette = self.palette()
|
|
53
|
+
role = QtGui.QPalette.ColorRole
|
|
54
|
+
for roles, name in (
|
|
55
|
+
((role.Base, role.Window), "menu"),
|
|
56
|
+
((role.Text, role.HighlightedText), "fg"),
|
|
57
|
+
((role.Highlight,), "selection"),
|
|
58
|
+
):
|
|
59
|
+
for color_role in roles:
|
|
60
|
+
palette.setColor(color_role, gui_color(colors, name))
|
|
61
|
+
self.setPalette(palette)
|
|
62
|
+
|
|
63
|
+
def show_completions(
|
|
64
|
+
self, completions: Collection[Completion], prefix: str
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Show a new set of completions, filtered by the prefix"""
|
|
67
|
+
self.all_completions = sorted(
|
|
68
|
+
completions, key=lambda c: (-c.priority, c.text.lower())
|
|
69
|
+
)
|
|
70
|
+
self.update_filter(prefix)
|
|
71
|
+
|
|
72
|
+
def update_filter(self, prefix: str) -> None:
|
|
73
|
+
"""Show the completions that start with a new prefix, without asking the providers"""
|
|
74
|
+
self.current_prefix = prefix
|
|
75
|
+
prefix_lower = prefix.lower()
|
|
76
|
+
self.clear()
|
|
77
|
+
for comp in self.all_completions:
|
|
78
|
+
if comp.text.lower().startswith(prefix_lower) and comp.text != prefix:
|
|
79
|
+
item = QtWidgets.QListWidgetItem(comp.display())
|
|
80
|
+
item.setData(ROLE, comp)
|
|
81
|
+
self.addItem(item)
|
|
82
|
+
|
|
83
|
+
if self.count() == 0:
|
|
84
|
+
self.hide()
|
|
85
|
+
return
|
|
86
|
+
self.setCurrentRow(0)
|
|
87
|
+
self._position_at_cursor()
|
|
88
|
+
self.show()
|
|
89
|
+
|
|
90
|
+
def _position_at_cursor(self) -> None:
|
|
91
|
+
"""Size the popup to its items, and put it below the cursor, or above it
|
|
92
|
+
if there isn't room on the screen"""
|
|
93
|
+
widths = (
|
|
94
|
+
self.fontMetrics().horizontalAdvance(self.item(i).text()) + 30
|
|
95
|
+
for i in range(min(self.count(), self.MEASURED_ITEMS))
|
|
96
|
+
)
|
|
97
|
+
self.setFixedWidth(min(max(self.MIN_WIDTH, *widths), self.MAX_WIDTH))
|
|
98
|
+
self.setFixedHeight(
|
|
99
|
+
min(self.count() * self.sizeHintForRow(0) + 4, self.MAX_HEIGHT)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
cursor_rect = self.editor.cursorRect(self.editor.textCursor())
|
|
103
|
+
popup_pos = self.editor.mapToGlobal(cursor_rect.bottomLeft())
|
|
104
|
+
screen = QtGui.QGuiApplication.screenAt(
|
|
105
|
+
self.editor.mapToGlobal(self.editor.rect().center())
|
|
106
|
+
)
|
|
107
|
+
if screen is None:
|
|
108
|
+
screen = QtGui.QGuiApplication.primaryScreen()
|
|
109
|
+
if popup_pos.y() + self.height() > screen.availableGeometry().bottom():
|
|
110
|
+
popup_pos = self.editor.mapToGlobal(cursor_rect.topLeft())
|
|
111
|
+
popup_pos.setY(popup_pos.y() - self.height())
|
|
112
|
+
self.move(popup_pos)
|
|
113
|
+
|
|
114
|
+
def selected_completion(self) -> Completion | None:
|
|
115
|
+
"""The selected completion, or None when nothing is selected"""
|
|
116
|
+
item = self.currentItem()
|
|
117
|
+
return None if item is None else item.data(ROLE)
|
|
118
|
+
|
|
119
|
+
def offset_current(self, offset: int) -> None:
|
|
120
|
+
"""Move the selection by offset rows, wrapping around the ends"""
|
|
121
|
+
row = (self.currentRow() + offset) % self.count()
|
|
122
|
+
self.setCurrentRow(row)
|
|
123
|
+
self.scrollToItem(self.item(row))
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""The built-in completion providers"""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from ...languages import identifier_query
|
|
9
|
+
from ...queries import query_byte_range
|
|
10
|
+
from .types import Completion, CompletionContext, Provider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IdentifierProvider(Provider):
|
|
14
|
+
"""Completes the names defined in the document
|
|
15
|
+
|
|
16
|
+
The names come from the identifier query of the editor language's LanguageSpec
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
PRIORITY = 3
|
|
20
|
+
|
|
21
|
+
def provide(self, context: CompletionContext) -> set[Completion]:
|
|
22
|
+
tree = self.editor.tree_manager.tree
|
|
23
|
+
if tree is None:
|
|
24
|
+
return set()
|
|
25
|
+
query = identifier_query(tree.language)
|
|
26
|
+
if query is None:
|
|
27
|
+
return set()
|
|
28
|
+
|
|
29
|
+
doc = self.editor.document()
|
|
30
|
+
root = tree.root_node
|
|
31
|
+
captures = query_byte_range(query, root, 0, root.end_byte, doc.node_text)
|
|
32
|
+
identifiers = set()
|
|
33
|
+
for capture_name, nodes in captures.items():
|
|
34
|
+
for node in nodes:
|
|
35
|
+
name = doc.node_text(node)
|
|
36
|
+
if name.isidentifier():
|
|
37
|
+
identifiers.add(Completion(name, capture_name, self.PRIORITY))
|
|
38
|
+
return identifiers
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _kind_of(value: object) -> str:
|
|
42
|
+
if inspect.ismodule(value):
|
|
43
|
+
return "module"
|
|
44
|
+
if isinstance(value, type):
|
|
45
|
+
return "class"
|
|
46
|
+
if callable(value):
|
|
47
|
+
return "function"
|
|
48
|
+
return "variable"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MainPythonProvider(Provider):
|
|
52
|
+
"""Completes the names in the running python's __main__ namespace
|
|
53
|
+
|
|
54
|
+
Useful when the editor is a console for the python process it runs in
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
PRIORITY = 3
|
|
58
|
+
|
|
59
|
+
def provide(self, context: CompletionContext) -> set[Completion]:
|
|
60
|
+
namespace = vars(sys.modules["__main__"])
|
|
61
|
+
prefix = context.prefix.lower()
|
|
62
|
+
return {
|
|
63
|
+
Completion(name, _kind_of(value), self.PRIORITY)
|
|
64
|
+
for name, value in namespace.items()
|
|
65
|
+
if name.lower().startswith(prefix)
|
|
66
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from tree_sitter import Node
|
|
8
|
+
|
|
9
|
+
from ...code_editor import CodeEditor
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Completion:
|
|
14
|
+
"""A single completion result
|
|
15
|
+
|
|
16
|
+
Equal names of the same kind are one completion, whatever their priority
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
text: str # Completion text (e.g., "path")
|
|
20
|
+
kind: str # "function", "class", "variable", "module", etc.
|
|
21
|
+
# For sorting (higher = more important)
|
|
22
|
+
priority: int = field(compare=False)
|
|
23
|
+
|
|
24
|
+
def display(self) -> str:
|
|
25
|
+
return f"{self.text} ({self.kind})"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class CompletionContext:
|
|
30
|
+
"""Where the cursor is, and what it is completing
|
|
31
|
+
|
|
32
|
+
The columns are str indexes into `line`, not Qt positions
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
line_num: int # 0-indexed line number
|
|
36
|
+
index: int # The cursor's column
|
|
37
|
+
prefix: str # The identifier being completed, before the cursor
|
|
38
|
+
start: int # The column the prefix starts at
|
|
39
|
+
line: str # The text of the cursor's line
|
|
40
|
+
node: Node | None # Tree-sitter node at the cursor
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Provider:
|
|
44
|
+
"""A source of completions for TabCompletion"""
|
|
45
|
+
|
|
46
|
+
def __init__(self, editor: CodeEditor) -> None:
|
|
47
|
+
self.editor = editor
|
|
48
|
+
|
|
49
|
+
def provide(self, context: CompletionContext) -> set[Completion]:
|
|
50
|
+
"""The completions for the identifier at the cursor
|
|
51
|
+
|
|
52
|
+
The popup only shows the ones that start with `context.prefix`, so
|
|
53
|
+
returning more is fine
|
|
54
|
+
"""
|
|
55
|
+
raise NotImplementedError("A Provider must override the .provide() method")
|