Skip to content

Support indented hard-wrapped links - #124

Merged
lawrencecchen merged 36 commits into
mainfrom
task-hard-newline-link-continuations
Jul 22, 2026
Merged

Support indented hard-wrapped links#124
lawrencecchen merged 36 commits into
mainfrom
task-hard-newline-link-continuations

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 18, 2026

Copy link
Copy Markdown

Recognize built-in URL and path links split by a real CRLF/newline plus indentation after common link punctuation.

The same normalized byte-to-cell map drives hit testing, opened text, and Cmd-hover highlighting. Custom matchers retain literal newline behavior.

Regression coverage uses the exact two-line .app path shape, verifies clicks from both rows, excludes indentation and sentence punctuation from hover/opening, and preserves semantic soft-wrap boundaries.

Verification:

  • zig build test -Dtest-filter=hard
  • zig build test -Dtest-filter="url regex"
  • focused existing URL/path boundary tests
  • full suite: 3010 passed, 17 skipped, 2 unrelated pre-existing PageList viewport-cache tests failed when rerun alone

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


Summary by cubic

Adds support for URLs and file paths that are hard-wrapped with a real newline + indentation so they resolve, highlight, and copy as one link. Also adds a per-surface, tokened render presentation callback that fires only after the exact frame is shown across Metal and OpenGL.

  • New Features

    • Canonical regex link resolution with newline+indent continuation normalization and an optional match-only delimiter for paths.
    • New matcher flags: hard_wrap_continuations and hard_wrap_match_delimiter; enabled for built-in URL and path; custom matchers default to literal-newline behavior.
    • Tokened frame presentation via ghostty_render_presented_cb; callback runs after platform presentation on both Metal and OpenGL.
  • Bug Fixes

    • Hover underline now follows click priority with OSC 8 taking precedence; overlapping lower-priority matchers no longer widen highlights.
    • Wrapped link resolution uses the exact terminal cells under the pointer; non-link cells inside wrapped regions are ignored; trailing spaces in wrapped paths are preserved for matching and trimmed on copy.
    • Hardened tokened completion: one-shot registration, gated delivery after renderer cleanup, rejects stale/out-of-order frames, safe teardown for stalled backends, and preserves OpenGL presentation errors.

Written for commit b211341. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Links can now span hard-wrapped lines, including indented continuations.
    • Default URL and path detection now follows hard-wrap rules for consistent delimiter behavior.
    • Wrapped links behave consistently for hovering, highlighting, selection, activation, and copy.
    • Added embedder “render presented” acknowledgement support with caller-provided tokens.
  • Bug Fixes
    • Link opening and clipboard copy now use the same canonical target text as on-screen selection, including across wrapped boundaries.
    • Improved overlap arbitration and OSC 8 ownership handling during link resolution.
  • Tests
    • Expanded coverage for hard-wrap boundaries, matcher priority, and canonical target behavior.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces shared hard-wrap-aware link preparation and canonical target resolution. Renderer integration separates preparation from resolution, while embedded rendering gains token-associated presentation callbacks propagated through platform frame lifecycles.

Changes

Prepared link resolution

