Skip to content

Commit 569095a

Browse files
authored
Merge pull request #96 from ynqa/feat/terminal-session
Add configurable terminal session lifecycle management
2 parents 33d6308 + 251c218 commit 569095a

37 files changed

Lines changed: 562 additions & 205 deletions

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ members = [
1212
[workspace.dependencies]
1313
anyhow = "1.0.102"
1414
async-trait = "0.1.89"
15+
bitflags = "2.9.0"
1516
crossbeam-skiplist = "0.1.3"
1617
crossterm = { version = "0.29.0", features = ["use-dev-tty", "event-stream", "serde"] }
1718
csv = "1.3.1"
1819
futures = "0.3.32"
1920
radix_trie = "0.3.0"
2021
rayon = "1.11.0"
21-
scopeguard = "1.2.0"
2222
serde = "1.0.228"
2323
serde_json = { version = "1.0.149", features = ["preserve_order"] }
2424
serde_yaml = "0.9.34"

Concept.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ promkit is organized around four responsibilities with clear boundaries:
4242
1. **Prompt lifecycle runtime (`promkit`)**
4343
- [`Prompt`](./promkit/src/runtime.rs) defines lifecycle hooks:
4444
`initialize -> evaluate -> finalize`
45-
- [`Prompt::run`](./promkit/src/runtime.rs) manages terminal setup/teardown
46-
(raw mode, cursor visibility) and drives input events from a singleton
47-
`EVENT_STREAM`.
45+
- [`Prompt::run`](./promkit/src/runtime.rs) drives input events from a
46+
singleton `EVENT_STREAM`.
47+
- `TerminalSession` manages opt-in terminal setup/teardown separately from
48+
the prompt lifecycle.
4849
- Events are processed sequentially.
4950

5051
2. **Application event policy (`examples` and downstream applications)**
@@ -83,7 +84,8 @@ promkit is organized around four responsibilities with clear boundaries:
8384
after layout is complete.
8485

8586
This keeps responsibilities explicit:
86-
- runtime = lifecycle and terminal event stream
87+
- runtime = prompt lifecycle and terminal event stream
88+
- terminal session = terminal mode lifecycle
8789
- application prompt = event and focus policy
8890
- widgets = state to graphemes
8991
- core renderer = terminal output

examples/async_task/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ publish = false
88
promkit = { path = "../../promkit", features = [
99
"runtime",
1010
"spinner",
11+
"terminal-session",
1112
"texteditor",
1213
] }
1314
tokio = { workspace = true }

examples/async_task/src/async_task.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use promkit::{
2727
spinner::{self, State},
2828
text_editor,
2929
},
30-
Prompt, Signal,
30+
Prompt, Signal, TerminalModes, TerminalSession,
3131
};
3232
use tokio::{
3333
sync::{mpsc, RwLock},
@@ -427,5 +427,8 @@ impl AsyncTaskPrompt {
427427

428428
#[tokio::main]
429429
async fn main() -> anyhow::Result<()> {
430+
let modes =
431+
TerminalModes::RAW_MODE | TerminalModes::HIDDEN_CURSOR | TerminalModes::MOUSE_CAPTURE;
432+
let _terminal_session = TerminalSession::try_new(modes)?;
430433
AsyncTaskPrompt::try_default().await?.spawn().await
431434
}

examples/checkbox/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ publish = false
66

77
[dependencies]
88
anyhow = { workspace = true }
9-
promkit = { path = "../../promkit", features = ["runtime", "checkbox", "text"] }
9+
promkit = { path = "../../promkit", features = ["runtime", "terminal-session", "checkbox", "text"] }
1010
tokio = { workspace = true }
1111

1212
[[bin]]

examples/checkbox/src/checkbox.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use promkit::{
1414
checkbox::{self, Checkbox, CheckboxHit},
1515
text::{self, Text},
1616
},
17-
Prompt, Signal,
17+
Prompt, Signal, TerminalModes, TerminalSession,
1818
};
1919

2020
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
@@ -185,20 +185,25 @@ impl Prompt for CheckboxPrompt {
185185

186186
#[tokio::main]
187187
async fn main() -> anyhow::Result<()> {
188-
let ret = CheckboxPrompt::new([
189-
"Apple",
190-
"Banana",
191-
"Orange",
192-
"Mango",
193-
"Strawberry",
194-
"Pineapple",
195-
"Grape",
196-
"Watermelon",
197-
"Kiwi",
198-
"Pear",
199-
])
200-
.run()
201-
.await?;
188+
let ret = {
189+
let modes =
190+
TerminalModes::RAW_MODE | TerminalModes::HIDDEN_CURSOR | TerminalModes::MOUSE_CAPTURE;
191+
let _terminal_session = TerminalSession::try_new(modes)?;
192+
CheckboxPrompt::new([
193+
"Apple",
194+
"Banana",
195+
"Orange",
196+
"Mango",
197+
"Strawberry",
198+
"Pineapple",
199+
"Grape",
200+
"Watermelon",
201+
"Kiwi",
202+
"Pear",
203+
])
204+
.run()
205+
.await?
206+
};
202207
println!("result: {:?}", ret);
203208
Ok(())
204209
}

examples/confirm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ publish = false
66

77
[dependencies]
88
anyhow = { workspace = true }
9-
promkit = { path = "../../promkit", features = ["runtime", "validate"] }
9+
promkit = { path = "../../promkit", features = ["runtime", "terminal-session", "validate"] }
1010
readline-example = { package = "readline", path = "../readline" }
1111
tokio = { workspace = true }
1212

examples/confirm/src/confirm.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use promkit::{validate::ValidatorManager, Prompt};
1+
use promkit::{validate::ValidatorManager, Prompt, TerminalModes, TerminalSession};
22
use readline_example::Readline;
33

44
#[tokio::main]
@@ -10,7 +10,12 @@ async fn main() -> anyhow::Result<()> {
1010
|_| "Please type 'y' or 'n' as an answer".into(),
1111
));
1212

13-
let ret = prompt.run().await?;
13+
let ret = {
14+
let modes =
15+
TerminalModes::RAW_MODE | TerminalModes::HIDDEN_CURSOR | TerminalModes::MOUSE_CAPTURE;
16+
let _terminal_session = TerminalSession::try_new(modes)?;
17+
prompt.run().await?
18+
};
1419
println!("result: {:?}", ret);
1520
Ok(())
1621
}

examples/csv/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ publish = false
77
[dependencies]
88
anyhow = { workspace = true }
99
clap = { version = "4.5.60", features = ["derive"] }
10-
promkit = { path = "../../promkit", features = ["runtime", "table"] }
10+
promkit = { path = "../../promkit", features = ["runtime", "terminal-session", "table"] }
1111
tokio = { workspace = true }
1212

1313
[[bin]]

examples/csv/src/csv.rs

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ use promkit::{
55
core::{
66
crossterm::{
77
event::{
8-
self, Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
9-
MouseEvent, MouseEventKind,
8+
Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, MouseEvent,
9+
MouseEventKind,
1010
},
11-
execute, terminal,
11+
terminal,
1212
},
1313
render::{Renderer, SharedRenderer},
1414
Widget,
1515
},
1616
widgets::table::{CsvOptions, Document, State},
17-
Prompt, Signal,
17+
Prompt, Signal, TerminalModes, TerminalSession,
1818
};
1919

2020
/// Interactive CSV viewer powered by promkit.
@@ -202,30 +202,16 @@ fn parse_document(args: &Args) -> anyhow::Result<Document> {
202202
}
203203
}
204204

205-
/// Ensure the terminal is restored to its original state when dropped.
206-
struct TerminalGuard;
207-
208-
impl Drop for TerminalGuard {
209-
fn drop(&mut self) {
210-
let _ = execute!(
211-
io::stdout(),
212-
terminal::LeaveAlternateScreen,
213-
event::DisableMouseCapture
214-
);
215-
}
216-
}
217-
218205
#[tokio::main]
219206
async fn main() -> anyhow::Result<()> {
220207
let args = Args::parse();
221208
let document = parse_document(&args)?;
222209

223-
execute!(
224-
io::stdout(),
225-
terminal::EnterAlternateScreen,
226-
event::EnableMouseCapture
227-
)?;
228-
let _terminal_guard = TerminalGuard;
210+
let modes = TerminalModes::RAW_MODE
211+
| TerminalModes::ALTERNATE_SCREEN
212+
| TerminalModes::HIDDEN_CURSOR
213+
| TerminalModes::MOUSE_CAPTURE;
214+
let _terminal_session = TerminalSession::try_new(modes)?;
229215

230216
CsvViewer::new(document).run().await
231217
}

0 commit comments

Comments
 (0)