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