Skip to content

feat: add mouse support#1

Merged
io41 merged 1 commit into
mainfrom
mouse-support
Nov 9, 2025
Merged

feat: add mouse support#1
io41 merged 1 commit into
mainfrom
mouse-support

Conversation

@io41
Copy link
Copy Markdown
Owner

@io41 io41 commented Nov 9, 2025

Useful for integration testing

Summary by CodeRabbit

  • New Features

    • Added mouse input: press, release, drag, and click; wheel up/down; modifier keys (Shift, Alt, Control); 1‑indexed coordinates with out‑of‑bounds warnings; click emits press+release. Mouse events use SGR mouse protocol and require mouse tracking to be enabled (the command itself does not trigger UI events).
  • Documentation

    • Expanded README with mouse command examples and usage (content duplicated in two locations).
    • Added WebSocket API docs describing /ws/events and /ws/alis endpoints.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 9, 2025

Walkthrough

Adds mouse-command support: new public mouse types and encoder, stdio parsing and tests for mouse JSON, main loop handling (including MouseClick as press+release with bounds warnings), Session.size() accessor, and expanded README mouse and WebSocket documentation (duplicated block).

Changes

Cohort / File(s) Summary
Documentation
README.md
Adds two duplicated documentation blocks describing the STDIO mouse command (JSON payload examples, event types: press,release,drag,click; buttons: left,middle,right,wheel_up,wheel_down; 1-indexed coords with out-of-bounds note; optional modifiers shift,alt,control; SGR protocol note) and expands WebSocket API docs (/ws/events, /ws/alis).
Core types & encoding
src/command.rs
Adds public types: MouseEvent, MouseEventType (Press,Release,Drag), MouseButton (Left,Middle,Right,WheelUp,WheelDown), MouseModifiers; adds Command::Mouse and Command::MouseClick variants; implements pub fn mouse_to_bytes(&MouseEvent) -> Vec<u8> producing SGR-like mouse sequences.
Stdio API parsing & tests
src/api/stdio.rs
Introduces private MouseArgs for deserialization; extends build_command() to parse/validate mouse args (map event/button strings, require non-zero 1-indexed row/col, build modifiers, detect click), returning Mouse or MouseClick; adds unit tests covering valid events, wheel buttons, modifiers and invalid inputs (bad event/button, zero coords, missing args).
Main event loop integration
src/main.rs
Adds validate_mouse_coordinates helper using session size; handles Command::Mouse by validating, encoding via mouse_to_bytes, and sending bytes; handles Command::MouseClick by sending a Press then Release sequence (both validated and encoded); logs out-of-bounds warnings.
Session API
src/session.rs
Adds pub fn size(&self) -> (usize, usize) forwarding to underlying VT size() to expose terminal dimensions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant STDIO_API
    participant Parser
    participant MainLoop
    participant Session
    participant Encoder
    participant InputChan

    Client->>STDIO_API: send `mouse` JSON
    STDIO_API->>Parser: deserialize MouseArgs
    Parser->>Parser: validate event/button, require row/col ≥ 1
    alt click event
        Parser-->>MainLoop: Command::MouseClick(MouseEvent)
    else other event
        Parser-->>MainLoop: Command::Mouse(MouseEvent)
    end

    MainLoop->>Session: size()
    Session-->>MainLoop: (rows, cols)
    MainLoop->>MainLoop: validate coordinates vs size
    alt out of bounds
        MainLoop->>MainLoop: log warning
    end

    alt Mouse
        MainLoop->>Encoder: mouse_to_bytes(event)
        Encoder-->>MainLoop: bytes (SGR)
        MainLoop->>InputChan: send bytes
    else MouseClick
        MainLoop->>Encoder: mouse_to_bytes(Press)
        Encoder-->>MainLoop: bytes_press
        MainLoop->>InputChan: send bytes_press
        MainLoop->>Encoder: mouse_to_bytes(Release)
        Encoder-->>MainLoop: bytes_release
        MainLoop->>InputChan: send bytes_release
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review SGR encoding correctness in mouse_to_bytes() (button base, modifier bits, drag bit, M vs m suffix).
  • Verify parsing/validation in src/api/stdio.rs (event/button mapping, 1-indexed row/col handling, click detection).
  • Confirm validate_mouse_coordinates logic and consistent use of Session.size().
  • Inspect added unit tests for completeness and edge cases.
  • Note README duplication that should be consolidated.

