Skip to content

Commit 564dcf9

Browse files
authored
Merge pull request #607 from TheMatt2/fix-carriage-return
Fix Carriage Return Handling in QtConsole
2 parents 03cc68a + dbf7562 commit 564dcf9

File tree

3 files changed

+98
-24
lines changed

3 files changed

+98
-24
lines changed

qtconsole/console_widget.py

+59-23
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ def __init__(self, parent=None, **kw):
297297
self._reading_callback = None
298298
self._tab_width = 4
299299

300+
# Cursor position of where to insert text.
301+
# Control characters allow this to move around on the current line.
302+
self._insert_text_cursor = self._control.textCursor()
303+
300304
# List of strings pending to be appended as plain text in the widget.
301305
# The text is not immediately inserted when available to not
302306
# choke the Qt event loop with paint events for the widget in
@@ -695,6 +699,9 @@ def do_execute(self, source, complete, indent):
695699
# effect when using a QTextEdit. I believe this is a Qt bug.
696700
self._control.moveCursor(QtGui.QTextCursor.End)
697701

702+
# Advance where text is inserted
703+
self._insert_text_cursor.movePosition(QtGui.QTextCursor.End)
704+
698705
def export_html(self):
699706
""" Shows a dialog to export HTML/XML in various formats.
700707
"""
@@ -712,6 +719,9 @@ def _finalize_input_request(self):
712719
self._append_before_prompt_cursor.setPosition(
713720
self._get_end_cursor().position())
714721

722+
self._insert_text_cursor.setPosition(
723+
self._get_end_cursor().position())
724+
715725
# The maximum block count is only in effect during execution.
716726
# This ensures that _prompt_pos does not become invalid due to
717727
# text truncation.
@@ -841,12 +851,12 @@ def paste(self, mode=QtGui.QClipboard.Clipboard):
841851

842852
self._insert_plain_text_into_buffer(cursor, dedent(text))
843853

844-
def print_(self, printer = None):
854+
def print_(self, printer=None):
845855
""" Print the contents of the ConsoleWidget to the specified QPrinter.
846856
"""
847-
if (not printer):
857+
if not printer:
848858
printer = QtPrintSupport.QPrinter()
849-
if(QtPrintSupport.QPrintDialog(printer).exec_() != QtPrintSupport.QPrintDialog.Accepted):
859+
if QtPrintSupport.QPrintDialog(printer).exec_() != QtPrintSupport.QPrintDialog.Accepted:
850860
return
851861
self._control.print_(printer)
852862

@@ -998,18 +1008,40 @@ def _append_custom(self, insert, input, before_prompt=False, *args, **kwargs):
9981008
current prompt, if there is one.
9991009
"""
10001010
# Determine where to insert the content.
1001-
cursor = self._control.textCursor()
1011+
cursor = self._insert_text_cursor
10021012
if before_prompt and (self._reading or not self._executing):
10031013
self._flush_pending_stream()
1004-
cursor._insert_mode=True
1005-
cursor.setPosition(self._append_before_prompt_pos)
1014+
1015+
# Jump to before prompt, if there is one
1016+
if cursor.position() >= self._append_before_prompt_pos \
1017+
and self._append_before_prompt_pos != self._get_end_pos():
1018+
cursor.setPosition(self._append_before_prompt_pos)
1019+
1020+
# If we appending on the same line as the prompt, use insert mode
1021+
# If so, the character at self._append_before_prompt_pos will not be a newline
1022+
cursor.movePosition(QtGui.QTextCursor.Right,
1023+
QtGui.QTextCursor.KeepAnchor)
1024+
if cursor.selection().toPlainText() != '\n':
1025+
cursor._insert_mode = True
1026+
cursor.movePosition(QtGui.QTextCursor.Left)
10061027
else:
1028+
# Insert at current printing point
1029+
# If cursor is before prompt jump to end, but only if there
1030+
# is a prompt (before_prompt_pos != end)
1031+
if cursor.position() <= self._append_before_prompt_pos \
1032+
and self._append_before_prompt_pos != self._get_end_pos():
1033+
cursor.movePosition(QtGui.QTextCursor.End)
1034+
10071035
if insert != self._insert_plain_text:
10081036
self._flush_pending_stream()
1009-
cursor.movePosition(QtGui.QTextCursor.End)
10101037

10111038
# Perform the insertion.
10121039
result = insert(cursor, input, *args, **kwargs)
1040+
1041+
# Remove insert mode tag
1042+
if hasattr(cursor, '_insert_mode'):
1043+
del cursor._insert_mode
1044+
10131045
return result
10141046

10151047
def _append_block(self, block_format=None, before_prompt=False):
@@ -1045,7 +1077,7 @@ def _clear_temporary_buffer(self):
10451077
# Select and remove all text below the input buffer.
10461078
cursor = self._get_prompt_cursor()
10471079
prompt = self._continuation_prompt.lstrip()
1048-
if(self._temp_buffer_filled):
1080+
if self._temp_buffer_filled:
10491081
self._temp_buffer_filled = False
10501082
while cursor.movePosition(QtGui.QTextCursor.NextBlock):
10511083
temp_cursor = QtGui.QTextCursor(cursor)
@@ -1657,24 +1689,23 @@ def _event_filter_page_keypress(self, event):
16571689
return False
16581690

16591691
def _on_flush_pending_stream_timer(self):
1660-
""" Flush the pending stream output and change the
1661-
prompt position appropriately.
1692+
""" Flush pending text into the widget on console timer trigger.
16621693
"""
1663-
cursor = self._control.textCursor()
1664-
cursor.movePosition(QtGui.QTextCursor.End)
16651694
self._flush_pending_stream()
1666-
cursor.movePosition(QtGui.QTextCursor.End)
16671695

