Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Carriage Return Handling in QtConsole #607

Merged
merged 10 commits into from
Aug 12, 2024

Conversation

TheMatt2
Copy link
Contributor

@TheMatt2 TheMatt2 commented May 27, 2024

Fix the handling of carriage return so it is not ignored at the end of a print line.

This allows for progress bars that rely on adding a carriage return at the end of the line such as:

print("Progress 55%", end = "\r")

tl;dr
Preserve position of cursor so ANSI commands such as \r that modify position will preserve the position they specified over multiple calls. This is particularly relevant if time passes between print calls (~100 ms).

Explanation

First of all, when you print() or call sys.stdout.write() in QtConsole, this ultimately causes the text to be passed to the frontend widget.

def append_stream(self, text):

This will lead to the text by passed to the console widget, which will call the ANSI Processor in:

def _insert_plain_text(self, cursor, text, flush=False):

The ANSI preprocessor already handles the carriage return, so code that that uses the carriage return in the middle of a print already worked.

print("Hello\rWorld")
World

This will even work for calls from multiple prints, but this only works because the text is internally buffered, and recombined so print("Hello", end = "\r"); print("World") is all combined into the text "Hello\rWorld" by the time _insert_plain_text()` is called.

However, _insert_plain_text() and _flush_pending_stream() both force the cursor position to the end of the line at the end of the call. This means that a '\r' at the end of a line is effectively ignored, if something stops the text from being combined back together, such as more than ~100 ms passing between prints.

Time

This PR fixes this issue by updating the cursor position to the place it was put to by _insert_plain_text().

Before this gets merged, I do want to make sure there are no odd situations where the cursor now not being the end of text leads to issues.

Behavior Change

Before

Before

After

After

Notes

Related Github Issues

This does not exactly address #350, not adding support for any new ANSI codes, just fixing the existing support for '\r'

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.
@TheMatt2
Copy link
Contributor Author

Added regression test. Needed to run it manually, as the test_scroll() keeps failing (it worked once though)

@ccordoba12
Copy link
Collaborator

ccordoba12 commented May 28, 2024

Hey @TheMatt2, thanks for your contribution! Unfortunately, it doesn't seem the right approach if it breaks scrolling.

My suggestion is for you to rethink your changes so that test_scroll passes consistently.

@TheMatt2
Copy link
Contributor Author

Hey @TheMatt2, thanks for your contribution! Unfortunately, it doesn't seem the right approach if it breaks scrolling.

My suggestion is for you to rethink your changes so that test_scroll passes consistently.

Let me rephrase. test_scroll() is breaking for me, without my changes. I don't think my changes are impacting scroll, that test is failing on its own.

Working on it, to see if I can figure it out, and go through the CI results.

@ccordoba12
Copy link
Collaborator

Ok, sorry for the misunderstanding. Since I haven't seen that test failing before, I assumed it was do to your changes.

@TheMatt2
Copy link
Contributor Author

So it seems the following error the CI reports is introduced by the PR, I'm looking into what the implications are.

FAILED qtconsole/tests/test_00_console_widget.py::test_scroll[True] - assert 1930 == 1170

The error I was referring to previously, is in my testing test_00_console_widget.py::test_scroll() hangs forever on MacOS, unrelated to the changes in this PR.

And on Windows, test_00_console_widget.py::test_scroll() is failing for me with an unrelated exception.

And it appears these failures were masking that there is an issue in this PR that needs to be addressed.
Suppose I may need to open issues for there others, but one thing at a time.

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.
Need to set _executing = True for test to work.
@TheMatt2 TheMatt2 marked this pull request as draft May 31, 2024 04:52
@TheMatt2
Copy link
Contributor Author

Updated approach to not break test_scroll(). While I am not sure exactly why this broke the test, the issue was printing was screwed up if the user clicked around during printing (evidently setTextCursor()` does not work how I thought it did.)

@TheMatt2
Copy link
Contributor Author

Change to draft, as test_carriage_return fails if w._executing = False. Want to resolve this before merging.

@TheMatt2
Copy link
Contributor Author

TheMatt2 commented Jun 8, 2024

After much revision, found a way to deal with this that solves the issue.

The issue was text is added using _insert_mode in cursor_widget.py if we are executing . The problem is insert mode breaks the \r handling, as we never "swallow" characters, only insert.

Solution is to only use _insert mode if text is being inserted on the line that holds the current prompt, as that is the only time we truly need this behavior.

This has been added to the test cases.

