terminal: Fix mouse scroll report count for negative scroll lines (#49931)

Emamul Andalib created

Follow-up to #45600.

## Summary

Fix mouse scroll reports sending only one event when scrolling down in
terminal apps with mouse mode (tmux, neovim, etc.), regardless of how
many lines were scrolled.

## The Problem

After #45600, trackpad scrolling speed was fixed. But when scrolling
**down** (negative `scroll_lines`), the terminal was still sending only
**one** scroll report per gesture, no matter how many lines the user
scrolled. Scrolling up worked correctly.

## Root Cause

In `scroll_report()` we had:

https://github.com/zed-industries/zed/blob/a8043dcff8f28a0443d7ec238e7f020689ebe1ff/crates/terminal/src/mappings/mouse.rs#L96

`scroll_lines` can be negative (scroll down) or positive (scroll up).
For negative values:

| scroll_lines | max(scroll_lines, 1) | Reports sent | Verdict |
|--------------|---------------------|--------------|------|
| 3 (up)       | 3                   | 3           |Right
| -3 (down)    | 1                   | 1           |WRONG|

So we always sent exactly 1 report when scrolling down, losing the
scroll magnitude.

Use `scroll_lines.unsigned_abs()` instead of `max(scroll_lines, 1)`.
This matches how `alt_scroll()` in the same file already handles
`scroll_lines`. Now both directions send the correct number of reports.

https://github.com/zed-industries/zed/blob/a8043dcff8f28a0443d7ec238e7f020689ebe1ff/crates/terminal/src/mappings/mouse.rs#L102

## Testing

- Added unit tests: `scroll_report_repeats_for_negative_scroll_lines`
and `scroll_report_repeats_for_positive_scroll_lines`
- Manually tested scrolling in tmux and neovim with mouse mode

---

Release Notes:

- Fixed mouse scroll in terminal apps (tmux, neovim, etc.) only sending
one scroll event when scrolling down, regardless of scroll amount

Change summary

crates/terminal/src/mappings/mouse.rs | 46 +++++++++++++++++++++++++++-
1 file changed, 44 insertions(+), 2 deletions(-)

Detailed changes

crates/terminal/src/mappings/mouse.rs 🔗

@@ -1,4 +1,4 @@
-use std::cmp::{self, max, min};
+use std::cmp::{self, min};
 use std::iter::repeat;
 
 use alacritty_terminal::grid::Dimensions;
@@ -93,12 +93,54 @@ pub fn scroll_report(
             e.modifiers,
             MouseFormat::from_mode(mode),
         )
-        .map(|report| repeat(report).take(max(scroll_lines, 1) as usize))
+        .map(|report| repeat(report).take(scroll_lines.unsigned_abs() as usize))
     } else {
         None
     }
 }
 
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use gpui::{ScrollDelta, TouchPhase, point};
+
+    #[test]
+    fn scroll_report_repeats_for_negative_scroll_lines() {
+        let grid_point = AlacPoint::new(GridLine(0), GridCol(0));
+
+        let scroll_event = ScrollWheelEvent {
+            delta: ScrollDelta::Lines(point(0., -1.)),
+            touch_phase: TouchPhase::Moved,
+            ..Default::default()
+        };
+
+        let mode = TermMode::MOUSE_MODE;
+        let reports: Vec<Vec<u8>> = scroll_report(grid_point, -3, &scroll_event, mode)
+            .expect("mouse mode should produce a scroll report")
+            .collect();
+
+        assert_eq!(reports.len(), 3);
+    }
+
+    #[test]
+    fn scroll_report_repeats_for_positive_scroll_lines() {
+        let grid_point = AlacPoint::new(GridLine(0), GridCol(0));
+
+        let scroll_event = ScrollWheelEvent {
+            delta: ScrollDelta::Lines(point(0., 1.)),
+            touch_phase: TouchPhase::Moved,
+            ..Default::default()
+        };
+
+        let mode = TermMode::MOUSE_MODE;
+        let reports: Vec<Vec<u8>> = scroll_report(grid_point, 3, &scroll_event, mode)
+            .expect("mouse mode should produce a scroll report")
+            .collect();
+
+        assert_eq!(reports.len(), 3);
+    }
+}
+
 pub fn alt_scroll(scroll_lines: i32) -> Vec<u8> {
     let cmd = if scroll_lines > 0 { b'A' } else { b'B' };