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.sync_terminal_mode_to_active_buffer();
242
243 self.relayout();
247 }
248
249 pub fn next_split(&mut self) {
251 self.switch_split(true);
252 self.set_status_message(t!("split.next").to_string());
253 }
254
255 pub fn prev_split(&mut self) {
257 self.switch_split(false);
258 self.set_status_message(t!("split.prev").to_string());
259 }
260
261 fn switch_split(&mut self, next: bool) {
263 let was_maximized = self
267 .windows
268 .get(&self.active_window)
269 .and_then(|w| w.buffers.splits())
270 .map(|(mgr, _)| mgr.is_maximized())
271 .unwrap_or(false);
272
273 if next {
274 self.windows
275 .get_mut(&self.active_window)
276 .and_then(|w| w.split_manager_mut())
277 .expect("active window must have a populated split layout")
278 .next_split();
279 } else {
280 self.windows
281 .get_mut(&self.active_window)
282 .and_then(|w| w.split_manager_mut())
283 .expect("active window must have a populated split layout")
284 .prev_split();
285 }
286
287 if was_maximized {
288 self.relayout();
289 }
290
291 let split_id = self
293 .windows
294 .get(&self.active_window)
295 .and_then(|w| w.buffers.splits())
296 .map(|(mgr, _)| mgr)
297 .expect("active window must have a populated split layout")
298 .active_split();
299 self.active_window_mut()
302 .promote_preview_if_not_in_split(split_id);
303 let buffer = self.active_buffer();
304 let tabs_width = self.active_window().effective_tabs_width();
305 self.active_window_mut()
306 .ensure_active_tab_visible(split_id, buffer, tabs_width);
307
308 let buffer_id = self.active_buffer();
309
310 self.sync_terminal_mode_to_active_buffer();
314
315 self.plugin_manager.read().unwrap().run_hook(
317 "buffer_activated",
318 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
319 );
320 }
321
322 pub fn adjust_split_size(&mut self, delta: f32) {
324 let active_split = self
325 .windows
326 .get(&self.active_window)
327 .and_then(|w| w.buffers.splits())
328 .map(|(mgr, _)| mgr)
329 .expect("active window must have a populated split layout")
330 .active_split();
331 if let Some(container) = self
332 .windows
333 .get(&self.active_window)
334 .and_then(|w| w.buffers.splits())
335 .map(|(mgr, _)| mgr)
336 .expect("active window must have a populated split layout")
337 .parent_container_of(active_split)
338 {
339 self.windows
340 .get_mut(&self.active_window)
341 .and_then(|w| w.split_manager_mut())
342 .expect("active window must have a populated split layout")
343 .adjust_ratio(container, delta);
344
345 let percent = (delta * 100.0) as i32;
346 self.set_status_message(t!("split.size_adjusted", percent = percent).to_string());
347 self.relayout();
349 }
350 }
351
352 pub fn toggle_maximize_split(&mut self) {
354 match self
355 .windows
356 .get_mut(&self.active_window)
357 .and_then(|w| w.split_manager_mut())
358 .expect("active window must have a populated split layout")
359 .toggle_maximize()
360 {
361 Ok(maximized) => {
362 if maximized {
363 self.set_status_message(t!("split.maximized").to_string());
364 } else {
365 self.set_status_message(t!("split.restored").to_string());
366 }
367 self.relayout();
369 }
370 Err(e) => self.set_status_message(e),
371 }
372 }
373
374 pub fn get_separator_areas(&self) -> &[(ContainerId, SplitDirection, u16, u16, u16)] {
377 &self.active_layout().separator_areas
378 }
379
380 pub fn get_tab_layouts(
382 &self,
383 ) -> &std::collections::HashMap<LeafId, crate::view::ui::tabs::TabLayout> {
384 &self.active_layout().tab_layouts
385 }
386
387 pub fn get_split_areas(
390 &self,
391 ) -> &[(
392 LeafId,
393 BufferId,
394 ratatui::layout::Rect,
395 ratatui::layout::Rect,
396 usize,
397 usize,
398 )] {
399 &self.active_layout().split_areas
400 }
401
402 pub fn get_split_ratio(&self, split_id: SplitId) -> Option<f32> {
407 self.windows
408 .get(&self.active_window)
409 .and_then(|w| w.buffers.splits())
410 .map(|(mgr, _)| mgr)
411 .expect("active window must have a populated split layout")
412 .get_ratio(split_id)
413 .or_else(|| self.grouped_split_ratio(crate::model::event::ContainerId(split_id)))
414 }
415
416 pub fn get_active_split(&self) -> LeafId {
418 self.windows
419 .get(&self.active_window)
420 .and_then(|w| w.buffers.splits())
421 .map(|(mgr, _)| mgr)
422 .expect("active window must have a populated split layout")
423 .active_split()
424 }
425
426 pub fn get_split_buffer(&self, split_id: SplitId) -> Option<BufferId> {
428 self.windows
429 .get(&self.active_window)
430 .and_then(|w| w.buffers.splits())
431 .map(|(mgr, _)| mgr)
432 .expect("active window must have a populated split layout")
433 .get_buffer_id(split_id)
434 }
435
436 pub fn get_split_tabs(&self, split_id: LeafId) -> Vec<BufferId> {
438 self.windows
439 .get(&self.active_window)
440 .and_then(|w| w.buffers.splits())
441 .map(|(_, vs)| vs)
442 .expect("active window must have a populated split layout")
443 .get(&split_id)
444 .map(|vs| vs.buffer_tab_ids_vec())
445 .unwrap_or_default()
446 }
447
448 pub fn get_split_count(&self) -> usize {
450 self.windows
451 .get(&self.active_window)
452 .and_then(|w| w.buffers.splits())
453 .map(|(mgr, _)| mgr)
454 .expect("active window must have a populated split layout")
455 .root()
456 .count_leaves()
457 }
458
459 pub fn compute_drop_zone(
461 &self,
462 col: u16,
463 row: u16,
464 source_split_id: LeafId,
465 ) -> Option<super::types::TabDropZone> {
466 self.compute_tab_drop_zone(col, row, source_split_id)
467 }
468}