File: bracket_matcher.py

package info (click to toggle)
python-pyface 8.0.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 13,944 kB
  • sloc: python: 54,107; makefile: 82
file content (117 lines) | stat: -rw-r--r-- 4,284 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" Provides bracket matching for Q[Plain]TextEdit widgets.
"""


from pyface.qt import QtCore, QtGui


class BracketMatcher(QtCore.QObject):
    """ Matches square brackets, braces, and parentheses based on cursor
        position.
    """

    # Protected class variables.
    _opening_map = {"(": ")", "{": "}", "[": "]"}
    _closing_map = {")": "(", "}": "{", "]": "["}

    # --------------------------------------------------------------------------
    # 'QObject' interface
    # --------------------------------------------------------------------------

    def __init__(self, text_edit):
        """ Create a call tip manager that is attached to the specified Qt
            text edit widget.
        """
        assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
        super().__init__()

        # The format to apply to matching brackets.
        self.format = QtGui.QTextCharFormat()
        self.format.setBackground(QtGui.QColor("silver"))

        self._text_edit = text_edit
        text_edit.cursorPositionChanged.connect(self._cursor_position_changed)

    def _remove_event_listeners(self):
        self._text_edit.cursorPositionChanged.disconnect(
            self._cursor_position_changed
        )

    # --------------------------------------------------------------------------
    # Protected interface
    # --------------------------------------------------------------------------

    def _find_match(self, position):
        """ Given a valid position in the text document, try to find the
            position of the matching bracket. Returns -1 if unsuccessful.
        """
        # Decide what character to search for and what direction to search in.
        document = self._text_edit.document()
        start_char = document.characterAt(position)
        search_char = self._opening_map.get(start_char)
        if search_char:
            increment = 1
        else:
            search_char = self._closing_map.get(start_char)
            if search_char:
                increment = -1
            else:
                return -1

        # Search for the character.
        char = start_char
        depth = 0
        while position >= 0 and position < document.characterCount():
            if char == start_char:
                depth += 1
            elif char == search_char:
                depth -= 1
            if depth == 0:
                break
            position += increment
            char = document.characterAt(position)
        else:
            position = -1
        return position

    def _selection_for_character(self, position):
        """ Convenience method for selecting a character.
        """
        selection = QtGui.QTextEdit.ExtraSelection()
        cursor = self._text_edit.textCursor()
        cursor.setPosition(position)
        cursor.movePosition(
            QtGui.QTextCursor.MoveOperation.NextCharacter, QtGui.QTextCursor.MoveMode.KeepAnchor
        )
        selection.cursor = cursor
        selection.format = self.format
        return selection

    # Signal handlers ----------------------------------------------------

    def _cursor_position_changed(self):
        """ Updates the document formatting based on the new cursor position.
        """
        # Clear out the old formatting.
        self._text_edit.setExtraSelections([])

        # Attempt to match a bracket for the new cursor position.
        cursor = self._text_edit.textCursor()
        if not cursor.hasSelection():
            position = cursor.position() - 1
            match_position = self._find_match(position)
            if match_position != -1:
                extra_selections = [
                    self._selection_for_character(pos)
                    for pos in (position, match_position)
                ]
                self._text_edit.setExtraSelections(extra_selections)