diff --git a/CHANGELOG.md b/CHANGELOG.md
index 499f791..609e2ec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)).
diff --git a/src/PrettyPrompt/Panes/CodePane.cs b/src/PrettyPrompt/Panes/CodePane.cs
index 0f5ba53..328dc66 100644
--- a/src/PrettyPrompt/Panes/CodePane.cs
+++ b/src/PrettyPrompt/Panes/CodePane.cs
@@ -454,12 +454,30 @@ void AppendFiltered(StringBuilder sb, string line)
}
}
+ ///
+ /// 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
+ ///
+ 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);
diff --git a/src/PrettyPrompt/PrettyPrompt.csproj b/src/PrettyPrompt/PrettyPrompt.csproj
index 6a3e1eb..ce400a7 100644
--- a/src/PrettyPrompt/PrettyPrompt.csproj
+++ b/src/PrettyPrompt/PrettyPrompt.csproj
@@ -1,7 +1,7 @@
- 6.0.1
+ 6.0.2
PrettyPrompt
repl readline console cli
diff --git a/src/PrettyPrompt/Rendering/Renderer.cs b/src/PrettyPrompt/Rendering/Renderer.cs
index 019ae04..04f442f 100644
--- a/src/PrettyPrompt/Rendering/Renderer.cs
+++ b/src/PrettyPrompt/Rendering/Renderer.cs
@@ -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(
diff --git a/tests/PrettyPrompt.Tests/RendererTests.cs b/tests/PrettyPrompt.Tests/RendererTests.cs
index 13b540b..fb721b5 100644
--- a/tests/PrettyPrompt.Tests/RendererTests.cs
+++ b/tests/PrettyPrompt.Tests/RendererTests.cs
@@ -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());
+
+ 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();