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 |
|
|
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 = |
|
|
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 |
|
|
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