diff --git a/IPython/frontend/qt/console/ansi_code_processor.py b/IPython/frontend/qt/console/ansi_code_processor.py index eeee218..f027ccf 100644 --- a/IPython/frontend/qt/console/ansi_code_processor.py +++ b/IPython/frontend/qt/console/ansi_code_processor.py @@ -26,12 +26,19 @@ MoveAction = namedtuple('MoveAction', ['action', 'dir', 'unit', 'count']) # An action for scroll requests (SU and ST) and form feeds. ScrollAction = namedtuple('ScrollAction', ['action', 'dir', 'unit', 'count']) +# An action for the carriage return character +CarriageReturnAction = namedtuple('CarriageReturnAction', ['action']) + +# An action for the beep character +BeepAction = namedtuple('BeepAction', ['action']) + # Regular expressions. CSI_COMMANDS = 'ABCDEFGHJKSTfmnsu' CSI_SUBPATTERN = '\[(.*?)([%s])' % CSI_COMMANDS OSC_SUBPATTERN = '\](.*?)[\x07\x1b]' -ANSI_PATTERN = re.compile('\x01?\x1b(%s|%s)\x02?' % \ - (CSI_SUBPATTERN, OSC_SUBPATTERN)) +ANSI_PATTERN = ('\x01?\x1b(%s|%s)\x02?' % \ + (CSI_SUBPATTERN, OSC_SUBPATTERN)) +ANSI_OR_SPECIAL_PATTERN = re.compile('(\b|\r)|(?:%s)' % ANSI_PATTERN) SPECIAL_PATTERN = re.compile('([\f])') #----------------------------------------------------------------------------- @@ -76,7 +83,7 @@ class AnsiCodeProcessor(object): self.actions = [] start = 0 - for match in ANSI_PATTERN.finditer(string): + for match in ANSI_OR_SPECIAL_PATTERN.finditer(string): raw = string[start:match.start()] substring = SPECIAL_PATTERN.sub(self._replace_special, raw) if substring or self.actions: @@ -85,20 +92,27 @@ class AnsiCodeProcessor(object): self.actions = [] groups = filter(lambda x: x is not None, match.groups()) - params = [ param for param in groups[1].split(';') if param ] - if groups[0].startswith('['): - # Case 1: CSI code. - try: - params = map(int, params) - except ValueError: - # Silently discard badly formed codes. - pass - else: - self.set_csi_code(groups[2], params) - - elif groups[0].startswith(']'): - # Case 2: OSC code. - self.set_osc_code(params) + if groups[0] == '\r': + self.actions.append(CarriageReturnAction('carriage-return')) + yield '' + elif groups[0] == '\b': + self.actions.append(BeepAction('beep')) + yield '' + else: + params = [ param for param in groups[1].split(';') if param ] + if groups[0].startswith('['): + # Case 1: CSI code. + try: + params = map(int, params) + except ValueError: + # Silently discard badly formed codes. + pass + else: + self.set_csi_code(groups[2], params) + + elif groups[0].startswith(']'): + # Case 2: OSC code. + self.set_osc_code(params) raw = string[start:] substring = SPECIAL_PATTERN.sub(self._replace_special, raw) diff --git a/IPython/frontend/qt/console/console_widget.py b/IPython/frontend/qt/console/console_widget.py index 5ccbe9e..2f776e9 100644 --- a/IPython/frontend/qt/console/console_widget.py +++ b/IPython/frontend/qt/console/console_widget.py @@ -1513,6 +1513,13 @@ class ConsoleWidget(LoggingConfigurable, QtGui.QWidget): cursor.joinPreviousEditBlock() cursor.deletePreviousChar() + elif act.action == 'carriage-return': + cursor.movePosition( + cursor.StartOfLine, cursor.KeepAnchor) + + elif act.action == 'beep': + QtGui.qApp.beep() + format = self._ansi_processor.get_format() cursor.insertText(substring, format) else: diff --git a/IPython/frontend/qt/console/tests/test_ansi_code_processor.py b/IPython/frontend/qt/console/tests/test_ansi_code_processor.py index f71334a..0f602b1 100644 --- a/IPython/frontend/qt/console/tests/test_ansi_code_processor.py +++ b/IPython/frontend/qt/console/tests/test_ansi_code_processor.py @@ -90,8 +90,8 @@ class TestAnsiCodeProcessor(unittest.TestCase): self.fail('Too many substrings.') self.assertEquals(i, 1, 'Too few substrings.') - def test_specials(self): - """ Are special characters processed correctly? + def test_formfeed(self): + """ Are formfeed characters processed correctly? """ string = '\f' # form feed self.assertEquals(list(self.processor.split_string(string)), ['']) @@ -102,6 +102,26 @@ class TestAnsiCodeProcessor(unittest.TestCase): self.assertEquals(action.unit, 'page') self.assertEquals(action.count, 1) + def test_carriage_return(self): + """ Are carriage return characters processed correctly? + """ + string = 'foo\rbar' # form feed + self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar']) + self.assertEquals(len(self.processor.actions), 1) + action = self.processor.actions[0] + self.assertEquals(action.action, 'carriage-return') + self.assertEquals(action.count, 1) + + def test_beep(self): + """ Are beep characters processed correctly? + """ + string = 'foo\bbar' # form feed + self.assertEquals(list(self.processor.split_string(string)), ['foo', '', 'bar']) + self.assertEquals(len(self.processor.actions), 1) + action = self.processor.actions[0] + self.assertEquals(action.action, 'beep') + self.assertEquals(action.count, 1) + if __name__ == '__main__': unittest.main()