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 if next {
254 self.windows
255 .get_mut(&self.active_window)
256 .and_then(|w| w.split_manager_mut())
257 .expect("active window must have a populated split layout")
258 .next_split();
259 } else {
260 self.windows
261 .get_mut(&self.active_window)
262 .and_then(|w| w.split_manager_mut())
263 .expect("active window must have a populated split layout")
264 .prev_split();
265 }
266
267 let split_id = self
269 .windows
270 .get(&self.active_window)
271 .and_then(|w| w.buffers.splits())
272 .map(|(mgr, _)| mgr)
273 .expect("active window must have a populated split layout")
274 .active_split();
275 self.active_window_mut()
278 .promote_preview_if_not_in_split(split_id);
279 let buffer = self.active_buffer();
280 let tabs_width = self.active_window().effective_tabs_width();
281 self.active_window_mut()
282 .ensure_active_tab_visible(split_id, buffer, tabs_width);
283
284 let buffer_id = self.active_buffer();
285
286 if self.active_window().terminal_mode
289 && self.active_window().is_terminal_buffer(previous_buffer)
290 && !self.active_window().is_terminal_buffer(buffer_id)
291 {
292 self.active_window_mut().terminal_mode = false;
293 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
294 }
295
296 self.plugin_manager.read().unwrap().run_hook(
298 "buffer_activated",
299 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
300 );
301
302 if self.active_window().is_terminal_buffer(buffer_id) {
304 self.active_window_mut().terminal_mode = true;
305 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Terminal;
306 }
307 }
308
309 pub fn adjust_split_size(&mut self, delta: f32) {
311 let active_split = self
312 .windows
313 .get(&self.active_window)
314 .and_then(|w| w.buffers.splits())
315 .map(|(mgr, _)| mgr)
316 .expect("active window must have a populated split layout")
317 .active_split();
318 if let Some(container) = self
319 .windows
320 .get(&self.active_window)
321 .and_then(|w| w.buffers.splits())
322 .map(|(mgr, _)| mgr)
323 .expect("active window must have a populated split layout")
324 .parent_container_of(active_split)
325 {
326 self.windows
327 .get_mut(&self.active_window)
328 .and_then(|w| w.split_manager_mut())
329 .expect("active window must have a populated split layout")
330 .adjust_ratio(container, delta);
331
332 let percent = (delta * 100.0) as i32;
333 self.set_status_message(t!("split.size_adjusted", percent = percent).to_string());
334 self.active_window_mut().resize_visible_terminals();
336 }
337 }
338
339 pub fn toggle_maximize_split(&mut self) {
341 match self
342 .windows
343 .get_mut(&self.active_window)
344 .and_then(|w| w.split_manager_mut())
345 .expect("active window must have a populated split layout")
346 .toggle_maximize()
347 {
348 Ok(maximized) => {
349 if maximized {
350 self.set_status_message(t!("split.maximized").to_string());
351 } else {
352 self.set_status_message(t!("split.restored").to_string());
353 }
354 self.active_window_mut().resize_visible_terminals();
356 }
357 Err(e) => self.set_status_message(e),
358 }
359 }
360
361 pub fn get_separator_areas(&self) -> &[(ContainerId, SplitDirection, u16, u16, u16)] {
364 &self.active_layout().separator_areas
365 }
366
367 pub fn get_tab_layouts(
369 &self,
370 ) -> &std::collections::HashMap<LeafId, crate::view::ui::tabs::TabLayout> {
371 &self.active_layout().tab_layouts
372 }
373
374 pub fn get_split_areas(
377 &self,
378 ) -> &[(
379 LeafId,
380 BufferId,
381 ratatui::layout::Rect,
382 ratatui::layout::Rect,
383 usize,
384 usize,
385 )] {
386 &self.active_layout().split_areas
387 }
388
389 pub fn get_split_ratio(&self, split_id: SplitId) -> Option<f32> {
394 self.windows
395 .get(&self.active_window)
396 .and_then(|w| w.buffers.splits())
397 .map(|(mgr, _)| mgr)
398 .expect("active window must have a populated split layout")
399 .get_ratio(split_id)
400 .or_else(|| self.grouped_split_ratio(crate::model::event::ContainerId(split_id)))
401 }
402
403 pub fn get_active_split(&self) -> LeafId {
405 self.windows
406 .get(&self.active_window)
407 .and_then(|w| w.buffers.splits())
408 .map(|(mgr, _)| mgr)
409 .expect("active window must have a populated split layout")
410 .active_split()
411 }
412
413 pub fn get_split_buffer(&self, split_id: SplitId) -> Option<BufferId> {
415 self.windows
416 .get(&self.active_window)
417 .and_then(|w| w.buffers.splits())
418 .map(|(mgr, _)| mgr)
419 .expect("active window must have a populated split layout")
420 .get_buffer_id(split_id)
421 }
422
423 pub fn get_split_tabs(&self, split_id: LeafId) -> Vec<BufferId> {
425 self.windows
426 .get(&self.active_window)
427 .and_then(|w| w.buffers.splits())
428 .map(|(_, vs)| vs)
429 .expect("active window must have a populated split layout")
430 .get(&split_id)
431 .map(|vs| vs.buffer_tab_ids_vec())
432 .unwrap_or_default()
433 }
434
435 pub fn get_split_count(&self) -> usize {
437 self.windows
438 .get(&self.active_window)
439 .and_then(|w| w.buffers.splits())
440 .map(|(mgr, _)| mgr)
441 .expect("active window must have a populated split layout")
442 .root()
443 .count_leaves()
444 }
445
446 pub fn compute_drop_zone(
448 &self,
449 col: u16,
450 row: u16,
451 source_split_id: LeafId,
452 ) -> Option<super::types::TabDropZone> {
453 self.compute_tab_drop_zone(col, row, source_split_id)
454 }
455}