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 self.config.editor.scroll_offset,
97 );
98
99 if let Some(source) = source_keyed_states {
102 for (buf_id, mut buf_state, folds) in source {
103 if let Some(state) = self
104 .windows
105 .get_mut(&self.active_window)
106 .map(|w| &mut w.buffers)
107 .expect("active window present")
108 .get_mut(&buf_id)
109 {
110 buf_state.folds.clear(&mut state.marker_list);
111 for fold in folds {
112 let start_line = fold.header_line.saturating_add(1);
113 let end_line = fold.end_line;
114 if start_line > end_line {
115 continue;
116 }
117 let Some(start_byte) = state.buffer.line_start_offset(start_line)
118 else {
119 continue;
120 };
121 let end_byte = state
122 .buffer
123 .line_start_offset(end_line.saturating_add(1))
124 .unwrap_or_else(|| state.buffer.len());
125 buf_state.folds.add(
126 &mut state.marker_list,
127 start_byte,
128 end_byte,
129 fold.placeholder.clone(),
130 );
131 }
132 }
133 view_state.keyed_states.insert(buf_id, buf_state);
134 }
135 }
136
137 self.windows
138 .get_mut(&self.active_window)
139 .and_then(|w| w.split_view_states_mut())
140 .expect("active window must have a populated split layout")
141 .insert(new_split_id, view_state);
142 let msg = match direction {
143 crate::model::event::SplitDirection::Horizontal => t!("split.horizontal"),
144 crate::model::event::SplitDirection::Vertical => t!("split.vertical"),
145 };
146 self.set_status_message(msg.to_string());
147 }
148 Err(e) => {
149 self.set_status_message(t!("split.error", error = e.to_string()).to_string());
150 }
151 }
152
153 self.relayout();
158 }
159
160 pub fn close_active_split(&mut self) {
162 self.active_window_mut().promote_current_preview();
167
168 let closing_split = self
169 .windows
170 .get(&self.active_window)
171 .and_then(|w| w.buffers.splits())
172 .map(|(mgr, _)| mgr)
173 .expect("active window must have a populated split layout")
174 .active_split();
175
176 let closing_split_tabs = self
178 .windows
179 .get(&self.active_window)
180 .and_then(|w| w.buffers.splits())
181 .map(|(_, vs)| vs)
182 .expect("active window must have a populated split layout")
183 .get(&closing_split)
184 .map(|vs| vs.open_buffers.clone())
185 .unwrap_or_default();
186
187 match self
188 .windows
189 .get_mut(&self.active_window)
190 .and_then(|w| w.split_manager_mut())
191 .expect("active window must have a populated split layout")
192 .close_split(closing_split)
193 {
194 Ok(_) => {
195 self.windows
197 .get_mut(&self.active_window)
198 .and_then(|w| w.split_view_states_mut())
199 .expect("active window must have a populated split layout")
200 .remove(&closing_split);
201
202 let new_active_split = self
204 .windows
205 .get(&self.active_window)
206 .and_then(|w| w.buffers.splits())
207 .map(|(mgr, _)| mgr)
208 .expect("active window must have a populated split layout")
209 .active_split();
210
211 if let Some(view_state) = self
213 .windows
214 .get_mut(&self.active_window)
215 .and_then(|w| w.split_view_states_mut())
216 .expect("active window must have a populated split layout")
217 .get_mut(&new_active_split)
218 {
219 for target in closing_split_tabs {
220 if !view_state.open_buffers.contains(&target) {
222 view_state.open_buffers.push(target);
223 }
224 }
225 }
226
227 self.set_status_message(t!("split.closed").to_string());
230 }
231 Err(e) => {
232 self.set_status_message(
233 t!("split.cannot_close", error = e.to_string()).to_string(),
234 );
235 }
236 }
237
238 self.relayout();
242 }
243
244 pub fn next_split(&mut self) {
246 self.switch_split(true);
247 self.set_status_message(t!("split.next").to_string());
248 }
249
250 pub fn prev_split(&mut self) {
252 self.switch_split(false);
253 self.set_status_message(t!("split.prev").to_string());
254 }
255
256 fn switch_split(&mut self, next: bool) {
258 let previous_buffer = self.active_buffer();
264
265 let was_maximized = self
269 .windows
270 .get(&self.active_window)
271 .and_then(|w| w.buffers.splits())
272 .map(|(mgr, _)| mgr.is_maximized())
273 .unwrap_or(false);
274
275 if next {
276 self.windows
277 .get_mut(&self.active_window)
278 .and_then(|w| w.split_manager_mut())
279 .expect("active window must have a populated split layout")
280 .next_split();
281 } else {
282 self.windows
283 .get_mut(&self.active_window)
284 .and_then(|w| w.split_manager_mut())
285 .expect("active window must have a populated split layout")
286 .prev_split();
287 }
288
289 if was_maximized {
290 self.relayout();
291 }
292
293 let split_id = self
295 .windows
296 .get(&self.active_window)
297 .and_then(|w| w.buffers.splits())
298 .map(|(mgr, _)| mgr)
299 .expect("active window must have a populated split layout")
300 .active_split();
301 self.active_window_mut()
304 .promote_preview_if_not_in_split(split_id);
305 let buffer = self.active_buffer();
306 let tabs_width = self.active_window().effective_tabs_width();
307 self.active_window_mut()
308 .ensure_active_tab_visible(split_id, buffer, tabs_width);
309
310 let buffer_id = self.active_buffer();
311
312 if self.active_window().terminal_mode
315 && self.active_window().is_terminal_buffer(previous_buffer)
316 && !self.active_window().is_terminal_buffer(buffer_id)
317 {
318 self.active_window_mut().terminal_mode = false;
319 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Normal;
320 }
321
322 self.plugin_manager.read().unwrap().run_hook(
324 "buffer_activated",
325 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
326 );
327
328 if self.active_window().is_terminal_buffer(buffer_id) {
330 self.active_window_mut().terminal_mode = true;
331 self.active_window_mut().key_context = crate::input::keybindings::KeyContext::Terminal;
332 }
333 }
334
335 pub fn adjust_split_size(&mut self, delta: f32) {
337 let active_split = self
338 .windows
339 .get(&self.active_window)
340 .and_then(|w| w.buffers.splits())
341 .map(|(mgr, _)| mgr)
342 .expect("active window must have a populated split layout")
343 .active_split();
344 if let Some(container) = self
345 .windows
346 .get(&self.active_window)
347 .and_then(|w| w.buffers.splits())
348 .map(|(mgr, _)| mgr)
349 .expect("active window must have a populated split layout")
350 .parent_container_of(active_split)
351 {
352 self.windows
353 .get_mut(&self.active_window)
354 .and_then(|w| w.split_manager_mut())
355 .expect("active window must have a populated split layout")
356 .adjust_ratio(container, delta);
357
358 let percent = (delta * 100.0) as i32;
359 self.set_status_message(t!("split.size_adjusted", percent = percent).to_string());
360 self.relayout();
362 }
363 }
364
365 pub fn toggle_maximize_split(&mut self) {
367 match self
368 .windows
369 .get_mut(&self.active_window)
370 .and_then(|w| w.split_manager_mut())
371 .expect("active window must have a populated split layout")
372 .toggle_maximize()
373 {
374 Ok(maximized) => {
375 if maximized {
376 self.set_status_message(t!("split.maximized").to_string());
377 } else {
378 self.set_status_message(t!("split.restored").to_string());
379 }
380 self.relayout();
382 }
383 Err(e) => self.set_status_message(e),
384 }
385 }
386
387 pub fn get_separator_areas(&self) -> &[(ContainerId, SplitDirection, u16, u16, u16)] {
390 &self.active_layout().separator_areas
391 }
392
393 pub fn get_tab_layouts(
395 &self,
396 ) -> &std::collections::HashMap<LeafId, crate::view::ui::tabs::TabLayout> {
397 &self.active_layout().tab_layouts
398 }
399
400 pub fn get_split_areas(
403 &self,
404 ) -> &[(
405 LeafId,
406 BufferId,
407 ratatui::layout::Rect,
408 ratatui::layout::Rect,
409 usize,
410 usize,
411 )] {
412 &self.active_layout().split_areas
413 }
414
415 pub fn get_split_ratio(&self, split_id: SplitId) -> Option<f32> {
420 self.windows
421 .get(&self.active_window)
422 .and_then(|w| w.buffers.splits())
423 .map(|(mgr, _)| mgr)
424 .expect("active window must have a populated split layout")
425 .get_ratio(split_id)
426 .or_else(|| self.grouped_split_ratio(crate::model::event::ContainerId(split_id)))
427 }
428
429 pub fn get_active_split(&self) -> LeafId {
431 self.windows
432 .get(&self.active_window)
433 .and_then(|w| w.buffers.splits())
434 .map(|(mgr, _)| mgr)
435 .expect("active window must have a populated split layout")
436 .active_split()
437 }
438
439 pub fn get_split_buffer(&self, split_id: SplitId) -> Option<BufferId> {
441 self.windows
442 .get(&self.active_window)
443 .and_then(|w| w.buffers.splits())
444 .map(|(mgr, _)| mgr)
445 .expect("active window must have a populated split layout")
446 .get_buffer_id(split_id)
447 }
448
449 pub fn get_split_tabs(&self, split_id: LeafId) -> Vec<BufferId> {
451 self.windows
452 .get(&self.active_window)
453 .and_then(|w| w.buffers.splits())
454 .map(|(_, vs)| vs)
455 .expect("active window must have a populated split layout")
456 .get(&split_id)
457 .map(|vs| vs.buffer_tab_ids_vec())
458 .unwrap_or_default()
459 }
460
461 pub fn get_split_count(&self) -> usize {
463 self.windows
464 .get(&self.active_window)
465 .and_then(|w| w.buffers.splits())
466 .map(|(mgr, _)| mgr)
467 .expect("active window must have a populated split layout")
468 .root()
469 .count_leaves()
470 }
471
472 pub fn compute_drop_zone(
474 &self,
475 col: u16,
476 row: u16,
477 source_split_id: LeafId,
478 ) -> Option<super::types::TabDropZone> {
479 self.compute_tab_drop_zone(col, row, source_split_id)
480 }
481}