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 the 'ArgumentOutOfRangeException' caused by top of the text being scrolled up-off the buffer #979

Merged
merged 6 commits into from
Sep 12, 2019

Conversation

daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Jul 30, 2019

Major change includes:

  • Update ReallyRender to avoid writing everything every time when editing the text. Instead, we find the first different logical line, and starting writing from there.
  • Refactor ReallyRender by moving some logic to separate methods.
  • Support editing text even if the top of the text has been scrolled up-off the buffer.
  • Disallow cursor to be set to a position that is off the buffer.
  • Fix a bug in ConvertLineAndColumnToOffset so that a point can be translated to the offset correctly.

Note: I would like to add some more tests, but not yet clear what tests to add.

Edit long text in small buffer console (66 x 17, Windows)

edit-in-small-buffer

Copy long text to small buffer console (66 x 17, Windows)

Function Get-IniContent copied from https://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91

copy-long-text-in-small-buffer

Copy long text in Ubuntu terminal

Function Get-IniContent copied from https://gallery.technet.microsoft.com/scriptcenter/ea40c1ef-c856-434b-b8fb-ebd7a76e8d91

copy-long-text-in-Linux

Edit long text in Ubuntu terminal

edit-long-text-in-Linux

- Update 'ReallyRender' to avoid writing everything every time when editing the text. Instead, we find the first different logical line, and starting writing from there.
- Support editing text even if the top of the text has been scrolled up-off the buffer.
- Disallow cursor to be set to a position that is off the buffer.
- Fix a bug in 'ConvertLineAndColumnToOffset' so that a point can be translated to the offset correctly.
@daxian-dbw daxian-dbw requested review from lzybkr and SteveL-MSFT July 30, 2019 17:02
@daxian-dbw daxian-dbw marked this pull request as ready for review July 30, 2019 22:27
@daxian-dbw
Copy link
Member Author

@lzybkr The PR is ready for review. Please take a look when you have time. Thanks!

@lzybkr
Copy link
Member

lzybkr commented Jul 31, 2019

For some reason, I had to add this to build on my dev machine:

diff --git a/PSReadLine/PSReadLine.csproj b/PSReadLine/PSReadLine.csproj
index 1a87173..98597fb 100644
--- a/PSReadLine/PSReadLine.csproj
+++ b/PSReadLine/PSReadLine.csproj
@@ -8,6 +8,7 @@
     <FileVersion>2.0.0</FileVersion>
     <InformationalVersion>2.0.0-beta4</InformationalVersion>
     <TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
+    <LangVersion>7.1</LangVersion>
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="PowerShellStandard.Library" version="5.1.0-*" />

@lzybkr
Copy link
Member

lzybkr commented Jul 31, 2019

I haven't looked closely at the changes, but I'll try the changes out for awhile and see if I notice any issues.

The first two things I've noticed:

  • Shift+Home seems to cause problems. The selection doesn't work and a blank line gets added each time.
  • You can't scroll back to the beginning of the line unless you delete enough of the text.

These two issues might make it annoying for some people to undo, e.g. some Emacs users will use Ctrl+aCtrl+k to delete the whole line.

@daxian-dbw
Copy link
Member Author

Weird. I don't need that line and the CI builds fine ... But I can add <LangVersion>latest</LangVersion> if needed.

@daxian-dbw
Copy link
Member Author

daxian-dbw commented Jul 31, 2019

Can you be more specific about the those two things?

  • Shift+Home causing problem --- in what cases? Like console buffer size and etc. I need those info to repro the issue.
  • can't scroll back to the beginning of the line --- again, in what cases? If the top of the text has already been scrolled up-off the buffer, then it's by design.

but I'll try the changes out for awhile and see if I notice any issues.

Yes please. I'm dogfooding my changes. You trying out it will definitely help to find issues.

@lzybkr
Copy link
Member

lzybkr commented Jul 31, 2019

  • Shift+Home - repro in Windows Terminal, type @" then Enter enough times so that the prompt is off the screen, then Shift+Home multiple times. It will beep and display a blank line (no continuation prompt).
  • Scroll back to the beginning of the line - any number of ways you might try: Home, arrow keys, etc. I can imagine it's hard to fix (PSReadLine wasn't designed for editing files), but it will cause difficulties for some users.

@daxian-dbw
Copy link
Member Author

Thanks @lzybkr, I can repro the Shift+Home issue. I will look into it.

For the second issue, it's by design. As you mentioned, PSReadLine is not designed for editing files, at least not yet. It's not perfect, but the experience is much better than before this change.
Today, moving the cursor doesn't require rendering. If we want to make it more like vim in this case, some fundamental changes will be needed.

@lzybkr
Copy link
Member

lzybkr commented Aug 1, 2019

I can't load PSReadLine in Windows PowerShell anymore, I'm not sure if it's from the PR or a previous one though.

@daxian-dbw
Copy link
Member Author

