Skip to content

ghostty: add ghostty_surface_set_selection_range C API#64

Open
tayiorbeii wants to merge 2 commits into
manaflow-ai:mainfrom
tayiorbeii:copy-mode-cursor-ux-slice-0
Open

ghostty: add ghostty_surface_set_selection_range C API#64
tayiorbeii wants to merge 2 commits into
manaflow-ai:mainfrom
tayiorbeii:copy-mode-cursor-ux-slice-0

Conversation

@tayiorbeii

@tayiorbeii tayiorbeii commented May 25, 2026

Copy link
Copy Markdown

Adds the ghostty_surface_set_selection_range C API function for programmatic selection control in embedded mode.

This is needed by cmux for the copy-mode cursor UX slices (see plans/2026-05-22-copy-mode-cursor-ux-slices.md Slice 0).

Changes:

  • include/ghostty.h: Declare ghostty_surface_set_selection_range
  • src/Surface.zig: Implement setSelectionRange public method
  • src/apprt/embedded.zig: Implement setSelectionRange on embedded surface

Acceptance:

  • Function is exported in the C API
  • Existing behavior unchanged

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Summary by cubic

Adds ghostty_surface_set_selection_range to the embedded C API to set text selections using screen coordinates (supports rectangular). Enables cmux copy-mode cursor UX Slice 0 while keeping existing selection APIs unchanged.

  • New Features
    • New C API: ghostty_surface_set_selection_range(ghostty_surface_t, row_start, col_start, row_end, col_end, is_rectangular).
    • Validates and pins inputs, updates selection, marks dirty, queues render; returns false and logs on error.

Written for commit 0a650e9. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a public API to set text selection by specifying start/end row and column coordinates.
    • Supports both linear and rectangular selection modes for precise programmatic control; operations report success/failure so callers can react.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cf0cee2a-b846-42d8-8adf-49d43498dd23

📥 Commits

Reviewing files that changed from the base of the PR and between c1d033e and 0a650e9.

📒 Files selected for processing (1)
  • include/ghostty.h

📝 Walkthrough

Walkthrough

Adds a public C API and Zig implementation to set a surface selection by start/end row and column with an optional rectangular flag; header declaration, Zig method converting coordinates and updating selection under lock, and C export wrapper are included.

Changes

Selection Range API Implementation

Layer / File(s) Summary
C API declaration
include/ghostty.h
Adds GHOSTTY_API bool ghostty_surface_set_selection_range(ghostty_surface_t, uint32_t, uint32_t, uint32_t, uint32_t, bool);.
Zig: Surface.setSelectionRange
src/Surface.zig
Adds setSelectionRange that casts columns to cell-count type, resolves start/end pins for the active screen pages, applies selection with is_rectangular, marks screen.dirty.selection, queues a render, and returns false on cast or pin lookup failure.
C export wrapper
src/apprt/embedded.zig
Adds ghostty_surface_set_selection_range which forwards parameters to core_surface.setSelectionRange, logs a warning on error, and returns a boolean success indicator.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A tiny rabbit taps the code,
Row and column paths bestowed,
From header, Zig, to C it hops,
Locks the state and sets the box,
Selection drawn — a quiet nod.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically describes the main change: adding a new C API function ghostty_surface_set_selection_range. The title is concise and directly matches the primary objective of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@greptile-apps

greptile-apps Bot commented May 25, 2026

Copy link
Copy Markdown

Greptile Summary

