1mod app;
17mod geometry;
18mod menu;
19mod tmux;
20mod ui;
21
22use std::io::{self, stdout, Stdout};
23
24use ratatui::backend::CrosstermBackend;
25use ratatui::crossterm::{
26 event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
27 execute,
28 terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
29};
30use ratatui::Terminal;
31
32use app::App;
33
34fn run(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> io::Result<()> {
35 let mut app = App::new()?;
36 loop {
37 terminal.draw(|f| app.draw(f))?;
38
39 match event::read()? {
40 Event::Key(k) => {
41 if app.prompt.is_some() {
43 match k.code {
44 KeyCode::Enter => app.confirm_rename()?,
45 KeyCode::Esc => {
46 app.prompt = None;
47 app.status = "rename cancelled".into();
48 }
49 KeyCode::Backspace => {
50 if let Some(p) = &mut app.prompt {
51 p.buf.pop();
52 }
53 }
54 KeyCode::Char(c) => {
55 if let Some(p) = &mut app.prompt {
56 p.buf.push(c);
57 }
58 }
59 _ => {}
60 }
61 continue;
62 }
63 if app.pending_kill.is_some() {
65 match k.code {
66 KeyCode::Char('y') | KeyCode::Char('Y') | KeyCode::Enter => {
67 app.confirm_kill()?
68 }
69 _ => {
70 app.pending_kill = None;
71 app.status = "cancelled".into();
72 }
73 }
74 continue;
75 }
76 if app.show_help {
77 app.show_help = false;
78 continue;
79 }
80 if app.menu.is_some() {
81 match k.code {
82 KeyCode::Esc | KeyCode::Char('q') => app.menu = None,
83 KeyCode::Up => {
84 if let Some(menu) = &mut app.menu {
85 menu.highlight = menu.highlight.saturating_sub(1);
86 }
87 }
88 KeyCode::Down => {
89 if let Some(menu) = &mut app.menu {
90 if menu.highlight + 1 < menu.items.len() {
91 menu.highlight += 1;
92 }
93 }
94 }
95 KeyCode::Enter => {
96 let h = app.menu.as_ref().map(|m| m.highlight).unwrap_or(0);
97 app.activate_menu(h)?;
98 }
99 _ => {}
100 }
101 continue;
102 }
103 match k.code {
105 KeyCode::Char('q') | KeyCode::Esc => return Ok(()),
106 KeyCode::Char('?') => app.show_help = true,
107 KeyCode::Tab => app.set_window_mode(!app.window_mode)?,
108 KeyCode::Char('w') => app.set_window_mode(true)?,
109 KeyCode::Char('p') => app.set_window_mode(false)?,
110 KeyCode::Char('r') => {
111 app.refresh()?;
112 app.status = "refreshed".into();
113 }
114 KeyCode::Char('R') => {
115 if let Some(id) = app.active_window_id() {
116 app.open_rename(id);
117 }
118 }
119 _ if app.window_mode => app.window_key(k)?,
120 _ => app.pane_key(k)?,
121 }
122 }
123 Event::Mouse(m) => app.on_mouse(m)?,
124 _ => {}
125 }
126
127 if app.should_quit {
128 return Ok(());
129 }
130 }
131}
132
133fn main() -> io::Result<()> {
134 if std::env::var_os("TMUX").is_none() {
135 eprintln!("tmux-tui: not inside a tmux session ($TMUX unset). Run it from tmux.");
136 std::process::exit(1);
137 }
138
139 enable_raw_mode()?;
140 let mut out = stdout();
141 execute!(out, EnterAlternateScreen, EnableMouseCapture)?;
142 let backend = CrosstermBackend::new(out);
143 let mut terminal = Terminal::new(backend)?;
144
145 let res = run(&mut terminal);
146
147 disable_raw_mode()?;
148 execute!(terminal.backend_mut(), LeaveAlternateScreen, DisableMouseCapture)?;
149 terminal.show_cursor()?;
150 res
151}