bracket_matcher.py
101 lines
| 3.7 KiB
| text/x-python
|
PythonLexer
epatters
|
r2894 | """ Provides bracket matching for Q[Plain]TextEdit widgets. | ||
""" | ||||
# System library imports | ||||
from PyQt4 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 | ||||
#-------------------------------------------------------------------------- | ||||
epatters
|
r2982 | def __init__(self, text_edit): | ||
epatters
|
r2894 | """ Create a call tip manager that is attached to the specified Qt | ||
text edit widget. | ||||
""" | ||||
epatters
|
r2982 | assert isinstance(text_edit, (QtGui.QTextEdit, QtGui.QPlainTextEdit)) | ||
super(BracketMatcher, self).__init__() | ||||
epatters
|
r2894 | |||
# The format to apply to matching brackets. | ||||
self.format = QtGui.QTextCharFormat() | ||||
self.format.setBackground(QtGui.QColor('silver')) | ||||
epatters
|
r2982 | self._text_edit = text_edit | ||
text_edit.cursorPositionChanged.connect(self._cursor_position_changed) | ||||
epatters
|
r2894 | |||
#-------------------------------------------------------------------------- | ||||
# 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. | ||||
epatters
|
r2982 | document = self._text_edit.document() | ||
epatters
|
r2894 | qchar = document.characterAt(position) | ||
start_char = qchar.toAscii() | ||||
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. | ||||
depth = 0 | ||||
while position >= 0 and position < document.characterCount(): | ||||
char = qchar.toAscii() | ||||
if char == start_char: | ||||
depth += 1 | ||||
elif char == search_char: | ||||
depth -= 1 | ||||
if depth == 0: | ||||
break | ||||
position += increment | ||||
qchar = document.characterAt(position) | ||||
else: | ||||
position = -1 | ||||
return position | ||||
def _selection_for_character(self, position): | ||||
""" Convenience method for selecting a character. | ||||
""" | ||||
selection = QtGui.QTextEdit.ExtraSelection() | ||||
epatters
|
r2982 | cursor = self._text_edit.textCursor() | ||
epatters
|
r2894 | cursor.setPosition(position) | ||
cursor.movePosition(QtGui.QTextCursor.NextCharacter, | ||||
QtGui.QTextCursor.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. | ||||
epatters
|
r2982 | self._text_edit.setExtraSelections([]) | ||
epatters
|
r2894 | |||
# Attempt to match a bracket for the new cursor position. | ||||
epatters
|
r2982 | cursor = self._text_edit.textCursor() | ||
epatters
|
r2894 | 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) ] | ||||
epatters
|
r2982 | self._text_edit.setExtraSelections(extra_selections) | ||