From ed4ddc80e4084cb83f7bbf054df51f7c1e001bb1 Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Mon, 27 May 2024 20:31:10 -0400 Subject: [PATCH 01/24] Preserve cursor position and _insert_plain_text() Preserve position of cursor so ANSI commands such as \r that modify position will preserve the position they specifed over multiple calls, even if these calls are not all combined into one. --- qtconsole/console_widget.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 62d20377..57979c86 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -1006,7 +1006,6 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs): else: if insert != self._insert_plain_text: self._flush_pending_stream() - cursor.movePosition(QtGui.QTextCursor.End) # Perform the insertion. result = insert(cursor, input, *args, **kwargs) @@ -1660,10 +1659,7 @@ def _on_flush_pending_stream_timer(self): """ Flush the pending stream output and change the prompt position appropriately. """ - cursor = self._control.textCursor() - cursor.movePosition(QtGui.QTextCursor.End) self._flush_pending_stream() - cursor.movePosition(QtGui.QTextCursor.End) def _flush_pending_stream(self): """ Flush out pending text into the widget. """ @@ -1674,7 +1670,7 @@ def _flush_pending_stream(self): text = self._get_last_lines_from_list(text, buffer_size) text = ''.join(text) t = time.time() - self._insert_plain_text(self._get_end_cursor(), text, flush=True) + self._insert_plain_text(self._control.textCursor(), text, flush=True) # Set the flush interval to equal the maximum time to update text. self._pending_text_flush_interval.setInterval( int(max(100, (time.time() - t) * 1000)) @@ -2123,7 +2119,7 @@ def _insert_plain_text(self, cursor, text, flush=False): cursor.select(QtGui.QTextCursor.Document) remove = True if act.area == 'line': - if act.erase_to == 'all': + if act.erase_to == 'all': cursor.select(QtGui.QTextCursor.LineUnderCursor) remove = True elif act.erase_to == 'start': @@ -2137,7 +2133,7 @@ def _insert_plain_text(self, cursor, text, flush=False): QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor) remove = True - if remove: + if remove: nspace=cursor.selectionEnd()-cursor.selectionStart() if fill else 0 cursor.removeSelectedText() if nspace>0: cursor.insertText(' '*nspace) # replace text by space, to keep cursor position as specified @@ -2181,11 +2177,12 @@ def _insert_plain_text(self, cursor, text, flush=False): remain = cursor2.position() - pos # number of characters until end of line n=len(substring) swallow = min(n, remain) # number of character to swallow - cursor.setPosition(pos+swallow,QtGui.QTextCursor.KeepAnchor) + cursor.setPosition(pos + swallow, QtGui.QTextCursor.KeepAnchor) cursor.insertText(substring,format) else: cursor.insertText(text) cursor.endEditBlock() + self._control.setTextCursor(cursor) if should_autoscroll: self._scroll_to_end() From 96ab4e84e08a71c6f0a75bf58d36da5959df564c Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Mon, 27 May 2024 22:19:47 -0400 Subject: [PATCH 02/24] Add regression test for _insert_plain_text() carriage return handling Add regression to test for #272 --- qtconsole/tests/test_00_console_widget.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index 2745be6e..42f6ebc7 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -371,6 +371,27 @@ def test_erase_in_line(self): # clear all the text cursor.insertText('') + def test_carriage_return(self): + """ Does overwriting the currentt line with carriage return work? + """ + w = ConsoleWidget() + test_inputs = ['Hello\n' + 'World\r', + '*' * 10, + '\r' + '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', + '\r\n'] + + expected_output = "Hello\u20290123456789\u2029" + + for text in test_inputs: + cursor = w._get_cursor() + w._insert_plain_text(cursor, text, flush = True) + w._flush_pending_stream() # emulate text being flushed + + self.assert_text_equal(cursor, expected_output) + def test_link_handling(self): noButton = QtCore.Qt.NoButton noButtons = QtCore.Qt.NoButton From caaff4c610e9ae0589429738fb3ed6cbf42ac1c3 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Mon, 27 May 2024 22:58:48 -0500 Subject: [PATCH 03/24] Use of "hack" verboseTB --- qtconsole/mainwindow.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qtconsole/mainwindow.py b/qtconsole/mainwindow.py index b5b0476a..ac086001 100644 --- a/qtconsole/mainwindow.py +++ b/qtconsole/mainwindow.py @@ -801,6 +801,7 @@ def set_syntax_style(self, syntax_style): colors='nocolor' elif styles.dark_style(syntax_style): colors='linux' + else: colors='lightbg' self.active_frontend.syntax_style = syntax_style @@ -810,6 +811,11 @@ def set_syntax_style(self, syntax_style): self.active_frontend._style_sheet_changed() self.active_frontend.reset(clear=True) self.active_frontend._execute("%colors linux", True) + selectColor = styles.get_colors(syntax_style) + self.active_frontend._execute("from IPython.core.ultratb import VerboseTB\n"+ + "VerboseTB._tb_highlight = 'bg:%(select)s'\n"%selectColor+ + f"VerboseTB._tb_highlight_style = '{syntax_style}'", True) + def close_active_frontend(self): self.close_tab(self.active_frontend) From f81d23d7703e9b76991a3c3466c513758d20117a Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Fri, 31 May 2024 00:49:04 -0400 Subject: [PATCH 04/24] Use additional cursor to track insert position for text insert Instead of using the main cursor (which the user can control by clicking), create a dedicated cursor `_insert_text_cursor` to track the position of where text should be inserted. --- qtconsole/console_widget.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 57979c86..7852211c 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -297,6 +297,10 @@ def __init__(self, parent=None, **kw): self._reading_callback = None self._tab_width = 4 + # Cursor position of where to insert text. + # Control characters allow this to move around on the current line. + self._insert_text_cursor = self._control.textCursor() + # List of strings pending to be appended as plain text in the widget. # The text is not immediately inserted when available to not # choke the Qt event loop with paint events for the widget in @@ -695,6 +699,9 @@ def do_execute(self, source, complete, indent): # effect when using a QTextEdit. I believe this is a Qt bug. self._control.moveCursor(QtGui.QTextCursor.End) + # Advance where text is inserted + self._insert_text_cursor.movePosition(QtGui.QTextCursor.End) + def export_html(self): """ Shows a dialog to export HTML/XML in various formats. """ @@ -712,6 +719,9 @@ def _finalize_input_request(self): self._append_before_prompt_cursor.setPosition( self._get_end_cursor().position()) + self._insert_text_cursor.setPosition( + self._get_end_cursor().position()) + # The maximum block count is only in effect during execution. # This ensures that _prompt_pos does not become invalid due to # text truncation. @@ -998,7 +1008,7 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs): current prompt, if there is one. """ # Determine where to insert the content. - cursor = self._control.textCursor() + cursor = self._insert_text_cursor if before_prompt and (self._reading or not self._executing): self._flush_pending_stream() cursor._insert_mode=True @@ -1009,6 +1019,11 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs): # Perform the insertion. result = insert(cursor, input, *args, **kwargs) + + # Remove insert mode tag + if hasattr(cursor, "_insert_mode"): + del cursor._insert_mode + return result def _append_block(self, block_format=None, before_prompt=False): @@ -1670,7 +1685,7 @@ def _flush_pending_stream(self): text = self._get_last_lines_from_list(text, buffer_size) text = ''.join(text) t = time.time() - self._insert_plain_text(self._control.textCursor(), text, flush=True) + self._insert_plain_text(self._insert_text_cursor, text, flush=True) # Set the flush interval to equal the maximum time to update text. self._pending_text_flush_interval.setInterval( int(max(100, (time.time() - t) * 1000)) @@ -2182,7 +2197,6 @@ def _insert_plain_text(self, cursor, text, flush=False): else: cursor.insertText(text) cursor.endEditBlock() - self._control.setTextCursor(cursor) if should_autoscroll: self._scroll_to_end() From a1abf41acee453a5d0585409a333d7de30163aef Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Fri, 31 May 2024 00:51:50 -0400 Subject: [PATCH 05/24] Update test_carriage_return() test. Need to set _executing = True for test to work. --- qtconsole/tests/test_00_console_widget.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index 42f6ebc7..ac3f4a92 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -375,21 +375,22 @@ def test_carriage_return(self): """ Does overwriting the currentt line with carriage return work? """ w = ConsoleWidget() - test_inputs = ['Hello\n' + test_inputs = ['Hello\n', 'World\r', '*' * 10, - '\r' + '\r', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '\r\n'] expected_output = "Hello\u20290123456789\u2029" + w._executing = True for text in test_inputs: - cursor = w._get_cursor() - w._insert_plain_text(cursor, text, flush = True) + w._append_plain_text(text, before_prompt = True) w._flush_pending_stream() # emulate text being flushed + cursor = w._get_cursor() self.assert_text_equal(cursor, expected_output) def test_link_handling(self): From b0eb6003c27c4cead2c469bd0c76fb8cb52b8b68 Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:29:51 -0500 Subject: [PATCH 06/24] Add support for ANSI 256 colors --- qtconsole/ansi_code_processor.py | 31 ++++++++++++++++++++++++++----- qtconsole/mainwindow.py | 1 - 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/qtconsole/ansi_code_processor.py b/qtconsole/ansi_code_processor.py index 08f222f6..dfc542cb 100644 --- a/qtconsole/ansi_code_processor.py +++ b/qtconsole/ansi_code_processor.py @@ -292,6 +292,31 @@ def _replace_special(self, match): self.actions.append(ScrollAction('scroll', 'down', 'page', 1)) return '' + def _parse_ansi_color(self, color, intensity): + """ + Map an ANSI color code to color name or a RGB tuple. + Based on: https://gist.github.com/MightyPork/1d9bd3a3fd4eb1a661011560f6921b5b + """ + parsed_color = None + if color < 8: + # Adjust for intensity, if possible. + if intensity > 0: + color += 8 + parsed_color = self.color_map.get(color, None) + elif (color > 231): + s = int((color - 232) * 10 + 8) + parsed_color = (s, s, s) + else: + n = color - 16 + b = n % 6 + g = (n - b) / 6 % 6 + r = (n - b - g * 6) / 36 % 6 + r = int(r * 40 + 55) if r else 0 + g = int(g * 40 + 55) if g else 0 + b = int(b * 40 + 55) if b else 0 + parsed_color = (r, g, b) + return parsed_color + class QtAnsiCodeProcessor(AnsiCodeProcessor): """ Translates ANSI escape codes into QTextCharFormats. @@ -323,12 +348,8 @@ def get_color(self, color, intensity=0): """ Returns a QColor for a given color code or rgb list, or None if one cannot be constructed. """ - if isinstance(color, int): - # Adjust for intensity, if possible. - if color < 8 and intensity > 0: - color += 8 - constructor = self.color_map.get(color, None) + constructor = self._parse_ansi_color(color, intensity) elif isinstance(color, (tuple, list)): constructor = color else: diff --git a/qtconsole/mainwindow.py b/qtconsole/mainwindow.py index ac086001..bd3fc276 100644 --- a/qtconsole/mainwindow.py +++ b/qtconsole/mainwindow.py @@ -810,7 +810,6 @@ def set_syntax_style(self, syntax_style): self.active_frontend._syntax_style_changed() self.active_frontend._style_sheet_changed() self.active_frontend.reset(clear=True) - self.active_frontend._execute("%colors linux", True) selectColor = styles.get_colors(syntax_style) self.active_frontend._execute("from IPython.core.ultratb import VerboseTB\n"+ "VerboseTB._tb_highlight = 'bg:%(select)s'\n"%selectColor+ From ad8b77c8126ca7642d5a802c030290c76925b6df Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Sat, 8 Jun 2024 14:34:28 -0400 Subject: [PATCH 07/24] Only use insert mode when needed to work with ANSI sequences --- qtconsole/console_widget.py | 32 ++++++++++++++++++----- qtconsole/frontend_widget.py | 2 +- qtconsole/tests/test_00_console_widget.py | 23 +++++++++++++++- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 7852211c..2123f853 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -1011,9 +1011,27 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs): cursor = self._insert_text_cursor if before_prompt and (self._reading or not self._executing): self._flush_pending_stream() - cursor._insert_mode=True - cursor.setPosition(self._append_before_prompt_pos) + + # Jump to before prompt, if there is one + if cursor.position() >= self._append_before_prompt_pos \ + and self._append_before_prompt_pos != self._get_end_pos(): + cursor.setPosition(self._append_before_prompt_pos) + + # If we appending on the same line as the prompt, use insert mode + # If so, the character at self._append_before_prompt_pos will not be a newline + cursor.movePosition(QtGui.QTextCursor.Right, + QtGui.QTextCursor.KeepAnchor) + if cursor.selection().toPlainText() != '\n': + cursor._insert_mode = True + cursor.movePosition(QtGui.QTextCursor.Left) else: + # Insert at current printing point + # If cursor is before prompt jump to end, but only if there + # is a prompt (before_prompt_pos != end) + if cursor.position() < self._append_before_prompt_pos \ + and self._append_before_prompt_pos != self._get_end_pos(): + cursor.movePosition(QtGui.QTextCursor.End) + if insert != self._insert_plain_text: self._flush_pending_stream() @@ -2104,12 +2122,12 @@ def _insert_plain_text(self, cursor, text, flush=False): if (self._executing and not flush and self._pending_text_flush_interval.isActive() and - cursor.position() == self._get_end_pos()): + cursor.position() == self._insert_text_cursor.position()): # Queue the text to insert in case it is being inserted at end self._pending_insert_text.append(text) if buffer_size > 0: self._pending_insert_text = self._get_last_lines_from_list( - self._pending_insert_text, buffer_size) + self._pending_insert_text, buffer_size) return if self._executing and not self._pending_text_flush_interval.isActive(): @@ -2185,7 +2203,9 @@ def _insert_plain_text(self, cursor, text, flush=False): # simulate replacement mode if substring is not None: format = self._ansi_processor.get_format() - if not (hasattr(cursor,'_insert_mode') and cursor._insert_mode): + + # Note that using _insert_mode means the \r ANSI sequence will not swallow characters. + if not (hasattr(cursor, '_insert_mode') and cursor._insert_mode): pos = cursor.position() cursor2 = QtGui.QTextCursor(cursor) # self._get_line_end_pos() is the previous line, don't use it cursor2.movePosition(QtGui.QTextCursor.EndOfLine) @@ -2193,7 +2213,7 @@ def _insert_plain_text(self, cursor, text, flush=False): n=len(substring) swallow = min(n, remain) # number of character to swallow cursor.setPosition(pos + swallow, QtGui.QTextCursor.KeepAnchor) - cursor.insertText(substring,format) + cursor.insertText(substring, format) else: cursor.insertText(text) cursor.endEditBlock() diff --git a/qtconsole/frontend_widget.py b/qtconsole/frontend_widget.py index 0a788bb9..8e2fbec8 100644 --- a/qtconsole/frontend_widget.py +++ b/qtconsole/frontend_widget.py @@ -728,7 +728,7 @@ def restart_kernel(self, message, now=False): def append_stream(self, text): """Appends text to the output stream.""" - self._append_plain_text(text, before_prompt=True) + self._append_plain_text(text, before_prompt = True) def flush_clearoutput(self): """If a clearoutput is pending, execute it.""" diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index ac3f4a92..41b3f462 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -371,7 +371,7 @@ def test_erase_in_line(self): # clear all the text cursor.insertText('') - def test_carriage_return(self): + def test_print_while_executing(self): """ Does overwriting the currentt line with carriage return work? """ w = ConsoleWidget() @@ -393,6 +393,27 @@ def test_carriage_return(self): cursor = w._get_cursor() self.assert_text_equal(cursor, expected_output) + def test_print_while_reading(self): + """ Does overwriting the currentt line with carriage return work? + """ + w = ConsoleWidget() + test_inputs = ['Hello\n', + 'World\r', + '*' * 10, + '\r', + '0', '1', '2', '3', '4', + '5', '6', '7', '8', '9', + '\r\n'] + + expected_output = "Hello\u20290123456789\u2029" + + for text in test_inputs: + w._append_plain_text(text, before_prompt = True) + w._flush_pending_stream() # emulate text being flushed + + cursor = w._get_cursor() + self.assert_text_equal(cursor, expected_output) + def test_link_handling(self): noButton = QtCore.Qt.NoButton noButtons = QtCore.Qt.NoButton From be39446c5aed04ace4e92c8b55b351676010ba4f Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Sun, 9 Jun 2024 11:08:08 -0400 Subject: [PATCH 08/24] Add fix for transition from insert before prompt to insert after prompt. And added test case. --- qtconsole/console_widget.py | 5 ++- qtconsole/tests/test_00_console_widget.py | 51 ++++++++++------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 2123f853..2b1f7435 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -1028,7 +1028,7 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs): # Insert at current printing point # If cursor is before prompt jump to end, but only if there # is a prompt (before_prompt_pos != end) - if cursor.position() < self._append_before_prompt_pos \ + if cursor.position() <= self._append_before_prompt_pos \ and self._append_before_prompt_pos != self._get_end_pos(): cursor.movePosition(QtGui.QTextCursor.End) @@ -2562,6 +2562,9 @@ def _show_prompt(self, prompt=None, html=False, newline=True, if move_forward: self._append_before_prompt_cursor.setPosition( self._append_before_prompt_cursor.position() + 1) + else: + # cursor position was 0, set before prompt cursor + self._append_before_prompt_cursor.setPosition(0) self._prompt_started() #------ Signal handlers ---------------------------------------------------- diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index 41b3f462..fa23b48b 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -371,48 +371,43 @@ def test_erase_in_line(self): # clear all the text cursor.insertText('') - def test_print_while_executing(self): - """ Does overwriting the currentt line with carriage return work? + def test_print_carriage_return(self): + """ Test that overwriting the current line works as intended, + before and after the cursor prompt. """ w = ConsoleWidget() - test_inputs = ['Hello\n', - 'World\r', - '*' * 10, - '\r', + + # Show a prompt + w._prompt = "prompt>" + w._prompt_sep = "\n" + + w._show_prompt() + self.assert_text_equal(w._get_cursor(), '\u2029prompt>') + + test_inputs = ['Hello\n', 'World\r', + '*' * 10, '\r', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '\r\n'] - expected_output = "Hello\u20290123456789\u2029" - - w._executing = True for text in test_inputs: w._append_plain_text(text, before_prompt = True) w._flush_pending_stream() # emulate text being flushed - cursor = w._get_cursor() - self.assert_text_equal(cursor, expected_output) - - def test_print_while_reading(self): - """ Does overwriting the currentt line with carriage return work? - """ - w = ConsoleWidget() - test_inputs = ['Hello\n', - 'World\r', - '*' * 10, - '\r', - '0', '1', '2', '3', '4', - '5', '6', '7', '8', '9', - '\r\n'] + self.assert_text_equal(w._get_cursor(), + "Hello\u20290123456789\u2029\u2029prompt>") - expected_output = "Hello\u20290123456789\u2029" + # Print after prompt + w._executing = True + test_inputs = ['\nF', 'o', 'o', + '\r', 'Bar', '\n'] for text in test_inputs: - w._append_plain_text(text, before_prompt = True) + w._append_plain_text(text, before_prompt = False) w._flush_pending_stream() # emulate text being flushed - cursor = w._get_cursor() - self.assert_text_equal(cursor, expected_output) + self.assert_text_equal(w._get_cursor(), + "Hello\u20290123456789\u2029\u2029prompt>\u2029Bar\u2029") def test_link_handling(self): noButton = QtCore.Qt.NoButton @@ -491,7 +486,7 @@ def test_prompt_cursors(self): w._prompt_pos - len(w._prompt)) # insert some text before the prompt - w._append_plain_text('line', before_prompt=True) + w._append_plain_text('line', before_prompt = True) self.assertEqual(w._prompt_pos, w._get_end_pos()) self.assertEqual(w._append_before_prompt_pos, w._prompt_pos - len(w._prompt)) From 50bb0fdba1bce2fb3af1f1394fa4dff955819ec6 Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Sun, 9 Jun 2024 12:26:50 -0400 Subject: [PATCH 09/24] Fix handling of a blank prompt Make sure prompt is correctly setup even if the prompt is literally a blank string, as could be set using input() --- qtconsole/console_widget.py | 14 +++++++++++++- qtconsole/tests/test_00_console_widget.py | 9 +++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 2b1f7435..20a30873 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -2519,6 +2519,9 @@ def _show_prompt(self, prompt=None, html=False, newline=True, cursor = self._get_end_cursor() + # Memorize end to check if we actually add any prompt characters + prior_end_pos = cursor.position() + # Save the current position to support _append*(before_prompt=True). # We can't leave the cursor at the end of the document though, because # that would cause any further additions to move the cursor. Therefore, @@ -2557,7 +2560,16 @@ def _show_prompt(self, prompt=None, html=False, newline=True, self._prompt_html = None self._flush_pending_stream() - self._prompt_cursor.setPosition(self._get_end_pos() - 1) + + current_end_pos = self._get_end_pos() + if prior_end_pos != current_end_pos: + # Set the prompt cursor to end minus 1, so long as + # the prompt was not blank + self._prompt_cursor.setPosition(current_end_pos - 1) + else: + # The prompt didn't move end, i.e. the prompt inserted exactly 0 characters + # Move cursor to end + self._prompt_cursor.setPosition(current_end_pos) if move_forward: self._append_before_prompt_cursor.setPosition( diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index fa23b48b..69368529 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -491,6 +491,15 @@ def test_prompt_cursors(self): self.assertEqual(w._append_before_prompt_pos, w._prompt_pos - len(w._prompt)) + # Test a blank prompt. Such as from input() + w._append_plain_text('\n') + w._show_prompt(prompt = '', separator = False) + + w._append_plain_text('plain text\n') + + self.assertEqual(w._prompt_pos, w._get_end_pos()) + self.assertEqual(w._append_before_prompt_pos, w._prompt_pos) + def test_select_all(self): w = ConsoleWidget() w._append_plain_text('Header\n') From ae9e57b658654618a2a379416244cd2d9acdfca8 Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Sun, 9 Jun 2024 12:38:27 -0400 Subject: [PATCH 10/24] Revert "Fix handling of a blank prompt" Changes actually broke all input() handling. Reverting. This reverts commit 50bb0fdba1bce2fb3af1f1394fa4dff955819ec6. --- qtconsole/console_widget.py | 14 +------------- qtconsole/tests/test_00_console_widget.py | 9 --------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 20a30873..2b1f7435 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -2519,9 +2519,6 @@ def _show_prompt(self, prompt=None, html=False, newline=True, cursor = self._get_end_cursor() - # Memorize end to check if we actually add any prompt characters - prior_end_pos = cursor.position() - # Save the current position to support _append*(before_prompt=True). # We can't leave the cursor at the end of the document though, because # that would cause any further additions to move the cursor. Therefore, @@ -2560,16 +2557,7 @@ def _show_prompt(self, prompt=None, html=False, newline=True, self._prompt_html = None self._flush_pending_stream() - - current_end_pos = self._get_end_pos() - if prior_end_pos != current_end_pos: - # Set the prompt cursor to end minus 1, so long as - # the prompt was not blank - self._prompt_cursor.setPosition(current_end_pos - 1) - else: - # The prompt didn't move end, i.e. the prompt inserted exactly 0 characters - # Move cursor to end - self._prompt_cursor.setPosition(current_end_pos) + self._prompt_cursor.setPosition(self._get_end_pos() - 1) if move_forward: self._append_before_prompt_cursor.setPosition( diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index 69368529..fa23b48b 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -491,15 +491,6 @@ def test_prompt_cursors(self): self.assertEqual(w._append_before_prompt_pos, w._prompt_pos - len(w._prompt)) - # Test a blank prompt. Such as from input() - w._append_plain_text('\n') - w._show_prompt(prompt = '', separator = False) - - w._append_plain_text('plain text\n') - - self.assertEqual(w._prompt_pos, w._get_end_pos()) - self.assertEqual(w._append_before_prompt_pos, w._prompt_pos) - def test_select_all(self): w = ConsoleWidget() w._append_plain_text('Header\n') From b2737879f2667a1b78abc023b647bed4f549bf05 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 11 Jun 2024 00:19:57 -0500 Subject: [PATCH 11/24] Format string hack --- qtconsole/mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtconsole/mainwindow.py b/qtconsole/mainwindow.py index bd3fc276..bee10b0c 100644 --- a/qtconsole/mainwindow.py +++ b/qtconsole/mainwindow.py @@ -812,7 +812,7 @@ def set_syntax_style(self, syntax_style): self.active_frontend.reset(clear=True) selectColor = styles.get_colors(syntax_style) self.active_frontend._execute("from IPython.core.ultratb import VerboseTB\n"+ - "VerboseTB._tb_highlight = 'bg:%(select)s'\n"%selectColor+ + f"VerboseTB._tb_highlight = 'bg:%{selectColor}'\n"+ f"VerboseTB._tb_highlight_style = '{syntax_style}'", True) From f5d14e3ed88482aeb6a9598194357c0a9a662209 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 11 Jun 2024 01:42:27 -0500 Subject: [PATCH 12/24] Fix test --- qtconsole/tests/test_jupyter_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtconsole/tests/test_jupyter_widget.py b/qtconsole/tests/test_jupyter_widget.py index 43ab4a18..3849814b 100644 --- a/qtconsole/tests/test_jupyter_widget.py +++ b/qtconsole/tests/test_jupyter_widget.py @@ -34,7 +34,7 @@ def test_stylesheet_changed(self): w = JupyterWidget(kind='rich') # By default, the background is light. White text is rendered as black - self.assertEqual(w._ansi_processor.get_color(15).name(), '#000000') + self.assertEqual(w._ansi_processor.get_color(15).name(), '#ffffff') # Change to a dark colorscheme. White text is rendered as white w.syntax_style = 'monokai' From ddedcdfc0da5571f2f99d037e0b54f499bdd8325 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Thu, 13 Jun 2024 00:13:27 -0500 Subject: [PATCH 13/24] Fix ANSI to RGB transformation function --- qtconsole/ansi_code_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtconsole/ansi_code_processor.py b/qtconsole/ansi_code_processor.py index dfc542cb..69254c1d 100644 --- a/qtconsole/ansi_code_processor.py +++ b/qtconsole/ansi_code_processor.py @@ -298,7 +298,7 @@ def _parse_ansi_color(self, color, intensity): Based on: https://gist.github.com/MightyPork/1d9bd3a3fd4eb1a661011560f6921b5b """ parsed_color = None - if color < 8: + if color < 16: # Adjust for intensity, if possible. if intensity > 0: color += 8 From 608d0e56bb35c3cabb39395653a46a7c56ccd088 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Thu, 13 Jun 2024 00:13:51 -0500 Subject: [PATCH 14/24] Fix use of "hack" --- qtconsole/mainwindow.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qtconsole/mainwindow.py b/qtconsole/mainwindow.py index bee10b0c..970dad11 100644 --- a/qtconsole/mainwindow.py +++ b/qtconsole/mainwindow.py @@ -810,10 +810,10 @@ def set_syntax_style(self, syntax_style): self.active_frontend._syntax_style_changed() self.active_frontend._style_sheet_changed() self.active_frontend.reset(clear=True) - selectColor = styles.get_colors(syntax_style) - self.active_frontend._execute("from IPython.core.ultratb import VerboseTB\n"+ - f"VerboseTB._tb_highlight = 'bg:%{selectColor}'\n"+ - f"VerboseTB._tb_highlight_style = '{syntax_style}'", True) + self.active_frontend._execute( + f"from IPython.core.ultratb import VerboseTB;" + "VerboseTB._tb_highlight_style = '{syntax_style}'", + True) def close_active_frontend(self): From 65308b53a7e564623c0f3ed5eabd02ca3e123028 Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:26:02 -0500 Subject: [PATCH 15/24] Update qtconsole/mainwindow.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré <16781833+dalthviz@users.noreply.github.com> --- qtconsole/mainwindow.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qtconsole/mainwindow.py b/qtconsole/mainwindow.py index 970dad11..2a53b131 100644 --- a/qtconsole/mainwindow.py +++ b/qtconsole/mainwindow.py @@ -811,8 +811,13 @@ def set_syntax_style(self, syntax_style): self.active_frontend._style_sheet_changed() self.active_frontend.reset(clear=True) self.active_frontend._execute( - f"from IPython.core.ultratb import VerboseTB;" - "VerboseTB._tb_highlight_style = '{syntax_style}'", +f""" +from IPython.core.ultratb import VerboseTB +try: + VerboseTB._tb_highlight_style = '{syntax_style}' +except AttributeError: + get_ipython().run_line_magic('colors', '{colors}') +""", True) From f59396101bf7b308c57aa40755ad25f2f11b193d Mon Sep 17 00:00:00 2001 From: jsbautista Date: Sun, 16 Jun 2024 23:26:47 -0500 Subject: [PATCH 16/24] Fix test --- qtconsole/tests/test_jupyter_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtconsole/tests/test_jupyter_widget.py b/qtconsole/tests/test_jupyter_widget.py index 3849814b..43ab4a18 100644 --- a/qtconsole/tests/test_jupyter_widget.py +++ b/qtconsole/tests/test_jupyter_widget.py @@ -34,7 +34,7 @@ def test_stylesheet_changed(self): w = JupyterWidget(kind='rich') # By default, the background is light. White text is rendered as black - self.assertEqual(w._ansi_processor.get_color(15).name(), '#ffffff') + self.assertEqual(w._ansi_processor.get_color(15).name(), '#000000') # Change to a dark colorscheme. White text is rendered as white w.syntax_style = 'monokai' From 7a1a34c6ef82d57eaf1da17f6738150c421397f5 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Fri, 21 Jun 2024 17:23:19 -0500 Subject: [PATCH 17/24] Fix test --- qtconsole/tests/test_jupyter_widget.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qtconsole/tests/test_jupyter_widget.py b/qtconsole/tests/test_jupyter_widget.py index 43ab4a18..b32ff1d5 100644 --- a/qtconsole/tests/test_jupyter_widget.py +++ b/qtconsole/tests/test_jupyter_widget.py @@ -39,6 +39,9 @@ def test_stylesheet_changed(self): # Change to a dark colorscheme. White text is rendered as white w.syntax_style = 'monokai' self.assertEqual(w._ansi_processor.get_color(15).name(), '#ffffff') + + # Color code 40 + self.assertEqual(w._ansi_processor.get_color(40).name(), '#37D737') @pytest.mark.skipif(not sys.platform.startswith('linux'), reason="Works only on Linux") From 2e752c68abe44ce65325f81660c2cdfb95f3ddca Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Tue, 25 Jun 2024 13:16:18 -0500 Subject: [PATCH 18/24] Update qtconsole/mainwindow.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré <16781833+dalthviz@users.noreply.github.com> --- qtconsole/mainwindow.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/qtconsole/mainwindow.py b/qtconsole/mainwindow.py index 2a53b131..4d5c86b6 100644 --- a/qtconsole/mainwindow.py +++ b/qtconsole/mainwindow.py @@ -813,9 +813,11 @@ def set_syntax_style(self, syntax_style): self.active_frontend._execute( f""" from IPython.core.ultratb import VerboseTB -try: +if getattr(VerboseTB, 'tb_highlight_style', None) is not None: + VerboseTB.tb_highlight_style = '{syntax_style}' +elif getattr(VerboseTB, '_tb_highlight_style', None) is not None: VerboseTB._tb_highlight_style = '{syntax_style}' -except AttributeError: +else: get_ipython().run_line_magic('colors', '{colors}') """, True) From ff4388018748173cae8a3e44bfc0c7624d8e4fca Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 25 Jun 2024 13:18:50 -0500 Subject: [PATCH 19/24] Fix test --- qtconsole/tests/test_jupyter_widget.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qtconsole/tests/test_jupyter_widget.py b/qtconsole/tests/test_jupyter_widget.py index b32ff1d5..e5cc9f61 100644 --- a/qtconsole/tests/test_jupyter_widget.py +++ b/qtconsole/tests/test_jupyter_widget.py @@ -36,12 +36,15 @@ def test_stylesheet_changed(self): # By default, the background is light. White text is rendered as black self.assertEqual(w._ansi_processor.get_color(15).name(), '#000000') + # Color code 40 + self.assertEqual(w._ansi_processor.get_color(40).name(), '#37D737') + # Change to a dark colorscheme. White text is rendered as white w.syntax_style = 'monokai' self.assertEqual(w._ansi_processor.get_color(15).name(), '#ffffff') - # Color code 40 - self.assertEqual(w._ansi_processor.get_color(40).name(), '#37D737') + # Color code 40 with monokai + self.assertEqual(w._ansi_processor.get_color(40).name(), '#00d700') @pytest.mark.skipif(not sys.platform.startswith('linux'), reason="Works only on Linux") From dd5c5b3559322e40972b87425e3afe232fa515b0 Mon Sep 17 00:00:00 2001 From: jsbautista Date: Tue, 25 Jun 2024 14:10:21 -0500 Subject: [PATCH 20/24] Fix test --- qtconsole/tests/test_jupyter_widget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtconsole/tests/test_jupyter_widget.py b/qtconsole/tests/test_jupyter_widget.py index e5cc9f61..d1741ddd 100644 --- a/qtconsole/tests/test_jupyter_widget.py +++ b/qtconsole/tests/test_jupyter_widget.py @@ -37,7 +37,7 @@ def test_stylesheet_changed(self): self.assertEqual(w._ansi_processor.get_color(15).name(), '#000000') # Color code 40 - self.assertEqual(w._ansi_processor.get_color(40).name(), '#37D737') + self.assertEqual(w._ansi_processor.get_color(40).name(), '#00d700') # Change to a dark colorscheme. White text is rendered as white w.syntax_style = 'monokai' From cf7ad25f515e0ff8b151c369b094bf99296f1aee Mon Sep 17 00:00:00 2001 From: jsbautista <42411448+jsbautista@users.noreply.github.com> Date: Sun, 7 Jul 2024 09:44:44 -0500 Subject: [PATCH 21/24] Update qtconsole/ansi_code_processor.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Daniel Althviz Moré <16781833+dalthviz@users.noreply.github.com> --- qtconsole/ansi_code_processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qtconsole/ansi_code_processor.py b/qtconsole/ansi_code_processor.py index 69254c1d..1caec54c 100644 --- a/qtconsole/ansi_code_processor.py +++ b/qtconsole/ansi_code_processor.py @@ -300,7 +300,7 @@ def _parse_ansi_color(self, color, intensity): parsed_color = None if color < 16: # Adjust for intensity, if possible. - if intensity > 0: + if intensity > 0 and color < 8: color += 8 parsed_color = self.color_map.get(color, None) elif (color > 231): From d9ff409d32049b76a38b6100bd06c484d92f015d Mon Sep 17 00:00:00 2001 From: cunshunxia Date: Fri, 19 Jul 2024 11:05:01 +0800 Subject: [PATCH 22/24] remove unused requirement 'pyzmq' 'pyzmq' is not used in sources, which was pulled in in 79b0b02f3 Signed-off-by: cunshunxia --- requirements/environment.yml | 1 - setup.py | 1 - 2 files changed, 2 deletions(-) diff --git a/requirements/environment.yml b/requirements/environment.yml index a3d7a5dc..b3681528 100644 --- a/requirements/environment.yml +++ b/requirements/environment.yml @@ -9,7 +9,6 @@ dependencies: - jupyter_client - pygments - ipykernel -- pyzmq >=17.1 # For testing - coveralls diff --git a/setup.py b/setup.py index a5968e6d..47b8729e 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,6 @@ 'pygments', 'ipykernel>=4.1', # not a real dependency, but require the reference kernel 'qtpy>=2.4.0', - 'pyzmq>=17.1', 'packaging' ], extras_require = { From dbf756208e60cb3a95cd85b621ae74c4f5c8ac90 Mon Sep 17 00:00:00 2001 From: TheMatt2 Date: Sat, 27 Jul 2024 01:24:22 -0400 Subject: [PATCH 23/24] Format suggestions from code review --- qtconsole/console_widget.py | 20 +++++++++++--------- qtconsole/tests/test_00_console_widget.py | 6 +++--- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/qtconsole/console_widget.py b/qtconsole/console_widget.py index 2b1f7435..ebb8d23a 100644 --- a/qtconsole/console_widget.py +++ b/qtconsole/console_widget.py @@ -851,12 +851,12 @@ def paste(self, mode=QtGui.QClipboard.Clipboard): self._insert_plain_text_into_buffer(cursor, dedent(text)) - def print_(self, printer = None): + def print_(self, printer=None): """ Print the contents of the ConsoleWidget to the specified QPrinter. """ - if (not printer): + if not printer: printer = QtPrintSupport.QPrinter() - if(QtPrintSupport.QPrintDialog(printer).exec_() != QtPrintSupport.QPrintDialog.Accepted): + if QtPrintSupport.QPrintDialog(printer).exec_() != QtPrintSupport.QPrintDialog.Accepted: return self._control.print_(printer) @@ -1039,7 +1039,7 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs): result = insert(cursor, input, *args, **kwargs) # Remove insert mode tag - if hasattr(cursor, "_insert_mode"): + if hasattr(cursor, '_insert_mode'): del cursor._insert_mode return result @@ -1077,7 +1077,7 @@ def _clear_temporary_buffer(self): # Select and remove all text below the input buffer. cursor = self._get_prompt_cursor() prompt = self._continuation_prompt.lstrip() - if(self._temp_buffer_filled): + if self._temp_buffer_filled: self._temp_buffer_filled = False while cursor.movePosition(QtGui.QTextCursor.NextBlock): temp_cursor = QtGui.QTextCursor(cursor) @@ -1689,13 +1689,15 @@ def _event_filter_page_keypress(self, event): return False def _on_flush_pending_stream_timer(self): - """ Flush the pending stream output and change the - prompt position appropriately. + """ Flush pending text into the widget on console timer trigger. """ self._flush_pending_stream() def _flush_pending_stream(self): - """ Flush out pending text into the widget. """ + """ Flush pending text into the widget. Only applies to text that is pending + when the console is in the running state. Text printed when console is + not running is shown immediately, and does not wait to be flushed. + """ text = self._pending_insert_text self._pending_insert_text = [] buffer_size = self._control.document().maximumBlockCount() @@ -2430,7 +2432,7 @@ def _readline(self, prompt='', callback=None, password=False): self._reading = True if password: - self._show_prompt('Warning: QtConsole does not support password mode, '\ + self._show_prompt('Warning: QtConsole does not support password mode, ' 'the text you type will be visible.', newline=True) if 'ipdb' not in prompt.lower(): diff --git a/qtconsole/tests/test_00_console_widget.py b/qtconsole/tests/test_00_console_widget.py index fa23b48b..c9b571e8 100644 --- a/qtconsole/tests/test_00_console_widget.py +++ b/qtconsole/tests/test_00_console_widget.py @@ -391,7 +391,7 @@ def test_print_carriage_return(self): '\r\n'] for text in test_inputs: - w._append_plain_text(text, before_prompt = True) + w._append_plain_text(text, before_prompt=True) w._flush_pending_stream() # emulate text being flushed self.assert_text_equal(w._get_cursor(), @@ -403,7 +403,7 @@ def test_print_carriage_return(self): '\r', 'Bar', '\n'] for text in test_inputs: - w._append_plain_text(text, before_prompt = False) + w._append_plain_text(text, before_prompt=False) w._flush_pending_stream() # emulate text being flushed self.assert_text_equal(w._get_cursor(), @@ -486,7 +486,7 @@ def test_prompt_cursors(self): w._prompt_pos - len(w._prompt)) # insert some text before the prompt - w._append_plain_text('line', before_prompt = True) + w._append_plain_text('line', before_prompt=True) self.assertEqual(w._prompt_pos, w._get_end_pos()) self.assertEqual(w._append_before_prompt_pos, w._prompt_pos - len(w._prompt)) From fe5f0c44a7ca4a767744e682b4b40df34e8446bb Mon Sep 17 00:00:00 2001 From: dalthviz <16781833+dalthviz@users.noreply.github.com> Date: Thu, 8 Aug 2024 14:53:54 -0500 Subject: [PATCH 24/24] Fix workflow coverage upload to coveralls step --- .github/workflows/linux-tests.yml | 3 ++- .gitignore | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 13cc3960..5bcb4003 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -23,6 +23,7 @@ jobs: PYTHON_VERSION: ${{ matrix.PYTHON_VERSION }} RUNNER_OS: 'ubuntu' COVERALLS_REPO_TOKEN: XWVhJf2AsO7iouBLuCsh0pPhwHy81Uz1v + COVERALLS_SERVICE_NAME: 'github-actions' strategy: fail-fast: false matrix: @@ -76,7 +77,7 @@ jobs: env: QT_API: ${{ matrix.QT_LIB }} PYTEST_QT_API: ${{ matrix.QT_LIB }} - - name: Upload coverage to Codecov + - name: Upload coverage to coveralls if: matrix.PYTHON_VERSION == '3.8' shell: bash -l {0} run: coveralls diff --git a/.gitignore b/.gitignore index c18177c9..7b496393 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ __pycache__ .coverage .pytest_cache .vscode +.spyproject