##// END OF EJS Templates
BUG: qtconsole -- non-standard handling of \a and \b. [Fixes #1561]
Puneeth Chaganti -
Show More
@@ -29,16 +29,22 b" ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count'])"
29 # An action for the carriage return character
29 # An action for the carriage return character
30 CarriageReturnAction = namedtuple('CarriageReturnAction', ['action'])
30 CarriageReturnAction = namedtuple('CarriageReturnAction', ['action'])
31
31
32 # An action for the \n character
33 NewLineAction = namedtuple('NewLineAction', ['action'])
34
32 # An action for the beep character
35 # An action for the beep character
33 BeepAction = namedtuple('BeepAction', ['action'])
36 BeepAction = namedtuple('BeepAction', ['action'])
34
37
38 # An action for backspace
39 BackSpaceAction = namedtuple('BackSpaceAction', ['action'])
40
35 # Regular expressions.
41 # Regular expressions.
36 CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu'
42 CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu'
37 CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS
43 CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS
38 OSC_SUBPATTERN = '\](.*?)[\x07\x1b]'
44 OSC_SUBPATTERN = '\](.*?)[\x07\x1b]'
39 ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \
45 ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \
40 (CSI_SUBPATTERN, OSC_SUBPATTERN))
46 (CSI_SUBPATTERN, OSC_SUBPATTERN))
41 ANSI_OR_SPECIAL_PATTERN = re.compile('(\b|\r(?!\n))|(?:%s)' % ANSI_PATTERN)
47 ANSI_OR_SPECIAL_PATTERN = re.compile('(\a|\b|\r(?!\n)|\r?\n)|(?:%s)' % ANSI_PATTERN)
42 SPECIAL_PATTERN = re.compile('([\f])')
48 SPECIAL_PATTERN = re.compile('([\f])')
43
49
44 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
@@ -83,24 +89,41 b' class AnsiCodeProcessor(object):'
83 self.actions = []
89 self.actions = []
84 start = 0
90 start = 0
85
91
92 # strings ending with \r are assumed to be ending in \r\n since
93 # \n is appended to output strings automatically. Accounting
94 # for that, here.
95 last_char = '\n' if len(string) > 0 and string[-1] == '\n' else None
96 string = string[:-1] if last_char is not None else string
97
86 for match in ANSI_OR_SPECIAL_PATTERN.finditer(string):
98 for match in ANSI_OR_SPECIAL_PATTERN.finditer(string):
87 raw = string[start:match.start()]
99 raw = string[start:match.start()]
88 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
100 substring = SPECIAL_PATTERN.sub(self._replace_special, raw)
89 if substring or self.actions:
101 if substring or self.actions:
90 yield substring
102 yield substring
103 self.actions = []
91 start = match.end()
104 start = match.end()
92
105
93 self.actions = []
94 groups = filter(lambda x: x is not None, match.groups())
106 groups = filter(lambda x: x is not None, match.groups())
95 if groups[0] == '\r':
107 g0 = groups[0]
96 self.actions.append(CarriageReturnAction('carriage-return'))
108 if g0 == '\a':
97 yield ''
98 elif groups[0] == '\b':
99 self.actions.append(BeepAction('beep'))
109 self.actions.append(BeepAction('beep'))
100 yield ''
110 yield None
111 self.actions = []
112 elif g0 == '\r':
113 self.actions.append(CarriageReturnAction('carriage-return'))
114 yield None
115 self.actions = []
116 elif g0 == '\b':
117 self.actions.append(BackSpaceAction('backspace'))
118 yield None
119 self.actions = []
120 elif g0 == '\n' or g0 == '\r\n':
121 self.actions.append(NewLineAction('newline'))
122 yield g0
123 self.actions = []
101 else:
124 else:
102 params = [ param for param in groups[1].split(';') if param ]
125 params = [ param for param in groups[1].split(';') if param ]
103 if groups[0].startswith('['):
126 if g0.startswith('['):
104 # Case 1: CSI code.
127 # Case 1: CSI code.
105 try:
128 try:
106 params = map(int, params)
129 params = map(int, params)
@@ -110,7 +133,7 b' class AnsiCodeProcessor(object):'
110 else:
133 else:
111 self.set_csi_code(groups[2], params)
134 self.set_csi_code(groups[2], params)
112
135
113 elif groups[0].startswith(']'):
136 elif g0.startswith(']'):
114 # Case 2: OSC code.
137 # Case 2: OSC code.
115 self.set_osc_code(params)
138 self.set_osc_code(params)
116
139
@@ -119,6 +142,10 b' class AnsiCodeProcessor(object):'
119 if substring or self.actions:
142 if substring or self.actions:
120 yield substring
143 yield substring
121
144
145 if last_char is not None:
146 self.actions.append(NewLineAction('newline'))
147 yield last_char
148
122 def set_csi_code(self, command, params=[]):
149 def set_csi_code(self, command, params=[]):
123 """ Set attributes based on CSI (Control Sequence Introducer) code.
150 """ Set attributes based on CSI (Control Sequence Introducer) code.
124
151
@@ -1553,9 +1553,32 b' class ConsoleWidget(LoggingConfigurable, QtGui.QWidget):'
1553 elif act.action == 'beep':
1553 elif act.action == 'beep':
1554 QtGui.qApp.beep()
1554 QtGui.qApp.beep()
1555
1555
1556 elif act.action == 'backspace':
1557 if not cursor.atBlockStart():
1558 cursor.movePosition(
1559 cursor.PreviousCharacter, cursor.KeepAnchor)
1560
1561 elif act.action == 'newline':
1562 cursor.movePosition(cursor.EndOfLine)
1563
1556 format = self._ansi_processor.get_format()
1564 format = self._ansi_processor.get_format()
1565
1566 selection = cursor.selectedText()
1567 if len(selection) == 0:
1568 cursor.insertText(substring, format)
1569 elif substring is not None:
1570 # BS and CR are treated as a change in print
1571 # position, rather than a backwards character
1572 # deletion for output equivalence with (I)Python
1573 # terminal.
1574 if len(substring) >= len(selection):
1557 cursor.insertText(substring, format)
1575 cursor.insertText(substring, format)
1558 else:
1576 else:
1577 old_text = selection[len(substring):]
1578 cursor.insertText(substring + old_text, format)
1579 cursor.movePosition(cursor.PreviousCharacter,
1580 cursor.KeepAnchor, len(old_text))
1581 else:
1559 cursor.insertText(text)
1582 cursor.insertText(text)
1560 cursor.endEditBlock()
1583 cursor.endEditBlock()
1561
1584
@@ -106,28 +106,65 b' class TestAnsiCodeProcessor(unittest.TestCase):'
106 """ Are carriage return characters processed correctly?
106 """ Are carriage return characters processed correctly?
107 """
107 """
108 string = 'foo\rbar' # carriage return
108 string = 'foo\rbar' # carriage return
109 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
109 splits = []
110 self.assertEquals(len(self.processor.actions), 1)
110 actions = []
111 action = self.processor.actions[0]
111 for split in self.processor.split_string(string):
112 self.assertEquals(action.action, 'carriage-return')
112 splits.append(split)
113 actions.append([action.action for action in self.processor.actions])
114 self.assertEquals(splits, ['foo', None, 'bar'])
115 self.assertEquals(actions, [[], ['carriage-return'], []])
113
116
114 def test_carriage_return_newline(self):
117 def test_carriage_return_newline(self):
115 """transform CRLF to LF"""
118 """transform CRLF to LF"""
116 string = 'foo\rbar\r\ncat\r\n' # carriage return and newline
119 string = 'foo\rbar\r\ncat\r\n\n' # carriage return and newline
117 # only one CR action should occur, and '\r\n' should transform to '\n'
120 # only one CR action should occur, and '\r\n' should transform to '\n'
118 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar\r\ncat\r\n'])
121 splits = []
119 self.assertEquals(len(self.processor.actions), 1)
122 actions = []
120 action = self.processor.actions[0]
123 for split in self.processor.split_string(string):
121 self.assertEquals(action.action, 'carriage-return')
124 splits.append(split)
125 actions.append([action.action for action in self.processor.actions])
126 self.assertEquals(splits, ['foo', None, 'bar', '\r\n', 'cat', '\r\n', '\n'])
127 self.assertEquals(actions, [[], ['carriage-return'], [], ['newline'], [], ['newline'], ['newline']])
122
128
123 def test_beep(self):
129 def test_beep(self):
124 """ Are beep characters processed correctly?
130 """ Are beep characters processed correctly?
125 """
131 """
126 string = 'foo\bbar' # form feed
132 string = 'foo\abar' # bell
127 self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar'])
133 splits = []
128 self.assertEquals(len(self.processor.actions), 1)
134 actions = []
129 action = self.processor.actions[0]
135 for split in self.processor.split_string(string):
130 self.assertEquals(action.action, 'beep')
136 splits.append(split)
137 actions.append([action.action for action in self.processor.actions])
138 self.assertEquals(splits, ['foo', None, 'bar'])
139 self.assertEquals(actions, [[], ['beep'], []])
140
141 def test_backspace(self):
142 """ Are backspace characters processed correctly?
143 """
144 string = 'foo\bbar' # backspace
145 splits = []
146 actions = []
147 for split in self.processor.split_string(string):
148 splits.append(split)
149 actions.append([action.action for action in self.processor.actions])
150 self.assertEquals(splits, ['foo', None, 'bar'])
151 self.assertEquals(actions, [[], ['backspace'], []])
152
153 def test_combined(self):
154 """ Are CR and BS characters processed correctly in combination?
155
156 BS is treated as a change in print position, rather than a
157 backwards character deletion. Therefore a BS at EOL is
158 effectively ignored.
159 """
160 string = 'abc\rdef\b' # CR and backspace
161 splits = []
162 actions = []
163 for split in self.processor.split_string(string):
164 splits.append(split)
165 actions.append([action.action for action in self.processor.actions])
166 self.assertEquals(splits, ['abc', None, 'def', None])
167 self.assertEquals(actions, [[], ['carriage-return'], [], ['backspace']])
131
168
132
169
133 if __name__ == '__main__':
170 if __name__ == '__main__':
General Comments 0
You need to be logged in to leave comments. Login now