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}