##// 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 # Local imports
9 # Local imports
10 from IPython.core.inputsplitter import InputSplitter
10 from IPython.core.inputsplitter import InputSplitter
11 from IPython.frontend.qt.base_frontend_mixin import BaseFrontendMixin
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 from call_tip_widget import CallTipWidget
14 from call_tip_widget import CallTipWidget
14 from completion_lexer import CompletionLexer
15 from completion_lexer import CompletionLexer
15 from console_widget import HistoryConsoleWidget
16 from console_widget import HistoryConsoleWidget
@@ -88,8 +89,7 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
88 executed = QtCore.pyqtSignal(object)
89 executed = QtCore.pyqtSignal(object)
89
90
90 # Protected class variables.
91 # Protected class variables.
91 _highlighter_class = Type(FrontendHighlighter)
92 _input_splitter_class = InputSplitter
92 _input_splitter_class = Type(InputSplitter)
93
93
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 # 'object' interface
95 # 'object' interface
@@ -99,10 +99,11 b' class FrontendWidget(HistoryConsoleWidget, BaseFrontendMixin):'
99 super(FrontendWidget, self).__init__(*args, **kw)
99 super(FrontendWidget, self).__init__(*args, **kw)
100
100
101 # FrontendWidget protected variables.
101 # FrontendWidget protected variables.
102 self._bracket_matcher = BracketMatcher(self._control)
102 self._call_tip_widget = CallTipWidget(self._control)
103 self._call_tip_widget = CallTipWidget(self._control)
103 self._completion_lexer = CompletionLexer(PythonLexer())
104 self._completion_lexer = CompletionLexer(PythonLexer())
104 self._hidden = False
105 self._hidden = False
105 self._highlighter = self._highlighter_class(self)
106 self._highlighter = FrontendHighlighter(self)
106 self._input_splitter = self._input_splitter_class(input_mode='block')
107 self._input_splitter = self._input_splitter_class(input_mode='block')
107 self._kernel_manager = None
108 self._kernel_manager = None
108
109
@@ -110,14 +110,12 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):'
110 elif hasattr(self._lexer, '_saved_state_stack'):
110 elif hasattr(self._lexer, '_saved_state_stack'):
111 del self._lexer._saved_state_stack
111 del self._lexer._saved_state_stack
112
112
113 index = 0
114 # Lex the text using Pygments
113 # Lex the text using Pygments
114 index = 0
115 for token, text in self._lexer.get_tokens(qstring):
115 for token, text in self._lexer.get_tokens(qstring):
116 l = len(text)
116 length = len(text)
117 format = self._get_format(token)
117 self.setFormat(index, length, self._get_format(token))
118 if format is not None:
118 index += length
119 self.setFormat(index, l, format)
120 index += l
121
119
122 if hasattr(self._lexer, '_saved_state_stack'):
120 if hasattr(self._lexer, '_saved_state_stack'):
123 data = PygmentsBlockUserData(
121 data = PygmentsBlockUserData(
@@ -185,11 +183,9 b' class PygmentsHighlighter(QtGui.QSyntaxHighlighter):'
185 def _get_format_from_style(self, token, style):
183 def _get_format_from_style(self, token, style):
186 """ Returns a QTextCharFormat for token by reading a Pygments style.
184 """ Returns a QTextCharFormat for token by reading a Pygments style.
187 """
185 """
188 result = None
186 result = QtGui.QTextCharFormat()
189 for key, value in style.style_for_token(token).items():
187 for key, value in style.style_for_token(token).items():
190 if value:
188 if value:
191 if result is None:
192 result = QtGui.QTextCharFormat()
193 if key == 'color':
189 if key == 'color':
194 result.setForeground(self._get_brush(value))
190 result.setForeground(self._get_brush(value))
195 elif key == 'bgcolor':
191 elif key == 'bgcolor':
General Comments 0
You need to be logged in to leave comments. Login now