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