|
1 | | -use std::io::IsTerminal; |
| 1 | +use std::io::{self, IsTerminal}; |
2 | 2 |
|
3 | 3 | use anyhow::Result; |
4 | 4 | use colored::Colorize; |
| 5 | +use crossterm::execute; |
| 6 | +use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen}; |
5 | 7 | use rustyline::DefaultEditor; |
6 | 8 | use tracing::debug; |
7 | 9 |
|
@@ -63,6 +65,13 @@ impl InputBuilder { |
63 | 65 | return Ok(None); |
64 | 66 | } |
65 | 67 |
|
| 68 | + // Enter the alternate screen so that the prompt is always visible and |
| 69 | + // cannot be scrolled out of the viewport. This fixes an issue in |
| 70 | + // terminals like VS Code (xterm.js) where rustyline's per-keystroke |
| 71 | + // redraw causes the viewport to jump back to the cursor position, |
| 72 | + // scrolling the prompt out of view. |
| 73 | + let _guard = AlternateScreenGuard::enter(); |
| 74 | + |
66 | 75 | let mut rl = DefaultEditor::new()?; |
67 | 76 |
|
68 | 77 | // On Windows, rustyline miscounts ANSI escape bytes as visible characters, |
@@ -103,6 +112,24 @@ impl InputBuilder { |
103 | 112 | } |
104 | 113 | } |
105 | 114 |
|
| 115 | +/// Guard that enters the terminal alternate screen on creation and exits it on |
| 116 | +/// drop. Failures are silently ignored — the alternate screen is a cosmetic |
| 117 | +/// best-effort fix for terminal viewport issues. |
| 118 | +struct AlternateScreenGuard; |
| 119 | + |
| 120 | +impl AlternateScreenGuard { |
| 121 | + fn enter() -> Option<Self> { |
| 122 | + execute!(io::stdout(), EnterAlternateScreen).ok()?; |
| 123 | + Some(Self) |
| 124 | + } |
| 125 | +} |
| 126 | + |
| 127 | +impl Drop for AlternateScreenGuard { |
| 128 | + fn drop(&mut self) { |
| 129 | + let _ = execute!(io::stdout(), LeaveAlternateScreen); |
| 130 | + } |
| 131 | +} |
| 132 | + |
106 | 133 | #[cfg(test)] |
107 | 134 | mod tests { |
108 | 135 | use pretty_assertions::assert_eq; |
|
0 commit comments