Skip to content

Commit 68b12de

Browse files
committed
fix(readline): align IME cursor and place output after prompt
1 parent 0dc808e commit 68b12de

13 files changed

Lines changed: 44 additions & 24 deletions

File tree

examples/readline/src/readline_loop.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
use promkit::{
2-
core::crossterm::{cursor, terminal},
3-
preset::readline::Readline,
4-
Prompt,
5-
};
1+
use promkit::{preset::readline::Readline, Prompt};
62

73
#[tokio::main]
84
async fn main() -> anyhow::Result<()> {
95
loop {
106
match Readline::default().run().await {
117
Ok(cmd) => {
12-
// If the prompt is finalized on the last line, print one line-feed
13-
// first so the result does not overwrite the prompt line.
14-
let (_, y) = cursor::position()?;
15-
let (_, h) = terminal::size()?;
16-
if y >= h.saturating_sub(1) {
17-
println!();
18-
}
198
println!("result: {:?}", cmd);
209
}
2110
Err(e) => {

promkit-core/src/render.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ impl<K: Ord + Clone + Send + 'static> Renderer<K> {
5454
terminal: Mutex::new(Terminal {
5555
origin,
5656
position: origin,
57+
after_frame_position: origin,
5758
}),
5859
views: SkipMap::new(),
5960
last_frame_for_hit_test: Mutex::new(Frame::default()),
@@ -137,6 +138,10 @@ impl<K: Ord + Clone + Send + 'static> Renderer<K> {
137138
})
138139
}
139140

141+
pub async fn move_to_after_frame(&self) -> anyhow::Result<()> {
142+
self.terminal.lock().await.move_to_after_frame()
143+
}
144+
140145
fn build_frame(&self, width: u16, origin_col: u16) -> Frame<K> {
141146
let mut frame = Frame::default();
142147
let mut current_row = 0u16;

promkit-core/src/terminal.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub struct Terminal {
1010
pub origin: (u16, u16),
1111
/// The current cursor position within the terminal.
1212
pub position: (u16, u16),
13+
/// The cursor position immediately after the rendered frame.
14+
pub after_frame_position: (u16, u16),
1315
}
1416

1517
impl Terminal {
@@ -83,11 +85,12 @@ impl Terminal {
8385
}
8486

8587
io::stdout().flush()?;
86-
self.position = if is_first_line {
88+
self.after_frame_position = if is_first_line {
8789
self.origin
8890
} else {
8991
(0, current_row)
9092
};
93+
self.position = self.after_frame_position;
9194
Ok(())
9295
}
9396

@@ -105,4 +108,24 @@ impl Terminal {
105108
self.position = (x, y);
106109
Ok(true)
107110
}
111+
112+
pub(crate) fn move_to_after_frame(&mut self) -> anyhow::Result<()> {
113+
let (_, height) = self.size()?;
114+
if height == 0 {
115+
return Ok(());
116+
}
117+
118+
let mut y = self.after_frame_position.1;
119+
if y >= height {
120+
let extra = y - height + 1;
121+
crossterm::queue!(io::stdout(), terminal::ScrollUp(extra))?;
122+
self.origin.1 = self.origin.1.saturating_sub(extra);
123+
y = height - 1;
124+
}
125+
126+
crossterm::queue!(io::stdout(), cursor::MoveTo(0, y))?;
127+
io::stdout().flush()?;
128+
self.position = (0, y);
129+
Ok(())
130+
}
108131
}

promkit/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ pub trait Prompt {
8686
///
8787
/// Returns a `Result` containing the final result of the prompt. The type of the result
8888
/// is defined by the `Return` associated type.
89-
fn finalize(&mut self) -> anyhow::Result<Self::Return>;
89+
async fn finalize(&mut self) -> anyhow::Result<Self::Return>;
9090

9191
/// Runs the prompt, handling events and producing a result.
9292
///
@@ -133,6 +133,6 @@ pub trait Prompt {
133133
}
134134
}
135135

136-
self.finalize()
136+
self.finalize().await
137137
}
138138
}

promkit/src/preset/checkbox.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl crate::Prompt for Checkbox {
6868

6969
type Return = Vec<String>;
7070

71-
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
71+
async fn finalize(&mut self) -> anyhow::Result<Self::Return> {
7272
Ok(self
7373
.checkbox
7474
.checkbox

promkit/src/preset/form.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ impl crate::Prompt for Form {
7676

7777
type Return = Vec<String>;
7878

79-
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
79+
async fn finalize(&mut self) -> anyhow::Result<Self::Return> {
8080
Ok(self
8181
.readlines
8282
.contents()

promkit/src/preset/json.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ impl crate::Prompt for Json {
6969

7070
type Return = ();
7171

72-
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
72+
async fn finalize(&mut self) -> anyhow::Result<Self::Return> {
7373
Ok(())
7474
}
7575
}

promkit/src/preset/listbox.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ impl crate::Prompt for Listbox {
6767

6868
type Return = String;
6969

70-
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
70+
async fn finalize(&mut self) -> anyhow::Result<Self::Return> {
7171
Ok(self.listbox.listbox.get().to_string())
7272
}
7373
}

promkit/src/preset/query_selector.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ impl crate::Prompt for QuerySelector {
106106

107107
type Return = String;
108108

109-
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
109+
async fn finalize(&mut self) -> anyhow::Result<Self::Return> {
110110
Ok(self.list.listbox.get().to_string())
111111
}
112112
}

promkit/src/preset/readline.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ impl crate::Prompt for Readline {
170170

171171
type Return = String;
172172

173-
fn finalize(&mut self) -> anyhow::Result<Self::Return> {
173+
async fn finalize(&mut self) -> anyhow::Result<Self::Return> {
174+
if let Some(renderer) = self.renderer.as_ref() {
175+
renderer.move_to_after_frame().await?;
176+
}
174177
let ret = self.readline.texteditor.text_without_cursor().to_string();
175178

176179
// Reset the text editor state for the next prompt.

0 commit comments

Comments
 (0)