The rendering issue when shift-selecting text that had been scrolled up-off the buffer has been fixed.
When shift-selecting text that had been scrolled off the buffer, no text will be selected and PSReadLine will beep because the cursor cannot be moved to that range of text. When rendering, we will find that nothing needs to be render and skip writing anything.

I can't load PSReadLine in Windows PowerShell anymore, I'm not sure if it's from the PR or a previous one though.

Did you build targeting net461? Building against net461 works fine on my machine and also works in CI.

@lzybkr
Copy link
Member

lzybkr commented Aug 1, 2019

I used the binary built in CI (AppVeyor).

@daxian-dbw
Copy link
Member Author

I guess you didn't unblock the zip file after downloading it. Can you please double check if that's the case?

@lzybkr
Copy link
Member

lzybkr commented Aug 1, 2019

Thanks for the hints, locally I did build the wrong target, and I did forget to unblock the file from AppVeyor.

@daxian-dbw
Copy link
Member Author

@lzybkr Any feedback after using PSReadLine with this change for a while?

@msftrncs
Copy link
Collaborator

msftrncs commented Aug 21, 2019

I've tested this PR just briefly. Noticed a couple small things, they may or not be related to this PR:

  1. The integrated terminal in VSCode does not permit editing above the top of the current window, as if there was no buffer (but I can scroll-wheel up and see more, but cannot edit it). Editing worked as expected in a regular console when the input was longer than the window.
  2. The <up-arrow> wouldn't take me up a line while editing if the line was so long it wrapped, and the cursor was at the end of the line, and had been deliberate placed there using <end> or <right arrow>.
  3. Cursor positioning, if the cursor is arrowed to the end of a line, then up or down, it jumps to the end of the lines of each line it touches. Normally editors only do this if you press <end> first.

Copy link
Member

@lzybkr lzybkr left a comment

Choose a reason for hiding this comment

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

I just noticed I started a review but didn't finish it. I'm on vacation and will finish when I return.

