GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-119517: Fixes for pasting in pyrepl by godlygeek · Pull Request #120253 · python/cpython

Conversation

@godlygeek

@godlygeek godlygeek commented Jun 8, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Drastically improve the performance of pyrepl when pasting large amounts of text.

CC: @pablogsal

  • Issue: Odd behavior from pasting large text in the new REPL #119517

Do-over of #120251 which I made a mess of 😓.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
godlygeek added 10 commits June 7, 2024 23:01
@godlygeek
Remove pyrepl's optimization for self-insert
6135335
This will be replaced by a less specialized optimization.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Use line-buffering when pyrepl echoes pastes
c568c89
Previously echoing was totally suppressed until the entire command had
been pasted and the terminal ended paste mode, but this gives the user
no feedback to indicate that an operation is in progress. Drawing
something to the screen once per line strikes a balance between
perceived responsiveness and performance.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Remove dead code from pyrepl
0de6080
`msg_at_bottom` is always true.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Speed up pyrepl's screen rendering computation
651cf93
The Reader in pyrepl doesn't hold a complete representation of the
screen area being drawn as persistent state. Instead, it recomputes it,
on each keypress. This is fast enough for a few hundred bytes, but
incredibly slow as the input buffer grows into the kilobytes (likely
because of pasting).

Rather than making some expensive and expansive changes to the repl's
internal representation of the screen, add some caching: remember some
data from one refresh to the next about what was drawn to the screen
and, if we don't find anything that has invalidated the results that
were computed last time around, reuse them. To keep this caching as
simple as possible, all we'll do is look for lines in the buffer that
were above the cursor the last time we were asked to update the screen,
and that are still above the cursor now. We assume that nothing can
affect a line that comes before both the old and new cursor location
without us being informed. Based on this assumption, we can reuse old
lines, which drastically speeds up the overwhelmingly common case where
the user is typing near the end of the buffer.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Speed up pyrepl prompt drawing
0df62c8
Cache the `can_colorize()` call rather than repeatedly recomputing it.
This call looks up an environment variable, and is called once per
character typed at the REPL. The environment variable lookup shows up as
a hot spot when profiling, and we don't expect this to change while the
REPL is running.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Speed up pasting multiple lines into the REPL
a4d3720
Previously, we were checking whether the command should be accepted each
time a line break was encountered, but that's not the expected behavior.
In bracketed paste mode, we expect everything pasted to be part of
a single block of code, and encountering a newline shouldn't behave like
a user pressing <Enter> to execute a command. The user should always
have a chance to review the pasted command before running it.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Use a read buffer for input in pyrepl
0c4a0cc
Previously we were reading one byte at a time, which causes much slower
IO than necessary. Instead, read in chunks, processing previously read
data before asking for more.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Optimize finding width of a single character
d50871f
`wlen` finds the width of a multi-character string by adding up the
width of each character, and then subtracting the width of any escape
sequences. It's often called for single character strings, however,
which can't possibly contain escape sequences. Optimize for that case.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Optimize disp_str for ASCII characters
07b3302
Since every ASCII character is known to display as single width, we can
avoid not only the Unicode data lookup in `disp_str` but also the one
hidden in `str_width` for them.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
Speed up cursor movements in long pyrepl commands
54a8987
When the current pyrepl command buffer contains many lines, scrolling up
becomes slow. We have optimizations in place to reuse lines above the
cursor position from one refresh to the next, but don't currently try to
reuse lines below the cursor position in the same way, so we wind up
with quadratic behavior where all lines of the buffer below the cursor
are recomputed each time the cursor moves up another line.

Optimize this by only computing one screen's worth of lines beyond the
cursor position. Any lines beyond that can't possibly be shown by the
console, and bounding this makes scrolling up have linear time
complexity instead.

