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