Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Release 6.0.2

- Fix broken completion list rendering when the prompt starts near the bottom of the window on macOS/Linux ([waf/CSharpRepl#395](https://github.com/waf/CSharpRepl/issues/395)).

# Release 6.0.1

- Improve macOS and Linux key handling ([#297](https://github.com/waf/PrettyPrompt/pull/297)).
Expand Down
22 changes: 20 additions & 2 deletions src/PrettyPrompt/Panes/CodePane.cs
Original file line number Diff line number Diff line change
Expand Up @@ -454,12 +454,30 @@ void AppendFiltered(StringBuilder sb, string line)
}
}

/// <summary>
/// Corrects TopCoordinate after RenderPrompt writes blank lines to reserve completion-pane room.
/// - near the window bottom, those lines scroll the buffer up, so the prompt moves up by however many didn't fit below the cursor.
/// - Windows self-corrects via MeasureConsole each keystroke; Linux/macOS can't, so we adjust here from what we wrote (no cursor read).
/// - without it, CodeAreaHeight collapses to ~1 and the completion pane can't draw until the next submission. https://github.com/waf/CSharpRepl/issues/395
/// </summary>
internal void AdjustTopCoordinateForReservedLines(int reservedLines)
{
var rowsBelowCursor = console.WindowHeight - 1 - TopCoordinate;
var scrolledRows = Math.Max(0, reservedLines - rowsBelowCursor);
if (scrolledRows > 0)
{
TopCoordinate = Math.Max(0, TopCoordinate - scrolledRows);
CodeAreaHeight = Math.Max(0, console.WindowHeight - TopCoordinate);
}
}

internal void MeasureConsole()
{
// Re-derive TopCoordinate from the live cursor only on Windows (CursorTop is a cheap local API there); see PR #231 for why it's needed.
// - kept Windows-only on purpose: on Linux/macOS CursorTop blocks on an ESC[6n round-trip per read (a network round-trip over SSH), and this runs twice per keystroke.
// - dotnet/runtime#88343 (input corruption from that read) is now fixed, but the round-trip cost remains, so we still avoid it off-Windows.
if(OperatingSystem.IsWindows())
{
// ideally we'd update this in Linux too, but https://github.com/dotnet/runtime/issues/88343 prevents it.
// see https://github.com/waf/PrettyPrompt/pull/231 for why we need this line at all.
TopCoordinate = Math.Max(0, console.CursorTop - console.WindowTop - Cursor.Row);
}
this.CodeAreaWidth = Math.Max(0, console.BufferWidth - configuration.Prompt.Length);
Expand Down
2 changes: 1 addition & 1 deletion src/PrettyPrompt/PrettyPrompt.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<Version>6.0.1</Version>
<Version>6.0.2</Version>

<PackageId>PrettyPrompt</PackageId>
<PackageTags>repl readline console cli</PackageTags>
Expand Down
4 changes: 4 additions & 0 deletions src/PrettyPrompt/Rendering/Renderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ public void RenderPrompt(CodePane codePane)
var newLinesCount = codePane.EmptySpaceAtBottomOfWindowHeight;
console.Write(new string('\n', newLinesCount) + GetMoveCursorUp(newLinesCount) + GetMoveCursorToColumn(1) + Reset);
console.Write(configuration.Prompt);

// those newlines may have scrolled the buffer up (prompt started near the window bottom); let the code pane
// correct TopCoordinate, since on non-Windows it can't re-derive it from the OS cursor.
codePane.AdjustTopCoordinateForReservedLines(newLinesCount);
}

public void RenderOutput(
Expand Down
22 changes: 22 additions & 0 deletions tests/PrettyPrompt.Tests/RendererTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,28 @@ public async Task RenderOutput_ConsoleHeightTooSmallAndCursorOnFirstLine_ShowsIn
Assert.Equal(expectedRender, output);
}

[Fact]
public void RenderPrompt_StartedAtBottomOfWindow_ReservesRoomForCompletionPane()
{
// prompt starts on the very bottom row of the window
const int Height = 30;
var console = ConsoleStub.NewConsole(width: 100, height: Height);
console.CursorTop.Returns(Height - 1);
console.WindowTop.Returns(0);
var configuration = new PromptConfiguration();
var renderer = new Renderer(console, configuration);
var codePane = new CodePane(console, configuration, new PromptCallbacks(), Substitute.For<IClipboard>());

var reserved = codePane.EmptySpaceAtBottomOfWindowHeight;

renderer.RenderPrompt(codePane);

// RenderPrompt writes `reserved` blank lines to make room for the completion pane; since the prompt
// started at the bottom that scrolls the buffer up, moving the prompt up by `reserved` rows.
Assert.Equal(Height - 1 - reserved, codePane.TopCoordinate);
Assert.Equal(reserved + 1, codePane.CodeAreaHeight);
}

private (CodePane codePane, CompletionPane completionPane, OverloadPane overloadPane) BuildUIPanes(string typedInput)
{
var callbacks = Substitute.For<IPromptCallbacks>();
Expand Down
Loading