fresh/app/
split_actions.rs1use rust_i18n::t;
11
12use crate::model::event::{BufferId, ContainerId, LeafId, SplitDirection, SplitId};
13use crate::view::folding::CollapsedFoldLineRange;
14use crate::view::split::SplitViewState;
15
16use super::Editor;
17
18impl Editor {
19 pub fn split_pane_horizontal(&mut self) {
21 self.split_pane_impl(crate::model::event::SplitDirection::Horizontal);
22 }
23
24 pub fn split_pane_vertical(&mut self) {
26 self.split_pane_impl(crate::model::event::SplitDirection::Vertical);
27 }
28
29 fn split_pane_impl(&mut self, direction: crate::model::event::SplitDirection) {
31 self.active_window_mut().promote_current_preview();
36
37 let current_buffer_id = self.active_buffer();
38 let active_split = self
39 .windows
40 .get(&self.active_window)
41 .and_then(|w| w.buffers.splits())
42 .map(|(mgr, _)| mgr)
43 .expect("active window must have a populated split layout")
44 .active_split();
45
46 let source_keyed_states = self
48 .windows
49 .get(&self.active_window)
50 .and_then(|w| w.buffers.splits())
51 .map(|(_, vs)| vs)
52 .expect("active window must have a populated split layout")
53 .get(&active_split)
54 .map(|vs| {
55 vs.keyed_states
56 .iter()
57 .filter(|(&buf_id, _)| buf_id != current_buffer_id)
58 .map(|(&buf_id, buf_state)| {
59 let folds = self
60 .buffers()
61 .get(&buf_id)
62 .map(|state| {
63 buf_state
64 .folds
65 .collapsed_line_ranges(&state.buffer, &state.marker_list)
66 })
67 .unwrap_or_default();
68 (buf_id, buf_state.clone(), folds)
69 })
70 .collect::<Vec<(
71 BufferId,
72 crate::view::split::BufferViewState,
73 Vec<CollapsedFoldLineRange>,
74 )>>()
75 });
76
77 match self
78 .split_manager_mut()
79 .split_active(direction, current_buffer_id, 0.5)
80 {
81 Ok(new_split_id) => {
82 let mut view_state = SplitViewState::with_buffer(
83 self.terminal_width,
84 self.terminal_height,
85 current_buffer_id,
86 );
87 view_state.apply_config_defaults(
88 self.config.editor.line_numbers,
89 self.config.editor.highlight_current_line,
90 self.active_window()
91 .resolve_line_wrap_for_buffer(current_buffer_id),
92 self.config.editor.wrap_indent,
93 self.active_window()
94 .resolve_wrap_column_for_buffer(current_buffer_id),
95 self.config.editor.rulers.clone(),
96 );
97
98 if let Some(source) = source_keyed_states {
101 for (buf_id, mut buf_state, folds) in source {
102 if let Some(state) = self
103 .windows
104 .get_mut(&self.active_window)
105 .map(|w| &mut w.buffers)
106 .expect("active window present")
107 .get_mut(&buf_id)
108 {
109 buf_state.folds.clear(&mut state.marker_list);
110 for fold in folds {
111 let start_line = fold.header_line.saturating_add(1);
112 let end_line = fold.end_line;
113 if start_line > end_line {
114 continue;
115 }
116 let Some(start_byte) = state.buffer.line_start_offset(start_line)
117 else {
118 continue;
119 };
120 let end_byte = state
121 .buffer
122 .line_start_offset(end_line.saturating_add(1))
123 .unwrap_or_else(|| state.buffer.len());
124 buf_state.folds.add(
125 &mut state.marker_list,
126 start_byte,
127 end_byte,
128 fold.placeholder.clone(),
129 );
130 }
131 }
132 view_state.keyed_states.insert(buf_id, buf_state);
133 }
134 }
135
136 self.windows
137 .get_mut(&self.active_window)
138 .and_then(|w| w.split_view_states_mut())
139 .expect("active window must have a populated split layout")
140 .insert(new_split_id, view_state);
141 let msg = match direction {
142 crate::model::event::SplitDirection::Horizontal => t!("split.horizontal"),
143 crate::model::event::SplitDirection::Vertical => t!("split.vertical"),
144 };
145 self.set_status_message(msg.to_string());
146 }
147 Err(e) => {
148 self.set_status_message(t!("split.error", error = e.to_string()).to_string());
149 }
150 }
151 }
152
153 pub fn close_active_split(&mut self) {
155 self.active_window_mut().promote_current_preview();
160
161 let closing_split = self
162 .windows
163 .get(&self.active_window)
164 .and_then(|w| w.buffers.splits())
165 .map(|(mgr, _)| mgr)
166 .expect("active window must have a populated split layout")
167 .active_split();
168
169 let closing_split_tabs = self
171 .windows
172 .get(&self.active_window)
173 .and_then(|w| w.buffers.splits())
174 .map(|(_, vs)| vs)
175 .expect("active window must have a populated split layout")
176 .get(&closing_split)
177 .map(|vs| vs.open_buffers.clone())
178 .unwrap_or_default();
179
180 match self
181 .windows
182 .get_mut(&self.active_window)
183 .and_then(|w| w.split_manager_mut())
184 .expect("active window must have a populated split layout")
185 .close_split(closing_split)
186 {
187 Ok(_) => {
188 self.windows
190 .get_mut(&self.active_window)
191 .and_then(|w| w.split_view_states_mut())
192 .expect("active window must have a populated split layout")
193 .remove(&closing_split);
194
195 let new_active_split = self
197 .windows
198 .get(&self.active_window)
199 .and_then(|w| w.buffers.splits())
200 .map(|(mgr, _)| mgr)
201 .expect("active window must have a populated split layout")
202 .active_split();
203
204 if let Some(view_state) = self
206 .windows
207 .get_mut(&self.active_window)
208 .and_then(|w| w.split_view_states_mut())
209 .expect("active window must have a populated split layout")
210 .get_mut(&new_active_split)
211 {
212 for target in closing_split_tabs {
213 if !view_state.open_buffers.contains(&target) {
215 view_state.open_buffers.push(target);
216 }
217 }
218 }
219
220 self.set_status_message(t!("split.closed").to_string());
223 }
224 Err(e) => {
225 self.set_status_message(
226 t!("split.cannot_close", error = e.to_string()).to_string(),
227 );
228 }
229 }
230 }
231
232 pub fn next_split(&mut self) {
234 self.switch_split(true);
235 self.set_status_message(t!("split.next").to_string());
236 }
237
238 pub fn prev_split(&mut self) {
240 self.switch_split(false);
241 self.set_status_message(t!("split.prev").to_string());
242 }
243
244 fn switch_split(&mut self, next: bool) {
246 let previous_buffer = self.active_buffer();
252
253 let was_maximized = self
257 .windows
258 .get(&self.active_window)
259 .and_then(|w| w.buffers.splits())
260 .map(|(mgr, _)| mgr.is_maximized())
261 .unwrap_or(false);
262
263 if next {
264 self.windows
265 .get_mut(&self.active_window)
266 .and_then(|w| w.split_manager_mut())
267 .expect("active window must have a populated split layout")
268 .next_split();
269 } else {
270 self.windows
271 .get_mut(&self.active_window)
272 .and_then(|w| w.split_manager_mut())
273 .expect("active window must have a populated split layout")
274 .prev_split();
275 }
276
277 if was_maximized {
278 self.active_window_mut().resize_visible_terminals();
279 }
280
281 let split_id = self
283 .windows
284 .get(&self.active_window)
285 .and_then(|w| w.buffers.splits())
286 .map(|(mgr, _)| mgr)
287 .expect("active window must have a populated split layout")
288 .active_split();
289 self.active_window_mut()
292 .promote_preview_if_not_in_split(split_id);
293 let buffer = self.active_buffer();
294 let tabs_width = self.active_window().effective_tabs_width();
295 self.active_window_mut()
296 .ensure_active_tab_visible(split_id, buffer, tabs_width);
297
298 let buffer_id = self.active_buffer();
299
300 if self.active_window().terminal_mode
303 && self.active_window().is_terminal_buffer(previous_buffer)
304 && !self.active_window().is_terminal_buffer(buffer_id)
305 {
306 self.active_window_mut().terminal_mode = false;
307 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
308 }
309
310 self.plugin_manager.read().unwrap().run_hook(
312 "buffer_activated",
313 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
314 );
315
316 if self.active_window().is_terminal_buffer(buffer_id) {
318 self.active_window_mut().terminal_mode = true;
319 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Terminal;
320 }
321 }
322
323 pub fn adjust_split_size(&mut self, delta: f32) {
325 let active_split = self
326 .windows
327 .get(&self.active_window)
328 .and_then(|w| w.buffers.splits())
329 .map(|(mgr, _)| mgr)
330 .expect("active window must have a populated split layout")
331 .active_split();
332 if let Some(container) = self
333 .windows
334 .get(&self.active_window)
335 .and_then(|w| w.buffers.splits())
336 .map(|(mgr, _)| mgr)
337 .expect("active window must have a populated split layout")
338 .parent_container_of(active_split)
339 {
340 self.windows
341 .get_mut(&self.active_window)
342 .and_then(|w| w.split_manager_mut())
343 .expect("active window must have a populated split layout")
344 .adjust_ratio(container, delta);
345
346 let percent = (delta * 100.0) as i32;
347 self.set_status_message(t!("split.size_adjusted", percent = percent).to_string());
348 self.active_window_mut().resize_visible_terminals();
350 }
351 }
352
353 pub fn toggle_maximize_split(&mut self) {
355 match self
356 .windows
357 .get_mut(&self.active_window)
358 .and_then(|w| w.split_manager_mut())
359 .expect("active window must have a populated split layout")
360 .toggle_maximize()
361 {
362 Ok(maximized) => {
363 if maximized {
364 self.set_status_message(t!("split.maximized").to_string());
365 } else {
366 self.set_status_message(t!("split.restored").to_string());
367 }
368 self.active_window_mut().resize_visible_terminals();
370 }
371 Err(e) => self.set_status_message(e),
372 }
373 }
374
375 pub fn get_separator_areas(&self) -> &[(ContainerId, SplitDirection, u16, u16, u16)] {
378 &self.active_layout().separator_areas
379 }
380
381 pub fn get_tab_layouts(
383 &self,
384 ) -> &std::collections::HashMap<LeafId, crate::view::ui::tabs::TabLayout> {
385 &self.active_layout().tab_layouts
386 }
387
388 pub fn get_split_areas(
391 &self,
392 ) -> &[(
393 LeafId,
394 BufferId,
395 ratatui::layout::Rect,
396 ratatui::layout::Rect,
397 usize,
398 usize,
399 )] {
400 &self.active_layout().split_areas
401 }
402
403 pub fn get_split_ratio(&self, split_id: SplitId) -> Option<f32> {
408 self.windows
409 .get(&self.active_window)
410 .and_then(|w| w.buffers.splits())
411 .map(|(mgr, _)| mgr)
412 .expect("active window must have a populated split layout")
413 .get_ratio(split_id)
414 .or_else(|| self.grouped_split_ratio(crate::model::event::ContainerId(split_id)))
415 }
416
417 pub fn get_active_split(&self) -> LeafId {
419 self.windows
420 .get(&self.active_window)
421 .and_then(|w| w.buffers.splits())
422 .map(|(mgr, _)| mgr)
423 .expect("active window must have a populated split layout")
424 .active_split()
425 }
426
427 pub fn get_split_buffer(&self, split_id: SplitId) -> Option<BufferId> {
429 self.windows
430 .get(&self.active_window)
431 .and_then(|w| w.buffers.splits())
432 .map(|(mgr, _)| mgr)
433 .expect("active window must have a populated split layout")
434 .get_buffer_id(split_id)
435 }
436
437 pub fn get_split_tabs(&self, split_id: LeafId) -> Vec<BufferId> {
439 self.windows
440 .get(&self.active_window)
441 .and_then(|w| w.buffers.splits())
442 .map(|(_, vs)| vs)
443 .expect("active window must have a populated split layout")
444 .get(&split_id)
445 .map(|vs| vs.buffer_tab_ids_vec())
446 .unwrap_or_default()
447 }
448
449 pub fn get_split_count(&self) -> usize {
451 self.windows
452 .get(&self.active_window)
453 .and_then(|w| w.buffers.splits())
454 .map(|(mgr, _)| mgr)
455 .expect("active window must have a populated split layout")
456 .root()
457 .count_leaves()
458 }
459
460 pub fn compute_drop_zone(
462 &self,
463 col: u16,
464 row: u16,
465 source_split_id: LeafId,
466 ) -> Option<super::types::TabDropZone> {
467 self.compute_tab_drop_zone(col, row, source_split_id)
468 }
469}