use clap::{load_yaml, App, ArgMatches}; use std::io; use crossterm::event::{Event, KeyCode}; use tui::backend::{Backend, CrosstermBackend}; use tui::layout::{Constraint, Direction, Layout, Rect}; use tui::widgets::{Block, Borders}; use tui::Terminal; use tui_clap::{Events, TuiClap}; fn main() -> Result<(), io::Error> { let yaml = load_yaml!("cli.yaml"); let app = App::from(yaml); let stdout = io::stdout(); let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; let mut tui = TuiClap::from_app(app); tui.input_widget().prompt("prompt > "); terminal.clear().expect("Could not clear terminal"); let events = Events::default(); loop { draw(&mut terminal, &mut tui)?; handle_input(&mut tui, &events) } } fn handle_input(tui: &mut TuiClap, events: &Events) { if let Ok(Some(Event::Key(key_event))) = events.next() { match key_event.code { KeyCode::Backspace => { tui.state().del_char() } KeyCode::Enter => { if let Ok(matches) = tui.parse() { match handle_matches(matches) { Ok(output) => { for message in output { tui.write_to_output(message) } } Err(err) => tui.write_to_output(err) } } } KeyCode::Char(char) => { tui.state().add_char(char) }, _ => {} } } } fn draw(terminal: &mut Terminal, tui: &mut TuiClap) -> io::Result<()> { terminal.draw(|f| { let chunks = Layout::default() .direction(Direction::Vertical) .margin(1) .constraints( [ Constraint::Percentage(10), Constraint::Percentage(80), Constraint::Percentage(10), ] .as_ref(), ) .split(f.size()); let block = Block::default().title("Block").borders(Borders::ALL); f.render_widget(block, chunks[0]); let chunks_output = Layout::default() .direction(Direction::Horizontal) .margin(1) .constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref()) .split(chunks[1]); let block = Block::default().title("Block 2").borders(Borders::ALL); f.render_widget(block, chunks_output[0]); let inset_area = edge_inset(&chunks_output[0], 1); tui.render_output(f, inset_area); let block = Block::default().title("Command").borders(Borders::ALL); f.render_widget(block, chunks[2]); let inset_area = edge_inset(&chunks[2], 1); tui.render_input(f, inset_area); })?; Ok(()) } fn edge_inset(area: &Rect, margin: u16) -> Rect { let mut inset_area = *area; inset_area.x += margin; inset_area.y += margin; inset_area.height -= margin; inset_area.width -= margin; inset_area } fn handle_matches(matches: ArgMatches) -> Result, String> { let mut output = vec![]; let config = matches.value_of("config").unwrap_or("default.conf"); let out = format!("Value for config: {}", config); output.push(out); // Calling .unwrap() is safe here because "INPUT" is required (if "INPUT" wasn't // required we could have used an 'if let' to conditionally get the value) let out = format!("Using input file: {}", matches.value_of("INPUT").unwrap()); output.push(out); // Vary the output based on how many times the user used the "verbose" flag // (i.e. 'myprog -v -v -v' or 'myprog -vvv' vs 'myprog -v' let out = match matches.occurrences_of("v") { 0 => "No verbose info".to_string(), 1 => "Some verbose info".to_string(), 2 => "Tons of verbose info".to_string(), _ => "Don't be crazy".to_string(), }; output.push(out); // You can handle information about subcommands by requesting their matches by name // (as below), requesting just the name used, or both at the same time if let Some(matches) = matches.subcommand_matches("test") { if matches.is_present("debug") { let out = "Printing debug info...".to_string(); output.push(out); } else { let out = "Printing normally...".to_string(); output.push(out); } }; Ok(output) }