16681696
def _flush_pending_stream(self):
1669-
""" Flush out pending text into the widget. """
1697+
""" Flush pending text into the widget. Only applies to text that is pending
1698+
when the console is in the running state. Text printed when console is
1699+
not running is shown immediately, and does not wait to be flushed.
1700+
"""
16701701
text = self._pending_insert_text
16711702
self._pending_insert_text = []
16721703
buffer_size = self._control.document().maximumBlockCount()
16731704
if buffer_size > 0:
16741705
text = self._get_last_lines_from_list(text, buffer_size)
16751706
text = ''.join(text)
16761707
t = time.time()
1677-
self._insert_plain_text(self._get_end_cursor(), text, flush=True)
1708+
self._insert_plain_text(self._insert_text_cursor, text, flush=True)
16781709
# Set the flush interval to equal the maximum time to update text.
16791710
self._pending_text_flush_interval.setInterval(
16801711
int(max(100, (time.time() - t) * 1000))
@@ -2093,12 +2124,12 @@ def _insert_plain_text(self, cursor, text, flush=False):
20932124

20942125
if (self._executing and not flush and
20952126
self._pending_text_flush_interval.isActive() and
2096-
cursor.position() == self._get_end_pos()):
2127+
cursor.position() == self._insert_text_cursor.position()):
20972128
# Queue the text to insert in case it is being inserted at end
20982129
self._pending_insert_text.append(text)
20992130
if buffer_size > 0:
21002131
self._pending_insert_text = self._get_last_lines_from_list(
2101-
self._pending_insert_text, buffer_size)
2132+
self._pending_insert_text, buffer_size)
21022133
return
21032134