Layer / File(s) Summary
Link configuration and hard-wrap contracts
src/input/Link.zig, src/config/Config.zig, src/config/url.zig, src/link_wrap.zig
Adds hard-wrap options, default matcher behavior, path regex updates, and normalization contracts.
Candidate preparation and regex resolution
src/link.zig
Builds bounded candidate domains, expands hard-wrapped selections, maps candidates, and resolves matches under preparation and search budgets.
Renderer and Surface integration
src/renderer/link.zig, src/renderer/generic.zig, src/Surface.zig
Uses prepared candidates for highlighting and canonical targets for hover, opening, copying, OSC 8 handling, and link tests.
Embedded presentation callback API
include/ghostty.h, src/apprt/embedded.zig, src/renderer.zig, src/apprt.zig, build.zig
Adds callback registration and tokenized forced-render entrypoints, with test-runtime and Metal test-linking updates.
Renderer presentation propagation
src/renderer/generic.zig, src/renderer/Thread.zig, src/renderer/Metal.zig, src/renderer/OpenGL.zig, src/renderer/metal/*, src/renderer/opengl/*
Carries presentation tokens through frame creation, completion lifetimes, swap-chain ownership, platform presentation, and completion callbacks.

Estimated code review effort: 5 (Critical) | ~100 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Embedder
  participant Surface
  participant RendererThread
  participant Frame
  participant PlatformLayer
  Embedder->>Surface: renderNowWithToken(token)
  Surface->>RendererThread: renderNowWithPresentation
  RendererThread->>Frame: drawFrameWithPresentation
  Frame->>PlatformLayer: present surface with callback and token
  PlatformLayer-->>Embedder: render_presented_cb(userdata, token)
Loading

Suggested reviewers: mitchellh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main link-handling change: supporting links split across indented hard wraps.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-hard-newline-link-continuations

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.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds support for recognizing terminal links (URLs and file paths) that are split across physical lines by prose hard-wrapping — a real CRLF followed by leading indentation after common link-break punctuation. A new link_wrap.zig module normalizes such boundaries into a contiguous string while preserving a byte-to-terminal-cell map, so hit-testing, hover highlighting, and link opening all operate on the same clean coordinate space.

  • link_wrap.normalize strips CRLF+indent sequences that follow break punctuation (-, /, ., etc.) and precede a valid link byte, rewriting both the string and its cell map in one pass.
  • expandHardWrappedLinkSelection extends the regex candidate window by one adjacent logical line above and below the clicked row before running the normalizer.
  • renderer/link.zig lazily computes a single normalized viewport string shared across all hard_wrap_continuations links per frame.

Confidence Score: 4/5

Safe to merge for the common single-wrap case; the one-level expansion limit and sentinel map-entry are edge cases with no data-loss risk.

The core normalization logic in link_wrap.zig is well-tested and handles CRLF, null-byte cell padding, and break-punctuation guards correctly. The main gap is that expandHardWrappedLinkSelection only reaches one adjacent row in each direction, so a path split across three terminal lines is only reliably clickable from its middle segment.

src/Surface.zig — expandHardWrappedLinkSelection and the four-map caching block in linkAtScreenPin deserve a second look for multi-wrap correctness.

Important Files Changed

Filename Overview
src/link_wrap.zig New file implementing hard-wrap normalization. Logic is sound: CRLF stripping, null-byte trimming, indentation skip, and break-punctuation guard all work correctly. The terminate_joined sentinel map-entry concern is minor.
src/Surface.zig Adds hard-wrap candidate expansion and normalized linkText extraction. Main limitation: expandHardWrappedLinkSelection only reaches one adjacent row, so 3+-line wrapped links are not consistently clickable.
src/renderer/link.zig Lazily normalizes the viewport string once for all hard_wrap_continuations links; correctly defers cleanup. Hover and cell-map coordinate lookups use the normalized map transparently.
src/input/Link.zig Adds hard_wrap_continuations field with default false, propagated through clone and equal correctly.
src/config/Config.zig Enables hard_wrap_continuations on both built-in URL and path matchers; test updated to verify the new flag.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Mouse click / hover on cell] --> B[linkAtScreenPin]
    B --> C{hard_wrap_continuations?}
    C -- No --> D[selectLine to linkCandidateMap normalize=false]
    C -- Yes --> E[selectLine to expandHardWrappedLinkSelection plus-minus 1 adjacent line]
    E --> F[linkCandidateMap normalize=true terminate_joined=true]
    F --> G[link_wrap.normalize strip CRLF+indent rewrite cell map]
    G --> H[searchIterator regex match on normalized string]
    D --> H
    H --> I{match contains mouse pin?}
    I -- No --> J[try next link]
    I -- Yes --> K[Return Link with selection and hard_wrap flag]
    K --> L{open or preview?}
    L --> M[linkText normalize=true terminate_joined=false]
    L --> N[link.selection used for cell highlighting]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[Mouse click / hover on cell] --> B[linkAtScreenPin]
    B --> C{hard_wrap_continuations?}
    C -- No --> D[selectLine to linkCandidateMap normalize=false]
    C -- Yes --> E[selectLine to expandHardWrappedLinkSelection plus-minus 1 adjacent line]
    E --> F[linkCandidateMap normalize=true terminate_joined=true]
    F --> G[link_wrap.normalize strip CRLF+indent rewrite cell map]
    G --> H[searchIterator regex match on normalized string]
    D --> H
    H --> I{match contains mouse pin?}
    I -- No --> J[try next link]
    I -- Yes --> K[Return Link with selection and hard_wrap flag]
    K --> L{open or preview?}
    L --> M[linkText normalize=true terminate_joined=false]
    L --> N[link.selection used for cell highlighting]
Loading

Reviews (1): Last reviewed commit: "fix: join indented hard-wrapped links" | Re-trigger Greptile

Comment thread src/Surface.zig Outdated
Comment thread src/link_wrap.zig

@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/apprt/embedded.zig`:
- Around line 513-518: Update newSurfaceOptions to copy render_presented_cb and
render_presented_userdata from ghostty_surface_inherited_config into the
inherited surface options, alongside the existing IO and renderer callbacks.
Preserve the callback values when creating child surfaces so tokened renders can
be acknowledged.
🪄 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: 17f12e71-f8fd-4c9d-950e-0e44b8c85934

📥 Commits

Reviewing files that changed from the base of the PR and between eb9004a and 8eb1857.

📒 Files selected for processing (13)
  • include/ghostty.h
  • src/Surface.zig
  • src/apprt/embedded.zig
  • src/config/url.zig
  • src/renderer.zig
  • src/renderer/Metal.zig
  • src/renderer/OpenGL.zig
  • src/renderer/Thread.zig
  • src/renderer/generic.zig
  • src/renderer/link.zig
  • src/renderer/metal/Frame.zig
  • src/renderer/metal/IOSurfaceLayer.zig
  • src/renderer/opengl/Frame.zig
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/link.zig

Comment thread src/apprt/embedded.zig Outdated
@lawrencecchen
lawrencecchen merged commit d8994a0 into main Jul 22, 2026
151 checks passed
@lawrencecchen
lawrencecchen deleted the task-hard-newline-link-continuations branch July 22, 2026 02:28
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.

2 participants