From cfe9e5e78813fa011fd6c12d4fdd0b29d96a220a Mon Sep 17 00:00:00 2001 From: Will Fuqua Date: Sun, 14 Jun 2026 21:34:08 +0700 Subject: [PATCH] MacOS/Linux: autocomplete window doesn't render at bottom of terminal on first prompt --- CHANGELOG.md | 4 ++++ src/PrettyPrompt/Panes/CodePane.cs | 22 ++++++++++++++++++++-- src/PrettyPrompt/PrettyPrompt.csproj | 2 +- src/PrettyPrompt/Rendering/Renderer.cs | 4 ++++ tests/PrettyPrompt.Tests/RendererTests.cs | 22 ++++++++++++++++++++++ 5 files changed, 51 insertions(+), 3 deletions(-) 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();