fresh/app/
split_actions.rs1use rust_i18n::t;
11
12use crate::model::event::{BufferId, Event, SplitDirection, SplitId};
13use crate::view::split::SplitViewState;
14
15use super::Editor;
16
17impl Editor {
18 pub fn split_pane_horizontal(&mut self) {
20 self.save_current_split_view_state();
22
23 let current_buffer_id = self.active_buffer();
25
26 match self.split_manager.split_active(
28 crate::model::event::SplitDirection::Horizontal,
29 current_buffer_id,
30 0.5,
31 ) {
32 Ok(new_split_id) => {
33 let mut view_state = SplitViewState::with_buffer(
35 self.terminal_width,
36 self.terminal_height,
37 current_buffer_id,
38 );
39 view_state.viewport.line_wrap_enabled = self.config.editor.line_wrap;
40 self.split_view_states.insert(new_split_id, view_state);
41 self.restore_current_split_view_state();
43 self.set_status_message(t!("split.horizontal").to_string());
44 }
45 Err(e) => {
46 self.set_status_message(t!("split.error", error = e.to_string()).to_string());
47 }
48 }
49 }
50
51 pub fn split_pane_vertical(&mut self) {
53 self.save_current_split_view_state();
55
56 let current_buffer_id = self.active_buffer();
58
59 match self.split_manager.split_active(
61 crate::model::event::SplitDirection::Vertical,
62 current_buffer_id,
63 0.5,
64 ) {
65 Ok(new_split_id) => {
66 let mut view_state = SplitViewState::with_buffer(
68 self.terminal_width,
69 self.terminal_height,
70 current_buffer_id,
71 );
72 view_state.viewport.line_wrap_enabled = self.config.editor.line_wrap;
73 self.split_view_states.insert(new_split_id, view_state);
74 self.restore_current_split_view_state();
76 self.set_status_message(t!("split.vertical").to_string());
77 }
78 Err(e) => {
79 self.set_status_message(t!("split.error", error = e.to_string()).to_string());
80 }
81 }
82 }
83
84 pub fn close_active_split(&mut self) {
86 let closing_split = self.split_manager.active_split();
87
88 let closing_split_tabs = self
90 .split_view_states
91 .get(&closing_split)
92 .map(|vs| vs.open_buffers.clone())
93 .unwrap_or_default();
94
95 match self.split_manager.close_split(closing_split) {
96 Ok(_) => {
97 self.split_view_states.remove(&closing_split);
99
100 let new_active_split = self.split_manager.active_split();
102
103 if let Some(view_state) = self.split_view_states.get_mut(&new_active_split) {
105 for buffer_id in closing_split_tabs {
106 if !view_state.open_buffers.contains(&buffer_id) {
108 view_state.open_buffers.push(buffer_id);
109 }
110 }
111 }
112
113 self.sync_split_view_state_to_editor_state();
117
118 self.set_status_message(t!("split.closed").to_string());
119 }
120 Err(e) => {
121 self.set_status_message(
122 t!("split.cannot_close", error = e.to_string()).to_string(),
123 );
124 }
125 }
126 }
127
128 pub fn next_split(&mut self) {
130 self.switch_split(true);
131 self.set_status_message(t!("split.next").to_string());
132 }
133
134 pub fn prev_split(&mut self) {
136 self.switch_split(false);
137 self.set_status_message(t!("split.prev").to_string());
138 }
139
140 fn switch_split(&mut self, next: bool) {
142 self.save_current_split_view_state();
143 if next {
144 self.split_manager.next_split();
145 } else {
146 self.split_manager.prev_split();
147 }
148 self.restore_current_split_view_state();
149
150 let buffer_id = self.active_buffer();
151
152 self.plugin_manager.run_hook(
154 "buffer_activated",
155 crate::services::plugins::hooks::HookArgs::BufferActivated { buffer_id },
156 );
157
158 if self.is_terminal_buffer(buffer_id) {
160 self.terminal_mode = true;
161 self.key_context = crate::input::keybindings::KeyContext::Terminal;
162 }
163 }
164
165 pub(crate) fn save_current_split_view_state(&mut self) {
167 let split_id = self.split_manager.active_split();
168 if let Some(buffer_state) = self.buffers.get(&self.active_buffer()) {
169 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
170 view_state.cursors = buffer_state.cursors.clone();
171 }
173 }
174 }
175
176 pub(crate) fn restore_current_split_view_state(&mut self) {
178 let split_id = self.split_manager.active_split();
179 self.sync_split_view_state_to_editor_state();
182 self.ensure_active_tab_visible(split_id, self.active_buffer(), self.effective_tabs_width());
185 }
186
187 pub(crate) fn sync_split_view_state_to_editor_state(&mut self) {
191 let split_id = self.split_manager.active_split();
192 if let Some(view_state) = self.split_view_states.get(&split_id) {
193 if let Some(buffer_state) = self.buffers.get_mut(&self.active_buffer()) {
194 buffer_state.cursors = view_state.cursors.clone();
195 }
197 }
198 }
199
200 pub(crate) fn adjust_other_split_cursors_for_event(&mut self, event: &Event) {
202 if let Event::BulkEdit { new_cursors, .. } = event {
204 let current_buffer_id = self.active_buffer();
206 let current_split_id = self.split_manager.active_split();
207
208 let splits_for_buffer = self.split_manager.splits_for_buffer(current_buffer_id);
210
211 let buffer_len = self
213 .buffers
214 .get(¤t_buffer_id)
215 .map(|s| s.buffer.len())
216 .unwrap_or(0);
217
218 for split_id in splits_for_buffer {
220 if split_id == current_split_id {
221 continue;
222 }
223
224 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
225 if let Some((_, pos, _)) = new_cursors.first() {
227 let new_pos = (*pos).min(buffer_len);
228 view_state.cursors.primary_mut().position = new_pos;
229 view_state.cursors.primary_mut().anchor = None;
230 }
231 }
232 }
233 return;
234 }
235
236 let adjustments = match event {
238 Event::Insert { position, text, .. } => {
239 vec![(*position, 0, text.len())]
240 }
241 Event::Delete { range, .. } => {
242 vec![(range.start, range.len(), 0)]
243 }
244 Event::Batch { events, .. } => {
245 events
247 .iter()
248 .filter_map(|e| match e {
249 Event::Insert { position, text, .. } => Some((*position, 0, text.len())),
250 Event::Delete { range, .. } => Some((range.start, range.len(), 0)),
251 _ => None,
252 })
253 .collect()
254 }
255 _ => vec![],
256 };
257
258 if adjustments.is_empty() {
259 return;
260 }
261
262 let current_buffer_id = self.active_buffer();
264 let current_split_id = self.split_manager.active_split();
265
266 let splits_for_buffer = self.split_manager.splits_for_buffer(current_buffer_id);
268
269 for split_id in splits_for_buffer {
271 if split_id == current_split_id {
272 continue; }
274
275 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
276 for (edit_pos, old_len, new_len) in &adjustments {
277 view_state
278 .cursors
279 .adjust_for_edit(*edit_pos, *old_len, *new_len);
280 }
281 }
282 }
283 }
284
285 pub fn adjust_split_size(&mut self, delta: f32) {
287 let active_split = self.split_manager.active_split();
288 if let Err(e) = self.split_manager.adjust_ratio(active_split, delta) {
289 self.set_status_message(t!("split.cannot_adjust", error = e).to_string());
290 } else {
291 let percent = (delta * 100.0) as i32;
292 self.set_status_message(t!("split.size_adjusted", percent = percent).to_string());
293 self.resize_visible_terminals();
295 }
296 }
297
298 pub fn toggle_maximize_split(&mut self) {
300 match self.split_manager.toggle_maximize() {
301 Ok(maximized) => {
302 if maximized {
303 self.set_status_message(t!("split.maximized").to_string());
304 } else {
305 self.set_status_message(t!("split.restored").to_string());
306 }
307 self.resize_visible_terminals();
309 }
310 Err(e) => self.set_status_message(e),
311 }
312 }
313
314 pub fn get_separator_areas(&self) -> &[(SplitId, SplitDirection, u16, u16, u16)] {
317 &self.cached_layout.separator_areas
318 }
319
320 pub fn get_tab_layouts(
322 &self,
323 ) -> &std::collections::HashMap<SplitId, crate::view::ui::tabs::TabLayout> {
324 &self.cached_layout.tab_layouts
325 }
326
327 pub fn get_split_areas(
330 &self,
331 ) -> &[(
332 SplitId,
333 BufferId,
334 ratatui::layout::Rect,
335 ratatui::layout::Rect,
336 usize,
337 usize,
338 )] {
339 &self.cached_layout.split_areas
340 }
341
342 pub fn get_split_ratio(&self, split_id: SplitId) -> Option<f32> {
344 self.split_manager.get_ratio(split_id)
345 }
346
347 pub fn get_active_split(&self) -> SplitId {
349 self.split_manager.active_split()
350 }
351
352 pub fn get_split_buffer(&self, split_id: SplitId) -> Option<BufferId> {
354 self.split_manager.get_buffer_id(split_id)
355 }
356
357 pub fn get_split_tabs(&self, split_id: SplitId) -> Vec<BufferId> {
359 self.split_view_states
360 .get(&split_id)
361 .map(|vs| vs.open_buffers.clone())
362 .unwrap_or_default()
363 }
364
365 pub fn get_split_count(&self) -> usize {
367 self.split_manager.root().count_leaves()
368 }
369
370 pub fn compute_drop_zone(
372 &self,
373 col: u16,
374 row: u16,
375 source_split_id: SplitId,
376 ) -> Option<super::types::TabDropZone> {
377 self.compute_tab_drop_zone(col, row, source_split_id)
378 }
379
380 pub(crate) fn sync_editor_state_to_split_view_state(&mut self) {
387 let split_id = self.split_manager.active_split();
388 if let Some(buffer_state) = self.buffers.get(&self.active_buffer()) {
389 if let Some(view_state) = self.split_view_states.get_mut(&split_id) {
390 view_state.cursors = buffer_state.cursors.clone();
391 }
393 }
394 }
395}