The way you could trigger this corner case where you still need insert mode is something like this

image

@TheMatt2
Copy link
Contributor Author

TheMatt2 commented Jun 8, 2024

At this point, the issue is solved, and all test cases pass, but I have one last edge case I want to chase down before removing the draft tag.

@TheMatt2
Copy link
Contributor Author

TheMatt2 commented Jun 9, 2024

Added fix for an edge case where switching between printing before the prompt, and printing after the prompt caused the self._prompt_sep to be printed in the wrong position.

All tests past... but there's a remaining issue if the prompt is a blank character... i.e. input(''). Still a draft.

@TheMatt2
Copy link
Contributor Author

TheMatt2 commented Jun 9, 2024

So if you run the following code fragment, you can hang the GUI, and it prints the error QTextCursor::setPosition: Position '429' out of range

This occurs on the main branch right now, not actually caused by any change I've made here. Attempting a fix.

image

Make sure prompt is correctly setup even if the prompt is literally a blank string, as could be set using input()
Changes actually broke all input() handling. Reverting.

This reverts commit 50bb0fd.
@TheMatt2
Copy link
Contributor Author

TheMatt2 commented Jun 9, 2024

So I've confirmed this is a separate issue that doesn't require any changes to this PR.
I tried a fix for this, but it that fix actually broke like it is actually going to be a little involved.

Before digging too much into that, I would actually like some feedback on the change in this PR, as this seems to now all being working (took a while though).

@TheMatt2
Copy link
Contributor Author

@ccordoba12 Thoughts?

@dalthviz dalthviz added this to the 5.6.0 milestone Jul 25, 2024
@dalthviz
Copy link
Collaborator

Hi @TheMatt2 thank you for all you work here and sorry for the late response! Could it be possible for you to merge with latest main so this can get green CI (some tests fixes have been done over there)? Also, I gave a quick check to this PR manually and seems like things are working as expected 👍 Will try to do a more in depth code review now but if there are any specific changes that you think need more attention let us know!

@ccordoba12
Copy link
Collaborator

@ccordoba12 Thoughts?

Sorry for dropping the ball here. @dalthviz will take it from here, he's also a maintainer of this repo.

Copy link
Collaborator

@dalthviz dalthviz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you again @TheMatt2 for your work here! Left a comment to update a method docstring and several suggestions for format nitpicks. Other than that this LGTM 👍

qtconsole/console_widget.py Show resolved Hide resolved
qtconsole/console_widget.py Outdated Show resolved Hide resolved
qtconsole/console_widget.py Show resolved Hide resolved
qtconsole/frontend_widget.py Show resolved Hide resolved
qtconsole/tests/test_00_console_widget.py Outdated Show resolved Hide resolved
qtconsole/tests/test_00_console_widget.py Outdated Show resolved Hide resolved
qtconsole/tests/test_00_console_widget.py Outdated Show resolved Hide resolved
@TheMatt2
Copy link
Contributor Author

Merged in main and made format changes. None of the changes in main look like they impact this code directly.

Appreciate you guys taking a look at this.

As for a note on the readiness of this code, I think this code is tested and improves on the current behavior.
The sharpest point that requires some thought is thinking carefully about situations where the "prompt" for the user is a blank string, such as with input(''). Since the code uses prompt_start == prompt_end to signify there is no prompt at the moment, the code has to be carefully crafted to make sure this isn't an issue. This kind of issue is what is causing #610. For this MR, I made of point of structuring the code in such a way there this should not pose an issue for any situation that the original code could already handle.

Copy link
Collaborator

@dalthviz dalthviz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again thank you @TheMatt2 ! This LGTM 👍

Just in case, do you have any further feedback @ccordoba12 ?

Copy link
Collaborator

@ccordoba12 ccordoba12 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small suggestions for you @TheMatt2, the rest looks good to me.

qtconsole/console_widget.py Show resolved Hide resolved
qtconsole/console_widget.py Show resolved Hide resolved
qtconsole/console_widget.py Show resolved Hide resolved
@dalthviz
Copy link
Collaborator

dalthviz commented Aug 8, 2024

@ccordoba12 to move this forward, is okay if we merge this as it is and then over a follow up PR I work on the style suggestions you left here?

@ccordoba12
Copy link
Collaborator

Sure, go ahead and merge this one.

@dalthviz dalthviz merged commit 564dcf9 into jupyter:main Aug 12, 2024
22 checks passed
@TheMatt2 TheMatt2 deleted the fix-carriage-return branch September 5, 2024 12:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants