-
Notifications
You must be signed in to change notification settings - Fork 0
tui-skeleton: terminal setup and event loop #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,21 +1,85 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mod app; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mod eval; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mod ui; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::io::{self, Result, Stdout}; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use std::time::Duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| use crossterm::event::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind, KeyModifiers, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use crossterm::execute; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use crossterm::terminal::{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use ratatui::Terminal; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use ratatui::backend::CrosstermBackend; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| use app::App; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| use eval::eval; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fn main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut app = App::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let _ = app.should_quit; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| app.move_focus(1, 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let _ = app.focused_label(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for ch in "1+1=".chars() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| app.press_button(&ch.to_string()); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type Tui = Terminal<CrosstermBackend<Stdout>>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fn setup_terminal() -> Result<Tui> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| enable_raw_mode()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut stdout = io::stdout(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Terminal::new(CrosstermBackend::new(stdout)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+23
to
+26
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If Useful? React with 👍 / 👎. |
||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fn restore_terminal(terminal: &mut Tui) -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Reverse of setup: drop mouse capture *before* leaving alt screen. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| execute!( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| terminal.backend_mut(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| DisableMouseCapture, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| LeaveAlternateScreen | ||||||||||||||||||||||||||||||||||||||||||||||||||
| )?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| disable_raw_mode()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| terminal.show_cursor()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Restore the terminal on panic so the user lands back in a cooked shell | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /// instead of a frozen raw-mode terminal. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fn install_panic_hook() { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let original = std::panic::take_hook(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| std::panic::set_hook(Box::new(move |info| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let _ = execute!(io::stdout(), DisableMouseCapture, LeaveAlternateScreen); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let _ = disable_raw_mode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| original(info); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fn run(terminal: &mut Tui, app: &mut App) -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| while !app.should_quit { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| terminal.draw(|frame| ui::draw(frame, app))?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if event::poll(Duration::from_millis(100))? { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| handle_event(event::read()?, app); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| app.evaluate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| match app.result { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Some(ref res) => println!("Result: {res}"), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| None => println!("No result"), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Dispatches a single terminal event to the app. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fn handle_event(event: Event, app: &mut App) { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if let Event::Key(key) = event | ||||||||||||||||||||||||||||||||||||||||||||||||||
| && key.kind == KeyEventKind::Press | ||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| match key.code { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| KeyCode::Char('q') => app.should_quit = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| app.should_quit = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| KeyCode::Esc => app.should_quit = true, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| _ => {} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of "let-chains" (
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| fn main() -> Result<()> { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| install_panic_hook(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut terminal = setup_terminal()?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let mut app = App::new(); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| let result = run(&mut terminal, &mut app); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| restore_terminal(&mut terminal)?; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| result | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| use ratatui::Frame; | ||
| use ratatui::widgets::Block; | ||
|
|
||
| use crate::app::App; | ||
|
|
||
| /// Stub renderer. Real layout (display + button grid) lands in `ui-display` | ||
| /// and `ui-buttons`. | ||
| pub fn draw(frame: &mut Frame, _app: &App) { | ||
| let block = Block::bordered().title("Calculator"); | ||
| frame.render_widget(block, frame.area()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If
execute!fails here, the function returns early via the?operator, but the terminal has already been put into raw mode by the previous line. This will leave the user's terminal in a broken state (no echo, no line buffering) upon exit. You should ensure raw mode is disabled if subsequent setup steps fail.