1use crate::model::event::{BufferId, LeafId, SplitId};
12use crate::state::EditorState;
13use std::collections::HashMap;
14
15impl crate::app::window::Window {
16 pub fn ensure_active_tab_visible(
21 &mut self,
22 split_id: LeafId,
23 active_buffer: BufferId,
24 available_width: u16,
25 ) {
26 tracing::debug!(
27 "ensure_active_tab_visible called: split={:?}, buffer={:?}, width={}",
28 split_id,
29 active_buffer,
30 available_width
31 );
32 let group_names: std::collections::HashMap<LeafId, String> = self
33 .grouped_subtrees
34 .iter()
35 .filter_map(|(leaf_id, node)| {
36 if let crate::view::split::SplitNode::Grouped { name, .. } = node {
37 Some((*leaf_id, name.clone()))
38 } else {
39 None
40 }
41 })
42 .collect();
43 let metadata = &self.buffer_metadata;
44 let composites = &self.composite_buffers;
45
46 self.buffers.with_all_mut(|buffer_map, _mgr, vs_map| {
47 let Some(view_state) = vs_map.get_mut(&split_id) else {
48 return;
49 };
50 let split_buffers = view_state.open_buffers.clone();
51 let (tab_widths, rendered_targets) = crate::view::ui::tabs::calculate_tab_widths(
52 &split_buffers,
53 buffer_map,
54 metadata,
55 composites,
56 &group_names,
57 );
58
59 let total_tabs_width: usize = tab_widths.iter().sum();
60 let max_visible_width = available_width as usize;
61
62 let active_target = view_state.active_target();
63 let active_target = if matches!(active_target, crate::view::split::TabTarget::Buffer(_))
64 {
65 crate::view::split::TabTarget::Buffer(active_buffer)
66 } else {
67 active_target
68 };
69
70 let active_tab_index = rendered_targets.iter().position(|t| *t == active_target);
71 let active_width_index =
72 active_tab_index.map(|buf_idx| if buf_idx == 0 { 0 } else { buf_idx * 2 });
73
74 let old_offset = view_state.tab_scroll_offset;
75 let new_scroll_offset = if let Some(idx) = active_width_index {
76 crate::view::ui::tabs::scroll_to_show_tab(
77 &tab_widths,
78 idx,
79 view_state.tab_scroll_offset,
80 max_visible_width,
81 )
82 } else {
83 view_state
84 .tab_scroll_offset
85 .min(total_tabs_width.saturating_sub(max_visible_width))
86 };
87
88 tracing::debug!(
89 " -> offset: {} -> {} (idx={:?}, max_width={}, total={})",
90 old_offset,
91 new_scroll_offset,
92 active_width_index,
93 max_visible_width,
94 total_tabs_width
95 );
96 view_state.tab_scroll_offset = new_scroll_offset;
97 });
98 }
99
100 pub(super) fn sync_scroll_groups(&mut self) {
111 let (mgr, vs_map) = self
112 .buffers
113 .splits()
114 .expect("window must have a populated split layout");
115 let active_split = mgr.active_split();
116 let group_count = self.scroll_sync_manager.groups().len();
117
118 if group_count > 0 {
119 tracing::debug!(
120 "sync_scroll_groups: active_split={:?}, {} groups",
121 active_split,
122 group_count
123 );
124 }
125
126 let sync_info: Vec<_> = self
127 .scroll_sync_manager
128 .groups()
129 .iter()
130 .filter_map(|group| {
131 tracing::debug!(
132 "sync_scroll_groups: checking group {}, left={:?}, right={:?}",
133 group.id,
134 group.left_split,
135 group.right_split
136 );
137
138 if !group.contains_split(active_split.into()) {
139 tracing::debug!(
140 "sync_scroll_groups: active split {:?} not in group",
141 active_split
142 );
143 return None;
144 }
145
146 let active_top_byte = vs_map.get(&active_split)?.viewport.top_byte;
147 let active_buffer_id = mgr.buffer_for_split(active_split)?;
148 let buffer_state = self.buffers.get(&active_buffer_id)?;
149 let buffer_len = buffer_state.buffer.len();
150 let active_line = buffer_state.buffer.get_line_number(active_top_byte);
151
152 tracing::debug!(
153 "sync_scroll_groups: active_split={:?}, buffer_id={:?}, top_byte={}, buffer_len={}, active_line={}",
154 active_split,
155 active_buffer_id,
156 active_top_byte,
157 buffer_len,
158 active_line
159 );
160
161 let (other_split, other_line) = if group.is_left_split(active_split.into()) {
162 (group.right_split, group.left_to_right_line(active_line))
163 } else {
164 (group.left_split, group.right_to_left_line(active_line))
165 };
166
167 tracing::debug!(
168 "sync_scroll_groups: syncing other_split={:?} to line {}",
169 other_split,
170 other_line
171 );
172
173 Some((other_split, other_line))
174 })
175 .collect();
176
177 for (other_split, target_line) in sync_info {
178 let other_leaf = LeafId(other_split);
179 let buffer_id = self
180 .buffers
181 .splits()
182 .expect("window must have a populated split layout")
183 .0
184 .buffer_for_split(other_leaf);
185 if let Some(buffer_id) = buffer_id {
186 self.scroll_split_viewport_to(buffer_id, other_leaf, target_line, false);
187 }
188 }
189
190 let active_buffer_id = if self.same_buffer_scroll_sync {
191 self.buffers
192 .splits()
193 .expect("window must have a populated split layout")
194 .0
195 .buffer_for_split(active_split)
196 } else {
197 None
198 };
199 if let Some(active_buf_id) = active_buffer_id {
200 let (mgr, vs_map) = self
201 .buffers
202 .splits()
203 .expect("window must have a populated split layout");
204 let active_top_byte = vs_map.get(&active_split).map(|vs| vs.viewport.top_byte);
205 let active_viewport_height = vs_map
206 .get(&active_split)
207 .map(|vs| vs.viewport.visible_line_count())
208 .unwrap_or(0);
209
210 if let Some(top_byte) = active_top_byte {
211 let other_splits: Vec<_> = vs_map
212 .keys()
213 .filter(|&&s| {
214 s != active_split
215 && mgr.buffer_for_split(s) == Some(active_buf_id)
216 && !self.scroll_sync_manager.is_split_synced(s.into())
217 })
218 .copied()
219 .collect();
220
221 if !other_splits.is_empty() {
222 let at_bottom = if let Some(state) = self.buffers.get_mut(&active_buf_id) {
223 let mut iter = state.buffer.line_iterator(top_byte, 80);
224 let mut lines_remaining = 0;
225 while iter.next_line().is_some() {
226 lines_remaining += 1;
227 if lines_remaining > active_viewport_height {
228 break;
229 }
230 }
231 lines_remaining <= active_viewport_height
232 } else {
233 false
234 };
235
236 let (_, vs_map_mut) = self
237 .buffers
238 .splits_mut()
239 .expect("window must have a populated split layout");
240 for other_split in other_splits {
241 if let Some(view_state) = vs_map_mut.get_mut(&other_split) {
242 view_state.viewport.top_byte = top_byte;
243 view_state.viewport.sync_scroll_to_end = at_bottom;
244 }
245 }
246 }
247 }
248 }
249 }
250
251 pub(super) fn pre_sync_ensure_visible(&mut self, active_split: LeafId) {
260 let group_info = self
261 .scroll_sync_manager
262 .find_group_for_split(active_split.into())
263 .map(|g| (g.left_split, g.right_split));
264
265 if let Some((left_split, right_split)) = group_info {
266 let buffer_id = self
267 .buffers
268 .splits()
269 .expect("window must have a populated split layout")
270 .0
271 .buffer_for_split(active_split);
272 if let Some(buffer_id) = buffer_id {
273 self.ensure_cursor_visible_for_split(buffer_id, active_split);
274 }
275
276 let active_sid: SplitId = active_split.into();
277 let other_split: SplitId = if active_sid == left_split {
278 right_split
279 } else {
280 left_split
281 };
282
283 if let Some((_, vs_map)) = self.buffers.splits_mut() {
284 if let Some(view_state) = vs_map.get_mut(&LeafId(other_split)) {
285 view_state.viewport.set_skip_ensure_visible();
286 tracing::debug!(
287 "pre_sync_ensure_visible: marked other split {:?} to skip ensure_visible",
288 other_split
289 );
290 }
291 }
292 }
293
294 if !self.same_buffer_scroll_sync {
295 return;
296 }
297 let active_buf_id = match self
298 .buffers
299 .splits()
300 .expect("window must have a populated split layout")
301 .0
302 .buffer_for_split(active_split)
303 {
304 Some(b) => b,
305 None => return,
306 };
307
308 let other_same_buffer_splits: Vec<_> = {
309 let (mgr, vs_map) = self
310 .buffers
311 .splits()
312 .expect("window must have a populated split layout");
313 vs_map
314 .keys()
315 .filter(|&&s| {
316 s != active_split
317 && mgr.buffer_for_split(s) == Some(active_buf_id)
318 && !self.scroll_sync_manager.is_split_synced(s.into())
319 })
320 .copied()
321 .collect()
322 };
323
324 if let Some((_, vs_map)) = self.buffers.splits_mut() {
325 for other_split in other_same_buffer_splits {
326 if let Some(view_state) = vs_map.get_mut(&other_split) {
327 view_state.viewport.set_skip_ensure_visible();
328 }
329 }
330 }
331 }
332}