Poem

🐰 I nibble bytes and hop on keys,
Clicks and drags now dance with ease.
From one-based rows the SGRs take flight,
Press then release — a tiny delight.
Hooray for mice in terminal light! 🖱️✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add mouse support' accurately and concisely describes the main change—adding comprehensive mouse event support across multiple modules including command definitions, stdio parsing, and main event handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mouse-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/main.rs (1)

114-134: Consider extracting the coordinate validation logic.

The MouseClick handler duplicates the same coordinate validation from the Mouse handler. Consider extracting this into a helper function to maintain DRY principles.

Example refactor:

fn validate_mouse_coordinates(mouse_event: &command::MouseEvent, session: &Session) {
    let (cols, rows) = session.size();
    if mouse_event.row > rows || mouse_event.col > cols {
        eprintln!(
            "warning: mouse coordinates ({},{}) exceed terminal size ({}x{})",
            mouse_event.col, mouse_event.row, cols, rows
        );
    }
}

Then use it in both handlers:

Some(Command::Mouse(mouse_event)) => {
    validate_mouse_coordinates(&mouse_event, &session);
    let data = command::mouse_to_bytes(&mouse_event);
    input_tx.send(data).await?;
}

Some(Command::MouseClick(mouse_event)) => {
    validate_mouse_coordinates(&mouse_event, &session);
    // Send press event
    ...
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed569e9 and b9868f1.

📒 Files selected for processing (5)
  • README.md (1 hunks)
  • src/api/stdio.rs (4 hunks)
  • src/command.rs (2 hunks)
  • src/main.rs (1 hunks)
  • src/session.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/main.rs (3)
src/cli.rs (2)
  • cols (38-40)
  • rows (42-44)
src/session.rs (1)
  • size (79-81)
src/command.rs (1)
  • mouse_to_bytes (66-99)
🔇 Additional comments (8)
src/session.rs (1)

79-81: LGTM!

Clean accessor that exposes terminal dimensions for coordinate validation in mouse event handling.

src/main.rs (1)

101-112: Mouse event handling looks correct.

The coordinate validation and encoding logic is appropriate. Note that out-of-bounds coordinates trigger a warning but are still forwarded to the application, which aligns with the documented behavior.

README.md (1)

208-250: Excellent documentation!

The mouse command documentation is comprehensive and clear:

  • JSON payload format with all fields explained
  • Event types and their semantics
  • Supported buttons including wheel events
  • Important notes about 1-indexed coordinates and SGR protocol requirements
  • Practical examples with modifiers

This will help users understand how to properly use the mouse command for integration testing.

src/api/stdio.rs (3)

27-39: Well-structured MouseArgs for deserialization.

The struct properly captures all mouse event fields with appropriate defaults for optional modifiers using #[serde(default)].


123-170: Robust mouse command parsing with proper validation.

The implementation correctly:

  • Distinguishes between click (convenience) and explicit event types
  • Validates event type and button strings with clear error messages
  • Enforces 1-indexed coordinates by rejecting zero values
  • Constructs MouseEvent with all required fields and modifiers

554-688: Comprehensive test coverage for mouse parsing.

The test suite thoroughly validates:

  • All event types (click, press, release, drag)
  • All button types (left, middle, right, wheel_up, wheel_down)
  • Modifier combinations
  • Error cases (invalid event, invalid button, zero coordinates, missing args)

This provides excellent confidence in the parsing logic.

src/command.rs (2)

34-64: Clean and well-structured mouse event types.

The data structures properly model:

  • Event lifecycle (Press, Release, Drag)
  • Button types including wheel events
  • Optional modifiers with sensible defaults
  • All fields are public for easy construction and inspection

The Clone and PartialEq derives enable flexible usage patterns.


66-99: Correct SGR extended mouse protocol encoding.

The implementation properly encodes mouse events per the SGR protocol specification:

  • Button codes: Left(0), Middle(1), Right(2), WheelUp(64), WheelDown(65) ✓
  • Modifier bits: Shift(+4), Alt(+8), Control(+16) ✓
  • Motion bit: Drag(+32) ✓
  • Suffix: 'M' for press/drag, 'm' for release ✓
  • Format: ESC[<btn;col;row<suffix>

Useful for integration testing
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/main.rs (1)

69-77: Consider validating lower bounds for mouse coordinates.

The validation checks upper bounds but doesn't verify that coordinates are at least 1. Since SGR mouse protocol uses 1-based coordinates, coordinates less than 1 are invalid and could cause issues.

Apply this diff to add lower bound validation:

 fn validate_mouse_coordinates(mouse_event: &command::MouseEvent, session: &Session) {
     let (cols, rows) = session.size();
-    if mouse_event.row > rows || mouse_event.col > cols {
+    if mouse_event.row < 1 || mouse_event.row > rows || mouse_event.col < 1 || mouse_event.col > cols {
         eprintln!(
             "warning: mouse coordinates ({},{}) exceed terminal size ({}x{})",
             mouse_event.col, mouse_event.row, cols, rows
         );
     }
 }
README.md (2)

240-242: Clarify optional fields syntax in JSON examples.

The example on lines 240-242 shows modifiers as boolean fields. Consider clarifying in the documentation whether omitted modifier fields default to false, or if they must be explicitly specified. This is especially important for API usability.

Current text (lines 234-235) states "default false", which is good, but the example could be enhanced to show both cases (with explicit modifiers and with omitted modifiers).

Consider adding a second example demonstrating omitted modifiers:

 Example with modifiers:
 ```json
 { "type": "mouse", "event": "click", "button": "left", "row": 10, "col": 25, "shift": true, "control": true }

+Without modifiers (all default to false):
+json +{ "type": "mouse", "event": "click", "button": "left", "row": 10, "col": 25 } +


---

`244-248`: **Document when mouse events are actually sent.**

Lines 244-248 correctly note that the application must enable mouse tracking for events to take effect. However, the statement could be clearer about what happens if mouse tracking is not enabled. Consider expanding this note to explicitly state: "If the application has not enabled mouse tracking, the mouse events will be sent but will have no visible effect on the application."



This is a minor clarity improvement for users who may not be familiar with terminal mouse protocols.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b9868f1eb615f81103e4578f1937e9613e0dec93 and 68b14dcdf3b26aa373e112955b51026d533fb635.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `README.md` (1 hunks)
* `src/api/stdio.rs` (4 hunks)
* `src/command.rs` (2 hunks)
* `src/main.rs` (2 hunks)
* `src/session.rs` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* src/session.rs
* src/api/stdio.rs
* src/command.rs

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code graph analysis (1)</summary>

<details>
<summary>src/main.rs (2)</summary><blockquote>

<details>
<summary>src/cli.rs (2)</summary>

* `cols` (38-40)
* `rows` (42-44)

</details>
<details>
<summary>src/command.rs (1)</summary>

* `mouse_to_bytes` (66-99)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>src/main.rs (2)</summary><blockquote>

`111-115`: **LGTM!**

The mouse event handling is straightforward and follows the same pattern as the existing `Command::Input` handling.

---

`117-131`: **Consider the atomicity of press/release events.**

If the press event is sent successfully but the release event fails (e.g., channel closes or receiver drops), the terminal could be left with a button in pressed state. While this may be acceptable for integration testing, consider whether transactional semantics or error recovery is needed.



This is likely fine for the stated use case, but verify whether:
1. The input channel can close between the two sends
2. A stuck button state would cause issues for your integration tests
3. You need any cleanup/recovery mechanism if the release fails

</blockquote></details>
<details>
<summary>README.md (2)</summary><blockquote>

`208-251`: **No duplication found—original review comment's concern is unfounded.**

The verification confirms there is only ONE mouse documentation section in README.md (line 208). The "SGR extended mouse protocol" phrase appears once, and there is only one "Event types:" block. The original review comment's claim of duplicate mouse documentation is incorrect and should be disregarded.



> Likely an incorrect or invalid review comment.

---

`208-251`: **All documented mouse features are fully implemented and functional.**

Verification confirms:
- ✓ All 4 event types supported: `press`, `release`, `drag`, and `click` (click is a command variant that sends press+release events)
- ✓ All 5 buttons: `left`, `middle`, `right`, `wheel_up`, `wheel_down`
- ✓ All 3 modifiers: `shift`, `alt`, `control`
- ✓ 1-indexed coordinates accepted and passed directly to SGR protocol format
- ✓ Out-of-bounds validation with warnings issued (src/main.rs:69-81)
- ✓ SGR protocol encoding implemented (src/command.rs:66-98)

No implementation gaps found. The code correctly implements the documented behavior, including click as a convenience shorthand that sends both press and release events.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@io41 io41 merged commit 76966d2 into main Nov 9, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant