@@ -131,47 +131,50 @@ impl Viewport {
131131
132132 let padding = self . edge_padding . min ( self . height / 4 ) ;
133133
134- // Count visual rows from scroll_position to selected_index
135- let mut rows_above: usize = 0 ;
136- for i in self . scroll_position ..selected_index. min ( total_lines) {
137- rows_above += line_height ( i) ;
138- }
139- let selected_h = line_height ( selected_index. min ( total_lines - 1 ) ) ;
140- let total_rows_through_selected = rows_above + selected_h;
141-
142134 // Selection is above the viewport — scroll up
143135 if selected_index < self . scroll_position {
144136 self . scroll_position = selected_index;
145- // Apply top padding: try to show `padding` visual rows above
146137 let mut pad_rows = 0 ;
147138 while self . scroll_position > 0 && pad_rows < padding {
148139 self . scroll_position -= 1 ;
149140 pad_rows += line_height ( self . scroll_position ) ;
150141 }
142+ return ;
143+ }
144+
145+ // Fast path: if selection is far below scroll_position, jump directly
146+ // near the selection instead of counting millions of rows.
147+ let gap = selected_index - self . scroll_position ;
148+ if gap > self . height * 2 {
149+ // Jump scroll_position close to selection, then fine-tune below
150+ self . scroll_position = selected_index. saturating_sub ( self . height . saturating_sub ( 1 ) ) ;
151+ }
152+
153+ // Count visual rows from scroll_position to selected_index
154+ let mut rows_above: usize = 0 ;
155+ let end = selected_index. min ( total_lines) ;
156+ for i in self . scroll_position ..end {
157+ rows_above += line_height ( i) ;
151158 }
159+ let selected_h = line_height ( selected_index. min ( total_lines - 1 ) ) ;
160+ let total_rows_through_selected = rows_above + selected_h;
161+
152162 // Selection is below the viewport — scroll down
153- else if total_rows_through_selected > self . height {
163+ if total_rows_through_selected > self . height {
154164 // Walk scroll_position forward until selected line fits on screen
155- while total_lines > 0 && self . scroll_position < selected_index {
156- let mut rows: usize = 0 ;
157- for i in self . scroll_position ..=selected_index. min ( total_lines - 1 ) {
158- rows += line_height ( i) ;
159- }
160- if rows <= self . height {
161- break ;
162- }
165+ let mut rows = total_rows_through_selected;
166+ while rows > self . height && self . scroll_position < selected_index {
167+ rows -= line_height ( self . scroll_position ) ;
163168 self . scroll_position += 1 ;
164169 }
165- // Apply bottom padding: try to show `padding` visual rows below
166- // by scrolling further if there's content below
170+ // Apply bottom padding
167171 let mut pad_rows = 0 ;
168172 let mut pad_idx = selected_index + 1 ;
169173 while pad_idx < total_lines && pad_rows < padding {
170174 pad_rows += line_height ( pad_idx) ;
171175 pad_idx += 1 ;
172176 }
173177 if pad_rows > 0 {
174- // Check if padding lines fit; if not, scroll more
175178 let mut rows: usize = 0 ;
176179 for i in self . scroll_position ..pad_idx. min ( total_lines) {
177180 rows += line_height ( i) ;
@@ -184,12 +187,10 @@ impl Viewport {
184187 }
185188 // Selection is within the visible area — check edge padding
186189 else if padding > 0 {
187- // Top padding: visual rows from scroll_position to selected
190+ // Top padding
188191 let mut top_visual = 0 ;
189- let mut top_count = 0 ;
190192 for i in self . scroll_position ..selected_index {
191193 top_visual += line_height ( i) ;
192- top_count += 1 ;
193194 if top_visual >= padding {
194195 break ;
195196 }
@@ -202,9 +203,8 @@ impl Viewport {
202203 }
203204 }
204205
205- // Bottom padding: visual rows from selected to end of viewport
206+ // Bottom padding
206207 let rows_after = self . height . saturating_sub ( total_rows_through_selected) ;
207- // Count visual rows of `padding` lines below selection
208208 let mut pad_visual = 0 ;
209209 let mut i = selected_index + 1 ;
210210 let mut pad_count = 0 ;
@@ -213,7 +213,6 @@ impl Viewport {
213213 pad_count += 1 ;
214214 i += 1 ;
215215 }
216- let _ = top_count; // used for padding logic above
217216 if rows_after < pad_visual. min ( padding) {
218217 let mut excess = pad_visual. min ( padding) - rows_after;
219218 while excess > 0 && self . scroll_position < selected_index {
@@ -224,11 +223,7 @@ impl Viewport {
224223 }
225224 }
226225
227- // Clamp: don't scroll past the point where last line is at bottom
228- // Compute max_scroll in visual terms: find earliest scroll_position
229- // where the last line still fits on screen.
230- // For simplicity (and O(1) for non-wrap), just ensure scroll_position
231- // doesn't exceed total_lines - 1.
226+ // Clamp scroll_position
232227 if self . scroll_position >= total_lines {
233228 self . scroll_position = total_lines. saturating_sub ( 1 ) ;
234229 }
@@ -846,4 +841,20 @@ mod tests {
846841 assert_eq ! ( vp. scroll_position, 0 ) ;
847842 assert_eq ! ( vp. selected_line( ) , 9 ) ;
848843 }
844+
845+ #[ test]
846+ fn test_resolve_large_file_scroll_position_zero ( ) {
847+ // Simulates opening a large file where anchor is at the end
848+ // but scroll_position starts at 0. Must not iterate millions of lines.
849+ let total = 50_000_000 ;
850+ let mut vp = Viewport :: new ( total - 1 ) ;
851+ vp. scroll_position = 0 ;
852+ let lines: Vec < usize > = ( 0 ..total) . collect ( ) ;
853+
854+ let view = vp. resolve ( & lines, 50 ) ;
855+
856+ assert_eq ! ( view. selected_index, total - 1 ) ;
857+ // scroll_position should be near the end
858+ assert ! ( view. scroll_position >= total - 50 ) ;
859+ }
849860}
0 commit comments