From ae56f78075ea1f033ab27fef0b3fa6b28e058b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Dom=C3=ADnguez=20Ochoa?= Date: Sat, 26 Apr 2025 20:31:15 +0200 Subject: [PATCH] fixed some issues with text rendering and line wrapping --- src/app/rendering/terminal_text/mod.rs | 59 ++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 8 deletions(-) diff --git a/src/app/rendering/terminal_text/mod.rs b/src/app/rendering/terminal_text/mod.rs index d11656d..0f6f8d8 100644 --- a/src/app/rendering/terminal_text/mod.rs +++ b/src/app/rendering/terminal_text/mod.rs @@ -198,13 +198,45 @@ impl TerminalText { fn get_line_bounds(&self, line: usize) -> TextBounds { // Calculate the bounds for a specific line + if line >= self.buffer.lines.len() { + // Return default bounds for invalid line + return TextBounds { + left: 0, + top: 0, + right: 0, + bottom: 0, + }; + } let line_height = self.buffer.metrics().line_height; let viewport_width = self.viewport.resolution().width; - // Calculate floating point values first - let top_f32 = (line as f32 * line_height).floor(); - let bottom_f32 = ((line as f32 + 1.0) * line_height).ceil(); + // Use layout_runs to get information about wrapped lines + let layout_iter = self.buffer.layout_runs(); + let mut top_line = 0; + let mut bottom_line = 0; + let mut found_line = false; + + // Iterate through layout runs to find our target line + for (visual_line_count, run) in layout_iter.enumerate() { + if run.line_i == line { + // This run belongs to our target line + if !found_line { + // First run of our target line + top_line = visual_line_count; + found_line = true; + } + // Update bottom line for each run we find for this line + bottom_line = visual_line_count + 1; + } else if found_line { + // We've processed all runs for our target line + break; + } + } + + // Calculate bounds based on visual line positions + let top_f32 = (top_line as f32 * line_height).floor(); + let bottom_f32 = (bottom_line as f32 * line_height).ceil(); // Safe conversions with overflow checks let top = if top_f32 > i32::MAX as f32 { @@ -241,15 +273,26 @@ impl TerminalText { // Start from line 0 (no scrolling yet) let start_line = 0; - // Calculate how many complete lines fit in the viewport - // Add 1 to include partially visible lines at the bottom - let visible_lines = (viewport_height / line_height).ceil() as usize + 1; + // Calculate visible lines based on wrapped text + let layout_iter = self.buffer.layout_runs(); + let mut last_logical_line = 0; - // Make sure we don't go beyond the actual number of lines + // Count how many visual lines we have and map to logical lines + for (visual_line_count, run) in layout_iter.enumerate() { + last_logical_line = run.line_i; + + // If we've exceeded the viewport height, we can stop counting + if ((start_line + visual_line_count) as f32 * line_height) > viewport_height { + break; + } + } + + // Add 1 to include partially visible lines at the bottom let end_line = if self.buffer.lines.is_empty() { 0 } else { - (start_line + visible_lines).min(self.buffer.lines.len()) + // Make sure we include at least the last logical line that has content in the viewport + (last_logical_line + 1).min(self.buffer.lines.len()) }; trace!("visible line range goes from {start_line} to {end_line}");