PSReadLine/Render.cs Outdated Show resolved Hide resolved
PSReadLine/Render.cs Outdated Show resolved Hide resolved
PSReadLine/Render.cs Outdated Show resolved Hide resolved
PSReadLine/Render.cs Outdated Show resolved Hide resolved
{
// This could happen when adding a new line in the end of the very last line.
// In this case, we scroll up by writing out a new line.
_console.SetCursorPosition(left: bufferWidth - 1, top: bufferHeight - 1);
Copy link
Member

Choose a reason for hiding this comment

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

Based on the method name, I thought this method would be side effect free, so moving the cursor is surprising.

Copy link
Member Author

Choose a reason for hiding this comment

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

You are right. It's not just calculation, but also set the cursor to the about-to-start-rendering position.
How about rename the method to PrepareToRender?

// then it's possible we are facing this special case and thus would need to do additional checks later.

minLineLength = renderLines.Length;
if (cursorX == Options.ContinuationPrompt.Length && cursorY == 0)
Copy link
Member

Choose a reason for hiding this comment

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

I think I understand this check, but couldn't it be true by luck, e.g. you have a short prompt?

Copy link
Member Author

Choose a reason for hiding this comment

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

With the cursorY > _initialY check above, this won't happen, because of the cursorY == 0 check here implies _initialY < 0, meaning that this won't be the first physical line where the user prompt is at.
Now that the cursorY > _initialY check is removed, we need to check for _initialY < 0 here.

Copy link
Member Author

Choose a reason for hiding this comment

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

This piece of code has been changed, to cover the more general scenarios -- after editing, the cursor is supposed to be moved off the buffer.
Please take another look.

// newY could be less than 0 if 'PromptText' is manually set to be a long string.
if (newY >= 0)
// Additional check for the special case mentioned above: the cursor is supposed to be moved to the previous line.
if (ConvertOffsetToPoint(_current).Y == -1)
Copy link
Member

Choose a reason for hiding this comment

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

The code feels wrong, but I'm not sure how precisely to address your concerns.

At any rate, in testing this out, you can get an exception if you delete multiple characters such that -1 is the wrong value, e.g. have a few blank lines at the start, get to this line of code, then type Alt+4Backspace to delete 4 characters at once.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for spotting the issue. I didn't know about the key binding to delete arbitrary number of characters. This changes the assumption of the code. I will rework the related part of the code.

Copy link
Member Author

Choose a reason for hiding this comment

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

This issue has been addressed, the logic is changed to cover the more general scenarios where the cursor is supposed to be moved to a position that is off the buffer after the editing.
In the following screenshot, the Hello world line and the following 3 empty lines are moved off the buffer, and then Alt+4Backspace works as expected.

fix1

However, as you might noticed, I removed the last empty line before moving the cursor to <0, 0> position. This is because if I don't remove the last line, Alt+4 scroll up the buffer to show the digit-argument: line, and will move the cursor one line above. In this case, the cursor will be moved to <0, -1>, and thus causes an exception.

This is not a problem introduced by this change. See the following screenshot with the beta.4 PSReadLine:

bk1

if (isFirstLogicalLine)
// The editing was in the first logical line. We have to write everything in this case.
// Move the cursor to the initial position if we haven't done so.
if (_initialY < 0)
Copy link
Member

Choose a reason for hiding this comment

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

While this condition makes some logical sense, it should be documented on the field and all references to the field should be checked and tested to verify a negative value is safe because I don't think it could have happened before this PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will go through all the uses of _initialY.

Copy link
Member Author

Choose a reason for hiding this comment

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

It will take some time to go through all uses of _initialY and verify those uses when _initialY is negative. I would like to do this in follow-up PRs. I opened #1015 to track this work.

because I don't think it could have happened before this PR.

This happens before this PR, but the code never handles this situation properly and that's causing the ArgumentOutOfRangeException issues.

_console.Write("\x1b[2J");
_console.SetCursorPosition(0, _console.WindowTop);

string newPrompt = GetPrompt();
Copy link
Member

Choose a reason for hiding this comment

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

You've mostly duplicated InvokePrompt, at least the bit starting with this line.

Copy link
Member Author

Choose a reason for hiding this comment

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

InvokePrompt calls back to ReallyRender. I don't want that here.

@@ -28,6 +28,13 @@ public override string ToString()

public partial class PSConsoleReadLine
{
struct RenderedLineInfo
Copy link
Member

Choose a reason for hiding this comment

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

This struct is really just the return value of CalculateWhereAndWhatToRender, but you return 2 of them. Maybe you should merge them into 1 struct instead and give it a better name (it's too similar to RenderedLineData anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Use one struct to hold both the current and previous line info. Rename the struct to be LineInfoForRendering.

Copy link
Member

Choose a reason for hiding this comment

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

I don't see the updated code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry for the delay. The PR has been updated.

@daxian-dbw
Copy link
Member Author

daxian-dbw commented Aug 21, 2019

@msftrncs Thanks for playing with the changes! To reply to your comments:

The integrated terminal in VSCode does not permit editing above the top of the current window, as if there was no buffer (but I can scroll-wheel up and see more, but cannot edit it). Editing worked as expected in a regular console when the input was longer than the window.

The buffer of integrated terminal in VSCode doesn't work like the console host on windows. Anything scrolled off the screen is out of the buffer. You can check the [console]::BufferHeight to verify the buffer size.
For the text scrolled off the buffer, they are still visible by scrolling the wheel up, but they are out of the buffer and cannot be edited. What you (and I) want eventually is an experience like in VIM, but PSReadLine is not designed for file editing, yet. See #979 (comment)

  1. The wouldn't take me up a line while editing if the line was so long it wrapped, and the cursor was at the end of the line, and had been deliberate placed there using or .

This behavior already exists today, without this PR. Can you please open an issue, if there isn't one already?

  1. Cursor positioning, if the cursor is arrowed to the end of a line, then up or down, it jumps to the end of the lines of each line it touches. Normally editors only do this if you press first.

This behavior already exists today. I personally don't think this is an issue though.

@msftrncs
Copy link
Collaborator

I noticed something else now, playing, (no exceptions yet) if one resizes the console window while editing (really just viewing) a multiline history, things get out of sync. In this case I had a previous multiline item up, larger than the window, sitting the end, and made the window full screen. The text doesn't change, but then switching the history, between other multiline items, draws the items without clear the previous content. Then with a multiline history item back in the screen, restore the window size to normal, the item is now too long, but only the last 8 lines are shown at the top of the screen, and moving through the history at this point is now unusable as the screen is all out of sync.

@msftrncs
Copy link
Collaborator

Back to VS Code's terminal. I noticed that as I scroll through the history, when one is too long for VS Code's terminal's buffer, and then the next one (going backwards or `'up') would fit, the buffer isn't adjusted or the prompt redrawn, so that the whole history will be shown.

Went 'up' to this item:
image
Then 'up' to this one (its two more lines above the top):
image

Maybe it has something to do with the fact the two histories have the same start? The earlier history is simply a shorter version of the later history. I was able to reproduce this on a new 'herestring' sample, and it works either direction, up or down, as long as the beginning is the same, the shorter history item will not adjust to show more of it. It also happens in the Windows 10 PowerShell console. I suspect this is part of the optimization to not have to redraw the whole buffer?

@daxian-dbw
Copy link
Member Author

I suspect this is part of the optimization to not have to redraw the whole buffer?

@msftclas Yes, this is because of the optimization to not redraw the whole buffer if possible. Those 2 history entries have the exactly the same first N logical lines, and thus the rendering starting from the N+1 logical line.
I personally don't concern about this scenario that much. The workaround is simply type up-arrow again and then type down-arrow. Unless the history entries are all incrementally adding new stuff that does't cause changes to the existing lines (including color), this workaround should make the desired history entry re-rendered.

@daxian-dbw
Copy link
Member Author

@lzybkr Could you please take another look when you have time? Thanks!

@daxian-dbw daxian-dbw added this to the 2.0.0-beta5 milestone Sep 13, 2019
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.

4 participants