##// END OF EJS Templates
First cut at a generic bracket matcher for Q[Plain]TextEdits.
epatters -
Show More
@@ -0,0 +1,101 b''
1 """ Provides bracket matching for Q[Plain]TextEdit widgets.
2 """
3
4 # System library imports
5 from PyQt4 import QtCore, QtGui
6
7
8 class BracketMatcher(QtCore.QObject):
9 """ Matches square brackets, braces, and parentheses based on cursor
10 position.
11 """
12
13 # Protected class variables.
14 _opening_map = { '(':')', '{':'}', '[':']' }
15 _closing_map = { ')':'(', '}':'{', ']':'[' }
16
17 #--------------------------------------------------------------------------
18 # 'QObject' interface
19 #--------------------------------------------------------------------------
20
21 def __init__(self, parent, multiline=True):
22 """ Create a call tip manager that is attached to the specified Qt
23 text edit widget.
24 """
25 assert isinstance(parent, (QtGui.QTextEdit, QtGui.QPlainTextEdit))
26 QtCore.QObject.__init__(self, parent)
27
28 # The format to apply to matching brackets.
29 self.format = QtGui.QTextCharFormat()
30 self.format.setBackground(QtGui.QColor('silver'))
31
32 parent.cursorPositionChanged.connect(self._cursor_position_changed)
33
34 #--------------------------------------------------------------------------
35 # Protected interface
36 #--------------------------------------------------------------------------
37
38 def _find_match(self, position):
39 """ Given a valid position in the text document, try to find the
40 position of the matching bracket. Returns -1 if unsuccessful.
41 """
42 # Decide what character to search for and what direction to search in.
43 document = self.parent().document()
44 qchar = document.characterAt(position)
45 start_char = qchar.toAscii()
46 search_char = self._opening_map.get(start_char)
47 if search_char:
48 increment = 1
49 else:
50 search_char = self._closing_map.get(start_char)
51 if search_char:
52 increment = -1
53 else:
54 return -1
55
56 # Search for the character.
57 depth = 0
58 while position >= 0 and position < document.characterCount():
59 char = qchar.toAscii()
60 if char == start_char:
61 depth += 1
62 elif char == search_char:
63 depth -= 1
64 if depth == 0:
65 break
66 position += increment
67 qchar = document.characterAt(position)
68 else:
69 position = -1
70 return position
71
72 def _selection_for_character(self, position):
73 """ Convenience method for selecting a character.
74 """
75 selection = QtGui.QTextEdit.ExtraSelection()
76 cursor = self.parent().textCursor()
77 cursor.setPosition(position)
78 cursor.movePosition(QtGui.QTextCursor.NextCharacter,
79 QtGui.QTextCursor.KeepAnchor)
80 selection.cursor = cursor
81 selection.format = self.format
82 return selection
83
84 #------ Signal handlers ----------------------------------------------------
85
86 def _cursor_position_changed(self):
87 """ Updates the document formatting based on the new cursor position.
88 """
89 # Clear out the old formatting.
90 text_edit = self.parent()
91 text_edit.setExtraSelections([])
92
93 # Attempt to match a bracket for the new cursor position.
94 cursor = text_edit.textCursor()
95 if not cursor.hasSelection():
96 position = cursor.position() - 1
97 match_position = self._find_match(position)
98 if match_position != -1:
99 extra_selections = [ self._selection_for_character(pos)
100 for pos in (position, match_position) ]
101 text_edit.setExtraSelections(extra_selections)
@@ -9,7 +9,8 b' from PyQt4 import QtCore, QtGui'
9 9 # Local imports
10 10 from IPython.core.inputsplitter import InputSplitter
11 11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
12 from IPython.utils.traitlets import Bool, Type
12 from IPython.utils.traitlets import Bool
13 from bracket_matcher import BracketMatcher
13 14 from call_tip_widget import CallTipWidget
14 15 from completion_lexer import CompletionLexer
15 16 from console_widget import HistoryConsoleWidget
@@ -88,8 +89,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
88 89 executed = QtCore.pyqtSignal(object)
89 90
90 91 # Protected class variables.
91 _highlighter_class = Type(FrontendHighlighter)
92 _input_splitter_class = Type(InputSplitter)
92 _input_splitter_class = InputSplitter
93 93
94 94 #---------------------------------------------------------------------------
95 95 # 'object' interface
@@ -99,10 +99,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
99 99 super(FrontendWidget, self).__init__(*args, **kw)
100 100
101 101 # FrontendWidget protected variables.
102 self._bracket_matcher = BracketMatcher(self._control)
102 103 self._call_tip_widget = CallTipWidget(self._control)
103 104 self._completion_lexer = CompletionLexer(PythonLexer())
104 105 self._hidden = False
105 self._highlighter = self._highlighter_class(self)
106 self._highlighter = FrontendHighlighter(self)
106 107 self._input_splitter = self._input_splitter_class(input_mode='block')
107 108 self._kernel_manager = None
108 109
@@ -110,14 +110,12 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):'
110 110 elif hasattr(self._lexer, '_saved_state_stack'):
111 111 del self._lexer._saved_state_stack
112 112
113 index = 0
114 113 # Lex the text using Pygments
114 index = 0
115 115 for token, text in self._lexer.get_tokens(qstring):
116 l = len(text)
117 format = self._get_format(token)
118 if format is not None:
119 self.setFormat(index, l, format)
120 index += l
116 length = len(text)
117 self.setFormat(index, length, self._get_format(token))
118 index += length
121 119
122 120 if hasattr(self._lexer, '_saved_state_stack'):
123 121 data = PygmentsBlockUserData(
@@ -185,11 +183,9 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):'
185 183 def _get_format_from_style(self, token, style):
186 184 """ Returns a QTextCharFormat for token by reading a Pygments style.
187 185 """
188 result = None
186 result = QtGui.QTextCharFormat()
189 187 for key, value in style.style_for_token(token).items():
190 188 if value:
191 if result is None:
192 result = QtGui.QTextCharFormat()
193 189 if key == 'color':
194 190 result.setForeground(self._get_brush(value))
195 191 elif key == 'bgcolor':
General Comments 0
You need to be logged in to leave comments. Login now