Skip to content

Fix implicit-link boundaries and link cursor flicker - #650

Open
furgalep wants to merge 2 commits into
migueldeicaza:mainfrom
furgalep:fix/trim-implicit-link-padding
Open

Fix implicit-link boundaries and link cursor flicker#650
furgalep wants to merge 2 commits into
migueldeicaza:mainfrom
furgalep:fix/trim-implicit-link-padding

Conversation

@furgalep

@furgalep furgalep commented Aug 21, 2026

Copy link
Copy Markdown

Summary

  • stop including end-of-line padding spaces in implicit filesystem-path matches
  • make AppKit cursor updates preserve the pointing hand over visible Command-links instead of resetting to the I-beam
  • perform one lock-protected link lookup per cursor update and skip implicit matching when it cannot be visible
  • add focused regressions

Terminal emulators pad every row with blank cells. Treating those cells as part of a link makes the hover underline and click target extend to the right edge. Separately, SwiftTerm's unconditional I-beam in cursorUpdate(with:) competed with host link-hover handling and caused rapid cursor flicker.

The application-specific at path boundary has been removed. A host-provided link resolver/policy would be a better follow-up for cases that need filesystem- or session-specific validation, since SwiftTerm may display remote filesystems where local probing is not authoritative.

Tests

  • swift test --filter GhosttyImplicitLinkDetectionTests
  • swift test --filter MacDefaultLinkTests
  • swift test (986 tests passed)

@furgalep furgalep changed the title Exclude terminal padding from implicit links Fix implicit-link padding and link cursor flicker Aug 21, 2026
@furgalep furgalep changed the title Fix implicit-link padding and link cursor flicker Fix implicit-link boundaries and link cursor flicker Aug 21, 2026
@migueldeicaza

Copy link
Copy Markdown
Owner

The issue with the "at" is that it sounds like it might be a valid file reference, and I can imagine other strings that we could argue whether they should be part of it or not. I wonder if we do need a different way of detecting these links.

@migueldeicaza migueldeicaza left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for taking the time to send these along - they look wonderful.

I am still mulling over the regex change, and whether we should do something to make this configurable instead or extend the probing infrastructure - perhaps we should validate the existence (it would only work for local mode)


func linkCursor(at position: Position, hasCommandModifier: Bool) -> NSCursor
{
guard let match = terminal.linkMatch(at: .buffer(position), mode: .explicitAndImplicit),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

This needs to use withTerminal, as we are now multi-threaded. I am


func linkCursor(at position: Position, hasCommandModifier: Bool) -> NSCursor
{
guard let match = terminal.linkMatch(at: .buffer(position), mode: .explicitAndImplicit),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Additionally, the same call always uses .explicitAndImplicit. With the default .hoverWithModifier mode, it runs the full implicit-link regex even when Command is not pressed. When Command is pressed, updateHoverLink performs the lookup first, and linkCursor immediately performs it again. Use implicitLinkCouldBeVisible to select the mode, or reuse the result from updateHoverLink.

@furgalep
furgalep force-pushed the fix/trim-implicit-link-padding branch from 385ba70 to 1aade1e Compare August 24, 2026 06:44
@furgalep

Copy link
Copy Markdown
Author

Addressed the review feedback and force-pushed the rebased branch:

  • rebased onto current upstream/main
  • wrapped link matching in withTerminal
  • cursor updates now reuse the hover lookup, so the implicit regex runs at most once
  • .hoverWithModifier without Command exits before any implicit lookup; always modes use .explicitOnly
  • removed the application-specific at boundary heuristic

I agree that filesystem/session-specific validation belongs behind a configurable host resolver or policy rather than in the generic regex. Local probing would not be authoritative for remote/container/tmux sessions, so I left that for a separate API discussion.

Validation: focused link suites and the full swift test run pass (986 tests).

@migueldeicaza migueldeicaza left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thank you for the follow up! One small little detail.

let hasCommandModifier = commandActive || event.modifierFlags.contains(.command)
linkCursor(at: hit, hasCommandModifier: hasCommandModifier).set()
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

AppKit calls cursorUpdate(with:) when the pointer enters a cursor rectangle.

resetCursorRects() defines one rectangle for the entire view. Moving from normal text to a link therefore updates the underline but not the cursor. Pressing or releasing Command while stationary also leaves a stale cursor. Update the cursor from mouseMoved and flagsChanged, or invalidate the cursor rectangles when link state changes. The new test calls linkCursor directly, so it does not test this event-routing behavior.

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