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.
Files changed (49) hide show
  1. QCodeSitter/__init__.py +38 -0
  2. QCodeSitter/behaviors/__init__.py +115 -0
  3. QCodeSitter/behaviors/auto_bracket.py +141 -0
  4. QCodeSitter/behaviors/code_folding.py +530 -0
  5. QCodeSitter/behaviors/comment_toggle.py +71 -0
  6. QCodeSitter/behaviors/highlight_matching_brackets.py +181 -0
  7. QCodeSitter/behaviors/highlight_matching_selection.py +59 -0
  8. QCodeSitter/behaviors/line_numbers.py +49 -0
  9. QCodeSitter/behaviors/multi_cursor_paint.py +131 -0
  10. QCodeSitter/behaviors/overscroll.py +104 -0
  11. QCodeSitter/behaviors/smart_indent.py +191 -0
  12. QCodeSitter/behaviors/syntax_highlighting.py +181 -0
  13. QCodeSitter/behaviors/tab_completion/__init__.py +257 -0
  14. QCodeSitter/behaviors/tab_completion/popup.py +123 -0
  15. QCodeSitter/behaviors/tab_completion/providers.py +66 -0
  16. QCodeSitter/behaviors/tab_completion/types.py +55 -0
  17. QCodeSitter/code_editor.py +540 -0
  18. QCodeSitter/cursors/__init__.py +8 -0
  19. QCodeSitter/cursors/clipboard.py +51 -0
  20. QCodeSitter/cursors/iterator.py +141 -0
  21. QCodeSitter/cursors/line_edits.py +128 -0
  22. QCodeSitter/cursors/multi.py +240 -0
  23. QCodeSitter/cursors/state.py +55 -0
  24. QCodeSitter/cursors/tracked.py +97 -0
  25. QCodeSitter/defaults.py +93 -0
  26. QCodeSitter/editing_keys.py +150 -0
  27. QCodeSitter/editor_options.py +246 -0
  28. QCodeSitter/gui_colors.py +51 -0
  29. QCodeSitter/gutters.py +141 -0
  30. QCodeSitter/integrations/__init__.py +1 -0
  31. QCodeSitter/integrations/preditor_interface.py +390 -0
  32. QCodeSitter/languages/__init__.py +99 -0
  33. QCodeSitter/languages/base.py +60 -0
  34. QCodeSitter/languages/python.py +146 -0
  35. QCodeSitter/languages/python_highlights.py +444 -0
  36. QCodeSitter/py.typed +0 -0
  37. QCodeSitter/queries/__init__.py +73 -0
  38. QCodeSitter/queries/predicates.py +172 -0
  39. QCodeSitter/syntax_specs.py +65 -0
  40. QCodeSitter/themes.py +271 -0
  41. QCodeSitter/tracked_document.py +107 -0
  42. QCodeSitter/tree_manager.py +180 -0
  43. QCodeSitter/utils.py +84 -0
  44. qcodesitter-0.1.0.dist-info/METADATA +255 -0
  45. qcodesitter-0.1.0.dist-info/RECORD +49 -0
  46. qcodesitter-0.1.0.dist-info/WHEEL +5 -0
  47. qcodesitter-0.1.0.dist-info/entry_points.txt +2 -0
  48. qcodesitter-0.1.0.dist-info/licenses/LICENSE.txt +7 -0
  49. 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")