Adds ghostty_surface_set_selection_range as a new C API function that sets a terminal selection from buffer (screen) coordinates, intended for cmux copy-mode cursor UX. The implementation correctly acquires the renderer mutex, casts column values to CellCountInt (u16) with overflow protection, resolves screen-space pins via the page list, calls the existing setSelection helper, marks the selection dirty, and queues a render — following the same pattern as the sibling selectCursorCell and clearSelection functions.

  • src/Surface.zig: New pub fn setSelectionRange method mirrors selectCursorCell in lock/pin/dirty/render pattern; row_start/row_end pass as u32 directly to Coordinate.y (correct type), while column values are safely narrowed to u16 via std.math.cast.
  • src/apprt/embedded.zig: Exported C function delegates to core_surface.setSelectionRange with the same catch |err| { log.warn; return false; } error-handling idiom used by the other selection exports.
  • include/ghostty.h: Declaration is syntactically valid but omits parameter names for the four consecutive uint32_t args, making the argument order opaque at the call site.

Confidence Score: 4/5

The change introduces a new, additive C API function; existing behavior is untouched and the new code follows the locking, pin-resolution, and render-queuing patterns established by the sibling selection functions.

The implementation is structurally sound and consistent with the rest of the selection API. The only issue is the missing parameter names in the C header, where four consecutive uint32_t arguments give no hint of their order to callers reading the header alone.

include/ghostty.h — the declaration would benefit from named parameters to prevent row/column transposition at call sites.

Important Files Changed

Filename Overview
include/ghostty.h Declares ghostty_surface_set_selection_range with 4 unnamed uint32_t params; consistent with existing codebase style but ambiguous at the call site.
src/Surface.zig Implements setSelectionRange following the exact pattern of selectCursorCell and clearSelection: lock, pin coords, call setSelection, mark dirty, queue render.
src/apprt/embedded.zig C API wrapper delegates to core_surface.setSelectionRange with consistent error handling pattern matching selectCursorCell and clearSelection.

Sequence Diagram

sequenceDiagram
    participant C as C Caller (cmux)
    participant E as embedded.zig (CAPI)
    participant S as Surface.zig
    participant T as Terminal / PageList
    participant R as Renderer Thread

    C->>E: ghostty_surface_set_selection_range(surface, row_start, col_start, row_end, col_end, is_rectangular)
    E->>S: core_surface.setSelectionRange(...)
    S->>S: renderer_state.mutex.lock()
    S->>T: screens.active (get active screen)
    S->>T: "pages.pin(.screen {x=col_start, y=row_start})"
    T-->>S: start_pin (or null → return false)
    S->>T: "pages.pin(.screen {x=col_end, y=row_end})"
    T-->>S: end_pin (or null → return false)
    S->>S: setSelection(Selection.init(start_pin, end_pin, is_rectangular))
    S->>T: screen.select(sel)
    S->>T: "screen.dirty.selection = true"
    S->>R: queueRender() (wakeup notify)
    S->>S: renderer_state.mutex.unlock()
    S-->>E: true
    E-->>C: true
Loading

Reviews (1): Last reviewed commit: "ghostty: add ghostty_surface_set_selecti..." | Re-trigger Greptile

Comment thread include/ghostty.h Outdated
Comment on lines +1181 to +1186
GHOSTTY_API bool ghostty_surface_set_selection_range(ghostty_surface_t,
uint32_t,
uint32_t,
uint32_t,
uint32_t,
bool);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The declaration omits parameter names for all four uint32_t arguments. With four consecutive same-typed params the argument order (row_start, col_start, row_end, col_end) is invisible at the call site, and callers must read the implementation to determine it. Other single-uint32_t functions in this header are unambiguous, but this one is not. Adding names matches how Zig defines it and prevents accidental row/column transposition.

Suggested change
GHOSTTY_API bool ghostty_surface_set_selection_range(ghostty_surface_t,
uint32_t,
uint32_t,
uint32_t,
uint32_t,
bool);
GHOSTTY_API bool ghostty_surface_set_selection_range(ghostty_surface_t,
uint32_t row_start,
uint32_t col_start,
uint32_t row_end,
uint32_t col_end,
bool is_rectangular);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

…stream/main)