Signed-off-by: Matt Wozniski <[email protected]>
@godlygeek
godlygeek requested review from ambv, lysnikolaou and pablogsal as code owners June 8, 2024 03:02
@bedevere-app bedevere-app Bot mentioned this pull request Jun 8, 2024
Odd behavior from pasting large text in the new REPL #119517
Closed
@bedevere-app bedevere-app Bot added the awaiting review label Jun 8, 2024
@pablogsal

pablogsal commented Jun 10, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

@treyhunner can you give this PR a test? This PR doesn't do lazy refresh approach and in my testing, makes pasting as fast as the old REPL when pasting the full Frankenstein text

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@treyhunner

treyhunner commented Jun 10, 2024

Copy link
Copy Markdown
Member

@pablogsal This works great for me! 👍

I was able to paste the entire text of Frankenstein in under 4 seconds! (was ~20 seconds before this change)

I can see the full output of the pasted text. This is particularly helpful when pasting just a few hundred lines of text.

This will be a very big improvement for my use case. 👏


The only (very slight) odd thing I noticed is the (paste) prompt and ... prompt are both used (with ... just at the end) in different parts of the pasted text. That leads to a slight misalignment due to (paste) being 4 characters longer than ....

Example snippet:

Python 3.14.0a0 (main, Jun 10 2024, 09:49:48) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
(paste) us_states = [
   [removed states]
(paste)     "Montana",
(paste)     "Nebraska",
(paste)     "Nevada",
...     "New Hampshire",
...     "New Jersey",
...     "New Mexico",
    [removed states]
>>>

As @pablogsal noted in #120234, IPython only shows the last terminal-sized chunk of output when scrolling up through history so this is a fairly minor issue.

However, I do anticipate that the current behavior will occasionally raise eyebrows from new Python users and may require a bit of hand-waving to explain it away.

Personally, I'd love to see ... used in paste mode (for consistency with the old REPL) because I plan to only ever use paste mode via bracketed paste. If lacking a distinct prompt for pastse mode might confuse some folks, maybe using (p) as the prompt (since it's 3 characters) might make sense.

Again though, this issue is minor and I doubt it would cause frequent confusion for new users.

Thank you for the big improvement @godlygeek.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal

pablogsal commented Jun 10, 2024

Copy link
Copy Markdown
Member

I think there is an easy fix for the prompt issue: just use the paste prompt on non-bracketed-paste mode (which is the original reason we added it, so a user typing manually can know that's in paste mode). WDYT @godlygeek ?

treyhunner reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@godlygeek

godlygeek commented Jun 10, 2024

Copy link
Copy Markdown
Contributor Author

I think that only showing the indicator when the user explicitly toggled on paste mode is a good idea. I also think that showing the indicator on every line is surprising. Even in manual paste mode, I'd only do a pasting indicator either on the line currently being edited, or (preferably) on the message line (where messages like "[ not unique ]" show up during tab completion).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal

pablogsal commented Jun 10, 2024

Copy link
Copy Markdown
Member

I'm happy to do either, so let's change it

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

pablogsal added 2 commits June 11, 2024 16:43
@pablogsal
Encapsulate the cached data in a class
0456835
@pablogsal
Do not show paste prompt on bracketed paste
ae1185a
@pablogsal

pablogsal commented Jun 11, 2024

Copy link
Copy Markdown
Member

@godlygeek I have made some small changes like encapsulating the cached data into a class and deactivated the prompt for paste in bracketed paste mode. I will land after the tests pass

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal
pablogsal enabled auto-merge (squash) June 11, 2024 15:44
@lysnikolaou lysnikolaou added the topic-repl Related to the interactive shell label Jun 11, 2024
@hugovk

hugovk commented Jun 11, 2024

Copy link
Copy Markdown
Member

This needs a NEWS file or skip-news label to auto-merge.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal pablogsal added skip news needs backport to 3.13 only security fixes labels Jun 11, 2024
pablogsal
pablogsal approved these changes Jun 11, 2024
View reviewed changes
@bedevere-app bedevere-app Bot removed the awaiting review label Jun 11, 2024
@bedevere-app bedevere-app Bot added the awaiting merge label Jun 11, 2024
@pablogsal
pablogsal merged commit 32a0fab into python:main Jun 11, 2024
@miss-islington-app

miss-islington-app Bot commented Jun 11, 2024

Copy link
Copy Markdown

Thanks @godlygeek for the PR, and @pablogsal for merging it 🌮🎉.. I'm working now to backport this PR to: 3.13.
🐍🍒⛏🤖

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the awaiting merge label Jun 11, 2024
miss-islington pushed a commit to miss-islington/cpython that referenced this pull request Jun 11, 2024
@godlygeek @pablogsal @miss-islington
pythongh-119517: Fixes for pasting in pyrepl (pythonGH-120253)
cfedcd1
* Remove pyrepl's optimization for self-insert

This will be replaced by a less specialized optimization.

* Use line-buffering when pyrepl echoes pastes

Previously echoing was totally suppressed until the entire command had
been pasted and the terminal ended paste mode, but this gives the user
no feedback to indicate that an operation is in progress. Drawing
something to the screen once per line strikes a balance between
perceived responsiveness and performance.

* Remove dead code from pyrepl

`msg_at_bottom` is always true.

* Speed up pyrepl's screen rendering computation

The Reader in pyrepl doesn't hold a complete representation of the
screen area being drawn as persistent state. Instead, it recomputes it,
on each keypress. This is fast enough for a few hundred bytes, but
incredibly slow as the input buffer grows into the kilobytes (likely
because of pasting).

Rather than making some expensive and expansive changes to the repl's
internal representation of the screen, add some caching: remember some
data from one refresh to the next about what was drawn to the screen
and, if we don't find anything that has invalidated the results that
were computed last time around, reuse them. To keep this caching as
simple as possible, all we'll do is look for lines in the buffer that
were above the cursor the last time we were asked to update the screen,
and that are still above the cursor now. We assume that nothing can
affect a line that comes before both the old and new cursor location
without us being informed. Based on this assumption, we can reuse old
lines, which drastically speeds up the overwhelmingly common case where
the user is typing near the end of the buffer.

* Speed up pyrepl prompt drawing

Cache the `can_colorize()` call rather than repeatedly recomputing it.
This call looks up an environment variable, and is called once per
character typed at the REPL. The environment variable lookup shows up as
a hot spot when profiling, and we don't expect this to change while the
REPL is running.

* Speed up pasting multiple lines into the REPL

Previously, we were checking whether the command should be accepted each
time a line break was encountered, but that's not the expected behavior.
In bracketed paste mode, we expect everything pasted to be part of
a single block of code, and encountering a newline shouldn't behave like
a user pressing <Enter> to execute a command. The user should always
have a chance to review the pasted command before running it.

* Use a read buffer for input in pyrepl

Previously we were reading one byte at a time, which causes much slower
IO than necessary. Instead, read in chunks, processing previously read
data before asking for more.

* Optimize finding width of a single character

`wlen` finds the width of a multi-character string by adding up the
width of each character, and then subtracting the width of any escape
sequences. It's often called for single character strings, however,
which can't possibly contain escape sequences. Optimize for that case.

* Optimize disp_str for ASCII characters

Since every ASCII character is known to display as single width, we can
avoid not only the Unicode data lookup in `disp_str` but also the one
hidden in `str_width` for them.

* Speed up cursor movements in long pyrepl commands

When the current pyrepl command buffer contains many lines, scrolling up
becomes slow. We have optimizations in place to reuse lines above the
cursor position from one refresh to the next, but don't currently try to
reuse lines below the cursor position in the same way, so we wind up
with quadratic behavior where all lines of the buffer below the cursor
are recomputed each time the cursor moves up another line.

Optimize this by only computing one screen's worth of lines beyond the
cursor position. Any lines beyond that can't possibly be shown by the
console, and bounding this makes scrolling up have linear time
complexity instead.

---------

(cherry picked from commit 32a0fab)

Co-authored-by: Matt Wozniski <[email protected]>
Signed-off-by: Matt Wozniski <[email protected]>
Co-authored-by: Pablo Galindo <[email protected]>
@bedevere-app

bedevere-app Bot commented Jun 11, 2024

Copy link
Copy Markdown

GH-120353 is a backport of this pull request to the 3.13 branch.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-app bedevere-app Bot removed the needs backport to 3.13 only security fixes label Jun 11, 2024
@pablogsal pablogsal mentioned this pull request Jun 11, 2024
gh-119517: Fix several issues when pasting lot of text in the REPL #120234
Closed
@lysnikolaou

lysnikolaou commented Jun 11, 2024

Copy link
Copy Markdown
Member

FWIW it still takes about 20 seconds to paste in the entire text of Frankenstein for me.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@godlygeek

godlygeek commented Jun 11, 2024

Copy link
Copy Markdown
Contributor Author

FWIW it still takes about 20 seconds to paste in the entire text of Frankenstein for me.

Did you check how long it takes in 3.13b1? Is this PR faster, or no improvement whatsoever?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal

pablogsal commented Jun 11, 2024

Copy link
Copy Markdown
Member

FWIW it still takes about 20 seconds to paste in the entire text of Frankenstein for me.

Maybe you are testing a debug version? Also I noticed that different terminals paste at different rates, so always check against PYTHON_BASIC_REPL=1

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@godlygeek

godlygeek commented Jun 11, 2024 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

I have made some small changes like encapsulating the cached data into a class

Thanks - I was waffling between keeping the changes localized vs doing that refactoring.

and deactivated the prompt for paste in bracketed paste mode

👍 I looked at what it would take to get the paste mode indicator to show up in the message line, as I had proposed. It doesn't work as nicely as I would have hoped: the Console class may decide not to show that line, if you're scrolled up, or if the cursor line is the bottom line of the screen. I may send another PR tweaking some things there to improve it, but ultimately it won't work as nicely as I had hoped, so just disabling it for bracketed pastes is definitely the quick fix.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lysnikolaou

lysnikolaou commented Jun 11, 2024

Copy link
Copy Markdown
Member

Did you check how long it takes in 3.13b1? Is this PR faster, or no improvement whatsoever?

Oh yeah, this is a significant improvement over 3.13b1. The old REPL is still faster and only takes ~6 seconds.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal

pablogsal commented Jun 11, 2024

Copy link
Copy Markdown
Member

he old REPL is still faster and only takes ~6 seconds.

Are you comparing using the same compiled version and setting PYTHON_BASIC_REPL on and off?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@godlygeek

godlygeek commented Jun 11, 2024

Copy link
Copy Markdown
Contributor Author

The old REPL is still faster and only takes ~6 seconds.

What terminal are you using? Does it supported bracketed pastes? One of the optimizations here only applies when bracketed paste mode is enabled.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lysnikolaou

lysnikolaou commented Jun 11, 2024

Copy link
Copy Markdown
Member

Are you comparing using the same compiled version and setting PYTHON_BASIC_REPL on and off?

Yes.

What terminal are you using? Does it supported bracketed pastes? One of the optimizations here only applies when bracketed paste mode is enabled.

I'm using iTerm2, which supports bracketed paste mode AFAIK.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

pablogsal added a commit that referenced this pull request Jun 11, 2024
@miss-islington @godlygeek @pablogsal
[3.13] gh-119517: Fixes for pasting in pyrepl (GH-120253) (#120353)
4936575
gh-119517: Fixes for pasting in pyrepl (GH-120253)

* Remove pyrepl's optimization for self-insert

This will be replaced by a less specialized optimization.

* Use line-buffering when pyrepl echoes pastes

Previously echoing was totally suppressed until the entire command had
been pasted and the terminal ended paste mode, but this gives the user
no feedback to indicate that an operation is in progress. Drawing
something to the screen once per line strikes a balance between
perceived responsiveness and performance.

* Remove dead code from pyrepl

`msg_at_bottom` is always true.

* Speed up pyrepl's screen rendering computation

The Reader in pyrepl doesn't hold a complete representation of the
screen area being drawn as persistent state. Instead, it recomputes it,
on each keypress. This is fast enough for a few hundred bytes, but
incredibly slow as the input buffer grows into the kilobytes (likely
because of pasting).

Rather than making some expensive and expansive changes to the repl's
internal representation of the screen, add some caching: remember some
data from one refresh to the next about what was drawn to the screen
and, if we don't find anything that has invalidated the results that
were computed last time around, reuse them. To keep this caching as
simple as possible, all we'll do is look for lines in the buffer that
were above the cursor the last time we were asked to update the screen,
and that are still above the cursor now. We assume that nothing can
affect a line that comes before both the old and new cursor location
without us being informed. Based on this assumption, we can reuse old
lines, which drastically speeds up the overwhelmingly common case where
the user is typing near the end of the buffer.

* Speed up pyrepl prompt drawing

Cache the `can_colorize()` call rather than repeatedly recomputing it.
This call looks up an environment variable, and is called once per
character typed at the REPL. The environment variable lookup shows up as
a hot spot when profiling, and we don't expect this to change while the
REPL is running.

* Speed up pasting multiple lines into the REPL

Previously, we were checking whether the command should be accepted each
time a line break was encountered, but that's not the expected behavior.
In bracketed paste mode, we expect everything pasted to be part of
a single block of code, and encountering a newline shouldn't behave like
a user pressing <Enter> to execute a command. The user should always
have a chance to review the pasted command before running it.

* Use a read buffer for input in pyrepl

Previously we were reading one byte at a time, which causes much slower
IO than necessary. Instead, read in chunks, processing previously read
data before asking for more.

* Optimize finding width of a single character

`wlen` finds the width of a multi-character string by adding up the
width of each character, and then subtracting the width of any escape
sequences. It's often called for single character strings, however,
which can't possibly contain escape sequences. Optimize for that case.

* Optimize disp_str for ASCII characters

Since every ASCII character is known to display as single width, we can
avoid not only the Unicode data lookup in `disp_str` but also the one
hidden in `str_width` for them.

* Speed up cursor movements in long pyrepl commands

When the current pyrepl command buffer contains many lines, scrolling up
becomes slow. We have optimizations in place to reuse lines above the
cursor position from one refresh to the next, but don't currently try to
reuse lines below the cursor position in the same way, so we wind up
with quadratic behavior where all lines of the buffer below the cursor
are recomputed each time the cursor moves up another line.

Optimize this by only computing one screen's worth of lines beyond the
cursor position. Any lines beyond that can't possibly be shown by the
console, and bounding this makes scrolling up have linear time
complexity instead.

---------

(cherry picked from commit 32a0fab)

Signed-off-by: Matt Wozniski <[email protected]>
Co-authored-by: Matt Wozniski <[email protected]>
Co-authored-by: Pablo Galindo <[email protected]>
@pablogsal

pablogsal commented Jun 11, 2024

Copy link
Copy Markdown
Member

Wow, in my Mac, using Iterm2 and a non-debug build, it takes exactly the same to paste the entire text. I wonder what's going on here....

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@pablogsal

pablogsal commented Jun 11, 2024

Copy link
Copy Markdown
Member

@lysnikolaou If you go to Iterm > Edit > Paste special > Paste faster. What value you get?

Screenshot 2024-06-11 at 18 28 41
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@lysnikolaou

lysnikolaou commented Jun 11, 2024

Copy link
Copy Markdown
Member

If you go to Iterm > Edit > Paste special > Paste faster. What value you get?

I'm getting 173 kB/sec.

I tested with a non-debug --enable-optimizations build and it now takes about the same, ~7 seconds with both the old and the new REPL. So false alarm.

Thanks again @godlygeek!

godlygeek and ambv reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mrahtz pushed a commit to mrahtz/cpython that referenced this pull request Jun 30, 2024
@godlygeek @pablogsal @mrahtz
pythongh-119517: Fixes for pasting in pyrepl (python#120253)
7484276
* Remove pyrepl's optimization for self-insert

This will be replaced by a less specialized optimization.

* Use line-buffering when pyrepl echoes pastes

Previously echoing was totally suppressed until the entire command had
been pasted and the terminal ended paste mode, but this gives the user
no feedback to indicate that an operation is in progress. Drawing
something to the screen once per line strikes a balance between
perceived responsiveness and performance.

* Remove dead code from pyrepl

`msg_at_bottom` is always true.

* Speed up pyrepl's screen rendering computation

The Reader in pyrepl doesn't hold a complete representation of the
screen area being drawn as persistent state. Instead, it recomputes it,
on each keypress. This is fast enough for a few hundred bytes, but
incredibly slow as the input buffer grows into the kilobytes (likely
because of pasting).

Rather than making some expensive and expansive changes to the repl's
internal representation of the screen, add some caching: remember some
data from one refresh to the next about what was drawn to the screen
and, if we don't find anything that has invalidated the results that
were computed last time around, reuse them. To keep this caching as
simple as possible, all we'll do is look for lines in the buffer that
were above the cursor the last time we were asked to update the screen,
and that are still above the cursor now. We assume that nothing can
affect a line that comes before both the old and new cursor location
without us being informed. Based on this assumption, we can reuse old
lines, which drastically speeds up the overwhelmingly common case where
the user is typing near the end of the buffer.

* Speed up pyrepl prompt drawing

Cache the `can_colorize()` call rather than repeatedly recomputing it.
This call looks up an environment variable, and is called once per
character typed at the REPL. The environment variable lookup shows up as
a hot spot when profiling, and we don't expect this to change while the
REPL is running.

* Speed up pasting multiple lines into the REPL

Previously, we were checking whether the command should be accepted each
time a line break was encountered, but that's not the expected behavior.
In bracketed paste mode, we expect everything pasted to be part of
a single block of code, and encountering a newline shouldn't behave like
a user pressing <Enter> to execute a command. The user should always
have a chance to review the pasted command before running it.

* Use a read buffer for input in pyrepl

Previously we were reading one byte at a time, which causes much slower
IO than necessary. Instead, read in chunks, processing previously read
data before asking for more.

* Optimize finding width of a single character

`wlen` finds the width of a multi-character string by adding up the
width of each character, and then subtracting the width of any escape
sequences. It's often called for single character strings, however,
which can't possibly contain escape sequences. Optimize for that case.

* Optimize disp_str for ASCII characters

Since every ASCII character is known to display as single width, we can
avoid not only the Unicode data lookup in `disp_str` but also the one
hidden in `str_width` for them.

* Speed up cursor movements in long pyrepl commands

When the current pyrepl command buffer contains many lines, scrolling up
becomes slow. We have optimizations in place to reuse lines above the
cursor position from one refresh to the next, but don't currently try to
reuse lines below the cursor position in the same way, so we wind up
with quadratic behavior where all lines of the buffer below the cursor
are recomputed each time the cursor moves up another line.

Optimize this by only computing one screen's worth of lines beyond the
cursor position. Any lines beyond that can't possibly be shown by the
console, and bounding this makes scrolling up have linear time
complexity instead.

---------

Signed-off-by: Matt Wozniski <[email protected]>
Co-authored-by: Pablo Galindo <[email protected]>
@treyhunner treyhunner mentioned this pull request Jul 2, 2024
KeyboardInterrupt during paste breaks the new REPL #121295
Closed
estyxx pushed a commit to estyxx/cpython that referenced this pull request Jul 17, 2024
@godlygeek @pablogsal @estyxx
pythongh-119517: Fixes for pasting in pyrepl (python#120253)
124b5b8
* Remove pyrepl's optimization for self-insert

This will be replaced by a less specialized optimization.

* Use line-buffering when pyrepl echoes pastes

Previously echoing was totally suppressed until the entire command had
been pasted and the terminal ended paste mode, but this gives the user
no feedback to indicate that an operation is in progress. Drawing
something to the screen once per line strikes a balance between
perceived responsiveness and performance.

* Remove dead code from pyrepl

`msg_at_bottom` is always true.

* Speed up pyrepl's screen rendering computation

The Reader in pyrepl doesn't hold a complete representation of the
screen area being drawn as persistent state. Instead, it recomputes it,
on each keypress. This is fast enough for a few hundred bytes, but
incredibly slow as the input buffer grows into the kilobytes (likely
because of pasting).

Rather than making some expensive and expansive changes to the repl's
internal representation of the screen, add some caching: remember some
data from one refresh to the next about what was drawn to the screen
and, if we don't find anything that has invalidated the results that
were computed last time around, reuse them. To keep this caching as
simple as possible, all we'll do is look for lines in the buffer that
were above the cursor the last time we were asked to update the screen,
and that are still above the cursor now. We assume that nothing can
affect a line that comes before both the old and new cursor location
without us being informed. Based on this assumption, we can reuse old
lines, which drastically speeds up the overwhelmingly common case where
the user is typing near the end of the buffer.

* Speed up pyrepl prompt drawing

Cache the `can_colorize()` call rather than repeatedly recomputing it.
This call looks up an environment variable, and is called once per
character typed at the REPL. The environment variable lookup shows up as
a hot spot when profiling, and we don't expect this to change while the
REPL is running.

* Speed up pasting multiple lines into the REPL

Previously, we were checking whether the command should be accepted each
time a line break was encountered, but that's not the expected behavior.
In bracketed paste mode, we expect everything pasted to be part of
a single block of code, and encountering a newline shouldn't behave like
a user pressing <Enter> to execute a command. The user should always
have a chance to review the pasted command before running it.

* Use a read buffer for input in pyrepl

Previously we were reading one byte at a time, which causes much slower
IO than necessary. Instead, read in chunks, processing previously read
data before asking for more.

* Optimize finding width of a single character

`wlen` finds the width of a multi-character string by adding up the
width of each character, and then subtracting the width of any escape
sequences. It's often called for single character strings, however,
which can't possibly contain escape sequences. Optimize for that case.

* Optimize disp_str for ASCII characters

Since every ASCII character is known to display as single width, we can
avoid not only the Unicode data lookup in `disp_str` but also the one
hidden in `str_width` for them.

* Speed up cursor movements in long pyrepl commands

When the current pyrepl command buffer contains many lines, scrolling up
becomes slow. We have optimizations in place to reuse lines above the
cursor position from one refresh to the next, but don't currently try to
reuse lines below the cursor position in the same way, so we wind up
with quadratic behavior where all lines of the buffer below the cursor
are recomputed each time the cursor moves up another line.

Optimize this by only computing one screen's worth of lines beyond the
cursor position. Any lines beyond that can't possibly be shown by the
console, and bounding this makes scrolling up have linear time
complexity instead.

---------

Signed-off-by: Matt Wozniski <[email protected]>
Co-authored-by: Pablo Galindo <[email protected]>
@eendebakpt eendebakpt mentioned this pull request Nov 2, 2024
3.13.0 REPL crashes on double ctrl+z in line overflow #126332
Closed
@chris-eibl chris-eibl mentioned this pull request Apr 26, 2025
Pasting in new REPL is slow #130328
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@pablogsal pablogsal pablogsal approved these changes
@lysnikolaou lysnikolaou Awaiting requested review from lysnikolaou lysnikolaou is a code owner
@ambv ambv Awaiting requested review from ambv ambv is a code owner

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

skip news topic-repl Related to the interactive shell

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@godlygeek @pablogsal @treyhunner @hugovk @lysnikolaou
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.