21042135
if self._executing and not self._pending_text_flush_interval.isActive():
@@ -2123,7 +2154,7 @@ def _insert_plain_text(self, cursor, text, flush=False):
21232154
cursor.select(QtGui.QTextCursor.Document)
21242155
remove = True
21252156
if act.area == 'line':
2126-
if act.erase_to == 'all':
2157+
if act.erase_to == 'all':
21272158
cursor.select(QtGui.QTextCursor.LineUnderCursor)
21282159
remove = True
21292160
elif act.erase_to == 'start':
@@ -2137,7 +2168,7 @@ def _insert_plain_text(self, cursor, text, flush=False):
21372168
QtGui.QTextCursor.EndOfLine,
21382169
QtGui.QTextCursor.KeepAnchor)
21392170
remove = True
2140-
if remove:
2171+
if remove:
21412172
nspace=cursor.selectionEnd()-cursor.selectionStart() if fill else 0
21422173
cursor.removeSelectedText()
21432174
if nspace>0: cursor.insertText(' '*nspace) # replace text by space, to keep cursor position as specified
@@ -2174,15 +2205,17 @@ def _insert_plain_text(self, cursor, text, flush=False):
21742205
# simulate replacement mode
21752206
if substring is not None:
21762207
format = self._ansi_processor.get_format()
2177-
if not (hasattr(cursor,'_insert_mode') and cursor._insert_mode):
2208+
2209+
# Note that using _insert_mode means the \r ANSI sequence will not swallow characters.
2210+
if not (hasattr(cursor, '_insert_mode') and cursor._insert_mode):
21782211
pos = cursor.position()
21792212
cursor2 = QtGui.QTextCursor(cursor) # self._get_line_end_pos() is the previous line, don't use it
21802213
cursor2.movePosition(QtGui.QTextCursor.EndOfLine)
21812214
remain = cursor2.position() - pos # number of characters until end of line
21822215
n=len(substring)
21832216
swallow = min(n, remain) # number of character to swallow
2184-
cursor.setPosition(pos+swallow,QtGui.QTextCursor.KeepAnchor)
2185-
cursor.insertText(substring,format)
2217+
cursor.setPosition(pos + swallow, QtGui.QTextCursor.KeepAnchor)
2218+
cursor.insertText(substring, format)
21862219
else:
21872220
cursor.insertText(text)
21882221
cursor.endEditBlock()
@@ -2399,7 +2432,7 @@ def _readline(self, prompt='', callback=None, password=False):
23992432

24002433
self._reading = True
24012434
if password:
2402-
self._show_prompt('Warning: QtConsole does not support password mode, '\
2435+
self._show_prompt('Warning: QtConsole does not support password mode, '
24032436
'the text you type will be visible.', newline=True)
24042437

24052438
if 'ipdb' not in prompt.lower():
@@ -2531,6 +2564,9 @@ def _show_prompt(self, prompt=None, html=False, newline=True,
25312564
if move_forward:
25322565
self._append_before_prompt_cursor.setPosition(
25332566
self._append_before_prompt_cursor.position() + 1)
2567+
else:
2568+
# cursor position was 0, set before prompt cursor
2569+
self._append_before_prompt_cursor.setPosition(0)
25342570
self._prompt_started()
25352571

25362572
#------ Signal handlers ----------------------------------------------------

qtconsole/frontend_widget.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ def restart_kernel(self, message, now=False):
728728

729729
def append_stream(self, text):
730730
"""Appends text to the output stream."""
731-
self._append_plain_text(text, before_prompt=True)
731+
self._append_plain_text(text, before_prompt = True)
732732

733733
def flush_clearoutput(self):
734734
"""If a clearoutput is pending, execute it."""

qtconsole/tests/test_00_console_widget.py

+38
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,44 @@ def test_erase_in_line(self):
371371
# clear all the text
372372
cursor.insertText('')
373373

374+
def test_print_carriage_return(self):
375+
""" Test that overwriting the current line works as intended,
376+
before and after the cursor prompt.
377+
"""
378+
w = ConsoleWidget()
379+
380+
# Show a prompt
381+
w._prompt = "prompt>"
382+
w._prompt_sep = "\n"
383+
384+
w._show_prompt()
385+
self.assert_text_equal(w._get_cursor(), '\u2029prompt>')
386+
387+
test_inputs = ['Hello\n', 'World\r',
388+
'*' * 10, '\r',
389+
'0', '1', '2', '3', '4',
390+
'5', '6', '7', '8', '9',
391+
'\r\n']
392+
393+
for text in test_inputs:
394+
w._append_plain_text(text, before_prompt=True)
395+
w._flush_pending_stream() # emulate text being flushed
396+
397+
self.assert_text_equal(w._get_cursor(),
398+
"Hello\u20290123456789\u2029\u2029prompt>")
399+
400+
# Print after prompt
401+
w._executing = True
402+
test_inputs = ['\nF', 'o', 'o',
403+
'\r', 'Bar', '\n']
404+
405+
for text in test_inputs:
406+
w._append_plain_text(text, before_prompt=False)
407+
w._flush_pending_stream() # emulate text being flushed
408+
409+
self.assert_text_equal(w._get_cursor(),
410+
"Hello\u20290123456789\u2029\u2029prompt>\u2029Bar\u2029")
411+
374412
def test_link_handling(self):
375413
noButton = QtCore.Qt.NoButton
376414
noButtons = QtCore.Qt.NoButton

0 commit comments

Comments
 (0)