Includes resolve of conflict: both selectCursorLine (upstream)
and setSelectionRange (Slice 0) are kept as independent cmux
additions to the fork.
@tayiorbeii tayiorbeii force-pushed the copy-mode-cursor-ux-slice-0 branch from e173506 to c1d033e Compare May 25, 2026 06:40

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Surface.zig`:
- Around line 2136-2140: The selection endpoints must be normalized before
constructing the terminal.Selection to guarantee start ≤ end; modify the block
that obtains start_pin and end_pin (from screen.pages.pin) to compare their
screen coordinates (e.g., pin.screen.x and pin.screen.y) and swap them when
necessary (or construct normalized start_pin/ end_pin variables) so that the
values passed to self.setSelection(terminal.Selection.init(start_pin, end_pin,
is_rectangular)) always satisfy the start ≤ end contract; ensure
screen.dirty.selection is still set after calling setSelection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76273134-c968-47c7-bf21-be18a57a01ab

📥 Commits

Reviewing files that changed from the base of the PR and between e173506 and c1d033e.

📒 Files selected for processing (3)
  • include/ghostty.h
  • src/Surface.zig
  • src/apprt/embedded.zig

Comment thread src/Surface.zig
Comment on lines +2136 to +2140
const start_pin = screen.pages.pin(.{ .screen = .{ .x = col_start_cell, .y = row_start } }) orelse return false;
const end_pin = screen.pages.pin(.{ .screen = .{ .x = col_end_cell, .y = row_end } }) orelse return false;

try self.setSelection(terminal.Selection.init(start_pin, end_pin, is_rectangular));
screen.dirty.selection = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize selection endpoints before constructing the selection.

Line 2139 uses caller-provided start/end as-is. Reversed coordinates can violate the start≤end selection contract and produce incorrect selection behavior. Normalize pins first.

Proposed fix
     const start_pin = screen.pages.pin(.{ .screen = .{ .x = col_start_cell, .y = row_start } }) orelse return false;
     const end_pin = screen.pages.pin(.{ .screen = .{ .x = col_end_cell, .y = row_end } }) orelse return false;
 
-    try self.setSelection(terminal.Selection.init(start_pin, end_pin, is_rectangular));
+    var ordered_start = start_pin;
+    var ordered_end = end_pin;
+    if (ordered_end.before(ordered_start)) {
+        std.mem.swap(terminal.Pin, &ordered_start, &ordered_end);
+    }
+
+    try self.setSelection(terminal.Selection.init(ordered_start, ordered_end, is_rectangular));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const start_pin = screen.pages.pin(.{ .screen = .{ .x = col_start_cell, .y = row_start } }) orelse return false;
const end_pin = screen.pages.pin(.{ .screen = .{ .x = col_end_cell, .y = row_end } }) orelse return false;
try self.setSelection(terminal.Selection.init(start_pin, end_pin, is_rectangular));
screen.dirty.selection = true;
const start_pin = screen.pages.pin(.{ .screen = .{ .x = col_start_cell, .y = row_start } }) orelse return false;
const end_pin = screen.pages.pin(.{ .screen = .{ .x = col_end_cell, .y = row_end } }) orelse return false;
var ordered_start = start_pin;
var ordered_end = end_pin;
if (ordered_end.before(ordered_start)) {
std.mem.swap(terminal.Pin, &ordered_start, &ordered_end);
}
try self.setSelection(terminal.Selection.init(ordered_start, ordered_end, is_rectangular));
screen.dirty.selection = true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Surface.zig` around lines 2136 - 2140, The selection endpoints must be
normalized before constructing the terminal.Selection to guarantee start ≤ end;
modify the block that obtains start_pin and end_pin (from screen.pages.pin) to
compare their screen coordinates (e.g., pin.screen.x and pin.screen.y) and swap
them when necessary (or construct normalized start_pin/ end_pin variables) so
that the values passed to self.setSelection(terminal.Selection.init(start_pin,
end_pin, is_rectangular)) always satisfy the start ≤ end contract; ensure
screen.dirty.selection is still set after calling setSelection.

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