Skip to main content

kronos_tide/
multiplexer.rs

1use crate::config::KronosConfig;
2use crate::pane::Pane;
3use crate::renderer::Renderer;
4use crossterm::{
5    event::{self, Event, KeyCode, KeyModifiers},
6    terminal,
7};
8use std::io;
9use std::time::Duration;
10
11pub struct Multiplexer {
12    config: KronosConfig,
13    panes: Vec<Pane>,
14    active_pane: usize,
15    next_pane_id: usize,
16    prefix_mode: bool,
17    renderer: Renderer,
18    running: bool,
19}
20
21#[derive(Debug, Clone, Copy, PartialEq)]
22pub enum SplitDirection {
23    Horizontal,
24    Vertical,
25}
26
27impl Multiplexer {
28    pub fn new(config: KronosConfig) -> Self {
29        let renderer = Renderer::new();
30
31        Self {
32            config,
33            panes: Vec::new(),
34            active_pane: 0,
35            next_pane_id: 0,
36            prefix_mode: false,
37            renderer,
38            running: true,
39        }
40    }
41
42    pub fn run(&mut self) -> io::Result<()> {
43        terminal::enable_raw_mode()?;
44        crossterm::execute!(
45            io::stdout(),
46            terminal::EnterAlternateScreen,
47            crossterm::cursor::Hide,
48            crossterm::event::EnableMouseCapture,
49        )?;
50
51        let (cols, rows) = terminal::size()?;
52        self.spawn_pane(0, 0, cols, rows);
53
54        while self.running {
55            self.render()?;
56
57            if event::poll(Duration::from_millis(16))? {
58                match event::read()? {
59                    Event::Key(key) => self.handle_key(key),
60                    Event::Resize(cols, rows) => self.handle_resize(cols, rows),
61                    Event::Mouse(mouse) => self.handle_mouse(mouse),
62                    _ => {}
63                }
64            }
65        }
66
67        crossterm::execute!(
68            io::stdout(),
69            crossterm::event::DisableMouseCapture,
70            crossterm::cursor::Show,
71            terminal::LeaveAlternateScreen,
72        )?;
73        terminal::disable_raw_mode()?;
74
75        Ok(())
76    }
77
78    fn spawn_pane(&mut self, x: u16, y: u16, width: u16, height: u16) {
79        if self.panes.len() >= self.config.max_panes {
80            return;
81        }
82        let id = self.next_pane_id;
83        self.next_pane_id += 1;
84        let pane = Pane::new(
85            id,
86            &self.config.shell,
87            x,
88            y,
89            width,
90            height,
91            self.config.scrollback_lines,
92        );
93        self.panes.push(pane);
94        self.active_pane = self.panes.len() - 1;
95    }
96
97    fn handle_key(&mut self, key: event::KeyEvent) {
98        if self.prefix_mode {
99            self.prefix_mode = false;
100            match key.code {
101                KeyCode::Char('c') => {
102                    let (cols, rows) = terminal::size().unwrap_or((120, 40));
103                    self.spawn_pane(0, 0, cols, rows);
104                }
105                KeyCode::Char('x') if self.panes.len() > 1 => {
106                    self.panes.remove(self.active_pane);
107                    if self.active_pane >= self.panes.len() {
108                        self.active_pane = self.panes.len() - 1;
109                    }
110                }
111                KeyCode::Char('n') if !self.panes.is_empty() => {
112                    self.active_pane = (self.active_pane + 1) % self.panes.len();
113                }
114                KeyCode::Char('p') if !self.panes.is_empty() => {
115                    self.active_pane = if self.active_pane == 0 {
116                        self.panes.len() - 1
117                    } else {
118                        self.active_pane - 1
119                    };
120                }
121                KeyCode::Char('v') => {
122                    self.split(SplitDirection::Vertical);
123                }
124                KeyCode::Char('h') => {
125                    self.split(SplitDirection::Horizontal);
126                }
127                KeyCode::Char('t') => {
128                    self.launch_tide();
129                }
130                KeyCode::Char('q') => {
131                    self.running = false;
132                }
133                KeyCode::Char('?') => {
134                    self.show_help();
135                }
136                _ => {}
137            }
138            return;
139        }
140
141        if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('b') {
142            self.prefix_mode = true;
143            return;
144        }
145
146        if let Some(pane) = self.panes.get_mut(self.active_pane) {
147            let bytes = key_to_bytes(key);
148            if !bytes.is_empty() {
149                pane.send_input(&bytes);
150            }
151        }
152    }
153
154    fn handle_resize(&mut self, cols: u16, rows: u16) {
155        if let Some(pane) = self.panes.get_mut(self.active_pane) {
156            pane.resize(cols, rows);
157        }
158    }
159
160    fn handle_mouse(&mut self, _mouse: event::MouseEvent) {}
161
162    fn split(&mut self, direction: SplitDirection) {
163        if self.panes.is_empty() || self.panes.len() >= self.config.max_panes {
164            return;
165        }
166        let current = &self.panes[self.active_pane];
167        let (x, y, w, h) = (current.x, current.y, current.width, current.height);
168
169        match direction {
170            SplitDirection::Vertical => {
171                let half = w / 2;
172                self.panes[self.active_pane].resize(half - 1, h);
173                self.spawn_pane(x + half, y, w - half, h);
174            }
175            SplitDirection::Horizontal => {
176                let half = h / 2;
177                self.panes[self.active_pane].resize(w, half - 1);
178                self.spawn_pane(x, y + half, w, h - half);
179            }
180        }
181    }
182
183    fn launch_tide(&mut self) {}
184
185    fn show_help(&self) {}
186
187    fn render(&mut self) -> io::Result<()> {
188        for (i, pane) in self.panes.iter().enumerate() {
189            let is_active = i == self.active_pane;
190            self.renderer.render_pane(pane, is_active)?;
191        }
192        self.renderer.render_status_bar(self)?;
193        self.renderer.flush()
194    }
195
196    pub fn pane_count(&self) -> usize {
197        self.panes.len()
198    }
199
200    pub fn active_pane_title(&self) -> &str {
201        self.panes
202            .get(self.active_pane)
203            .map(|p| p.title.as_str())
204            .unwrap_or("none")
205    }
206
207    pub fn is_prefix_mode(&self) -> bool {
208        self.prefix_mode
209    }
210}
211
212fn key_to_bytes(key: event::KeyEvent) -> Vec<u8> {
213    match key.code {
214        KeyCode::Char(c) => {
215            if key.modifiers.contains(KeyModifiers::CONTROL) {
216                let ctrl = (c as u8).wrapping_sub(b'a').wrapping_add(1);
217                vec![ctrl]
218            } else {
219                let mut buf = [0u8; 4];
220                let s = c.encode_utf8(&mut buf);
221                s.as_bytes().to_vec()
222            }
223        }
224        KeyCode::Enter => vec![b'\r'],
225        KeyCode::Backspace => vec![0x7f],
226        KeyCode::Tab => vec![b'\t'],
227        KeyCode::Esc => vec![0x1b],
228        KeyCode::Up => b"\x1b[A".to_vec(),
229        KeyCode::Down => b"\x1b[B".to_vec(),
230        KeyCode::Right => b"\x1b[C".to_vec(),
231        KeyCode::Left => b"\x1b[D".to_vec(),
232        KeyCode::Home => b"\x1b[H".to_vec(),
233        KeyCode::End => b"\x1b[F".to_vec(),
234        KeyCode::PageUp => b"\x1b[5~".to_vec(),
235        KeyCode::PageDown => b"\x1b[6~".to_vec(),
236        KeyCode::Delete => b"\x1b[3~".to_vec(),
237        _ => vec![],
238    }
239}