diff --git a/external-deps/qtconsole/.gitrepo b/external-deps/qtconsole/.gitrepo index b1be2c7cd0e..39d5d433ab3 100644 --- a/external-deps/qtconsole/.gitrepo +++ b/external-deps/qtconsole/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/jupyter/qtconsole.git branch = main - commit = a72387f01dea2076346ecb38857de5266414dbe6 - parent = 8d3b773f38158025f7d65a503c2dd55cf6c15b46 + commit = 7915694709460f95d92d48daf3c57eb7a3f2bce4 + parent = 3274e793422497da39940e8ac4dad3ef793acafe method = merge cmdver = 0.4.3 diff --git a/external-deps/qtconsole/qtconsole/ansi_code_processor.py b/external-deps/qtconsole/qtconsole/ansi_code_processor.py index 063c9067441..16f2ddde6a9 100644 --- a/external-deps/qtconsole/qtconsole/ansi_code_processor.py +++ b/external-deps/qtconsole/qtconsole/ansi_code_processor.py @@ -92,10 +92,7 @@ def split_string(self, string): self.actions = [] start = 0 - # strings ending with \r are assumed to be ending in \r\n since - # \n is appended to output strings automatically. Accounting - # for that, here. - last_char = '\n' if len(string) > 0 and string[-1] == '\n' else None + last_char = None string = string[:-1] if last_char is not None else string for match in ANSI_OR_SPECIAL_PATTERN.finditer(string): @@ -122,7 +119,7 @@ def split_string(self, string): self.actions = [] elif g0 == '\n' or g0 == '\r\n': self.actions.append(NewLineAction('newline')) - yield g0 + yield None self.actions = [] else: params = [ param for param in groups[1].split(';') if param ] @@ -147,7 +144,7 @@ def split_string(self, string): if last_char is not None: self.actions.append(NewLineAction('newline')) - yield last_char + yield None def set_csi_code(self, command, params=[]): """ Set attributes based on CSI (Control Sequence Introducer) code. @@ -185,6 +182,22 @@ def set_csi_code(self, command, params=[]): count = params[0] if params else 1 self.actions.append(ScrollAction('scroll', dir, 'line', count)) + elif command == 'A': # Move N lines Up + dir = 'up' + count = params[0] if params else 1 + self.actions.append(MoveAction('move', dir, 'line', count)) + + elif command == 'B': # Move N lines Down + dir = 'down' + count = params[0] if params else 1 + self.actions.append(MoveAction('move', dir, 'line', count)) + + elif command == 'F': # Goes back to the begining of the n-th previous line + dir = 'leftup' + count = params[0] if params else 1 + self.actions.append(MoveAction('move', dir, 'line', count)) + + def set_osc_code(self, params): """ Set attributes based on OSC (Operating System Command) parameters. diff --git a/external-deps/qtconsole/qtconsole/console_widget.py b/external-deps/qtconsole/qtconsole/console_widget.py index 09bd7f71a1d..aa492bafdc4 100644 --- a/external-deps/qtconsole/qtconsole/console_widget.py +++ b/external-deps/qtconsole/qtconsole/console_widget.py @@ -2188,6 +2188,27 @@ def _insert_plain_text(self, cursor, text, flush=False): cursor.select(QtGui.QTextCursor.Document) cursor.removeSelectedText() + elif act.action == 'move' and act.unit == 'line': + if act.dir == 'up': + for i in range(act.count): + cursor.movePosition( + QtGui.QTextCursor.Up + ) + elif act.dir == 'down': + for i in range(act.count): + cursor.movePosition( + QtGui.QTextCursor.Down + ) + elif act.dir == 'leftup': + for i in range(act.count): + cursor.movePosition( + QtGui.QTextCursor.Up + ) + cursor.movePosition( + QtGui.QTextCursor.StartOfLine, + QtGui.QTextCursor.MoveAnchor + ) + elif act.action == 'carriage-return': cursor.movePosition( QtGui.QTextCursor.StartOfLine, @@ -2203,7 +2224,19 @@ def _insert_plain_text(self, cursor, text, flush=False): QtGui.QTextCursor.MoveAnchor) elif act.action == 'newline': - cursor.movePosition(QtGui.QTextCursor.EndOfLine) + if ( + cursor.block() != cursor.document().lastBlock() + and not cursor.document() + .toPlainText() + .endswith(self._prompt) + ): + cursor.movePosition(QtGui.QTextCursor.NextBlock) + else: + cursor.movePosition( + QtGui.QTextCursor.EndOfLine, + QtGui.QTextCursor.MoveAnchor, + ) + cursor.insertText("\n") # simulate replacement mode if substring is not None: diff --git a/external-deps/qtconsole/qtconsole/tests/test_ansi_code_processor.py b/external-deps/qtconsole/qtconsole/tests/test_ansi_code_processor.py index 2b7dd71f42f..ed00d631cc7 100644 --- a/external-deps/qtconsole/qtconsole/tests/test_ansi_code_processor.py +++ b/external-deps/qtconsole/qtconsole/tests/test_ansi_code_processor.py @@ -139,7 +139,7 @@ def test_carriage_return_newline(self): for split in self.processor.split_string(string): splits.append(split) actions.append([action.action for action in self.processor.actions]) - self.assertEqual(splits, ['foo', None, 'bar', '\r\n', 'cat', '\r\n', '\n']) + self.assertEqual(splits, ['foo', None, 'bar', None, 'cat', None, None]) self.assertEqual(actions, [[], ['carriage-return'], [], ['newline'], [], ['newline'], ['newline']]) def test_beep(self): @@ -182,6 +182,49 @@ def test_combined(self): self.assertEqual(splits, ['abc', None, 'def', None]) self.assertEqual(actions, [[], ['carriage-return'], [], ['backspace']]) + def test_move_cursor_up(self): + """Are the ANSI commands for the cursor movement actions + (movement up and to the beginning of the line) processed correctly? + """ + # This line moves the cursor up once, then moves it up five more lines. + # Next, it moves the cursor to the beginning of the previous line, and + # finally moves it to the beginning of the fifth line above the current + # position + string = '\x1b[A\x1b[5A\x1b[F\x1b[5F' + i = -1 + for i, substring in enumerate(self.processor.split_string(string)): + if i == 0: + self.assertEqual(len(self.processor.actions), 1) + action = self.processor.actions[0] + self.assertEqual(action.action, 'move') + self.assertEqual(action.dir, 'up') + self.assertEqual(action.unit, 'line') + self.assertEqual(action.count, 1) + elif i == 1: + self.assertEqual(len(self.processor.actions), 1) + action = self.processor.actions[0] + self.assertEqual(action.action, 'move') + self.assertEqual(action.dir, 'up') + self.assertEqual(action.unit, 'line') + self.assertEqual(action.count, 5) + elif i == 2: + self.assertEqual(len(self.processor.actions), 1) + action = self.processor.actions[0] + self.assertEqual(action.action, 'move') + self.assertEqual(action.dir, 'leftup') + self.assertEqual(action.unit, 'line') + self.assertEqual(action.count, 1) + elif i == 3: + self.assertEqual(len(self.processor.actions), 1) + action = self.processor.actions[0] + self.assertEqual(action.action, 'move') + self.assertEqual(action.dir, 'leftup') + self.assertEqual(action.unit, 'line') + self.assertEqual(action.count, 5) + else: + self.fail('Too many substrings.') + self.assertEqual(i, 3, 'Too few substrings.') + if __name__ == '__main__': unittest.main()