Skip to content

fix(pty): link bare filenames#2774

Open
janburzinski wants to merge 2 commits into
mainfrom
jan/eng-1602-referenced-file-names-in-chat-are-not-clickable
Open

fix(pty): link bare filenames#2774
janburzinski wants to merge 2 commits into
mainfrom
jan/eng-1602-referenced-file-names-in-chat-are-not-clickable

Conversation

@janburzinski

Copy link
Copy Markdown
Collaborator

Description

  • links bare filenames in pty output, not just paths with folders

Screenshot/Recording (if applicable)

https://cap.link/6y6qk1r7k2f56r8

Checklist
  • I kept this PR small and focused
  • I ran a self-review before opening this PR
  • I ran the relevant local checks or explained why not
  • I updated docs when behavior or setup changed
  • I added or updated tests when behavior changed, or explained why not
  • I only added comments where the logic is not obvious
  • I used Conventional Commits for commit
    messages and, when possible, the PR title

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR extends terminal file-link detection to cover bare filenames (e.g. Notes.md) in addition to path-prefixed references, and wires up a workspace file-index lookup as a fallback when a bare filename cannot be resolved directly to a path on disk.

  • Regex + denylist (file-link-detection.ts): the FILE_PATH_PATTERN regex gains an alternate bare-filename branch requiring a stem of ≥ 2 characters (filtering e.g, i.e), and a static COMMON_NON_FILE_BARE_NAMES set blocks known framework names like Node.js and Vue.js from generating phantom links.
  • Exact-name index lookup (workspace-file-index-store.ts, service, controller): a new findFilesByName SQL query performs a case-sensitive exact filename = match ordered by path depth (slash count), returning the shallowest hit first — replacing the previous relevance-ranked search that could silently exclude exact matches beyond its limit.
  • Modifier-key repaint fix (file-link-provider.ts): ActivationModifierTracker now accepts a refresh callback in hover() and fires it whenever the pressed state changes without a pointer event, fixing underline show/hide when the modifier key is toggled while the cursor is stationary.

Confidence Score: 5/5

Safe to merge — the change is well-scoped, fully tested, and the fallback lookup is correctly gated behind isBareFilename so existing path-based link handling is unaffected.

Every new code path has a matching unit or integration test. The exact-name SQL lookup replaces a workaround that had a known correctness gap. The bare-filename branch is gated at both the detection layer (regex + denylist) and the resolution layer (isBareFilename guard), so false positives that reach the editor produce a toast rather than silently misbehaving. The modifier-repaint fix is a pure UI improvement with no correctness impact.

No files require special attention.

Important Files Changed

Filename Overview
apps/emdash-desktop/src/renderer/lib/pty/file-link-detection.ts Extends FILE_PATH_PATTERN to match bare filenames (stem ≥ 2 chars), adds a small denylist for common framework names, and changes the early-exit guard from indexOf('/') to indexOf('.')
apps/emdash-desktop/src/renderer/features/tasks/stores/open-file-in-file-editor.ts Adds a bare-filename fallback path via findWorkspaceFileByName when the directly-resolved path does not exist, using the shallowest index hit
apps/emdash-desktop/src/main/core/search/workspace-file-index-store.ts Adds findFilesByName: exact-match SQL query ordered by path depth (slash count); no LIMIT clause, so all matches are returned even though callers only use the first result
apps/emdash-desktop/src/renderer/lib/pty/file-link-provider.ts Adds hoveredRefresh callback and changed detection so toggling the modifier key while the pointer is stationary forces a terminal repaint to show/hide the underline
apps/emdash-desktop/src/renderer/tests/file-link-provider.test.ts Comprehensive test additions covering bare filename detection, abbreviation exemptions, denylist bypass, modifier-key repaint, and URL non-linkification
apps/emdash-desktop/src/main/core/search/controller.ts Wires new findWorkspaceFilesByName RPC endpoint through to searchService.findFilesByName
apps/emdash-desktop/src/main/core/search/search-service.ts Thin pass-through from SearchService to WorkspaceFileIndexService.findFilesByName
apps/emdash-desktop/src/main/core/search/workspace-file-index-service.ts Thin pass-through from WorkspaceFileIndexService to the store's findFilesByName
apps/emdash-desktop/src/shared/core/search.ts Adds WorkspaceFileNameQuery interface for the new RPC endpoint
apps/emdash-desktop/src/main/core/search/workspace-file-index-service.test.ts Extends service unit tests with findFilesByName coverage using the FakeStore fixture

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant PTY as PTY Terminal
    participant FLD as file-link-detection
    participant FLP as FileLinkProvider
    participant OFE as openFileInTaskEditor
    participant RPC as RPC (IPC)
    participant Store as WorkspaceFileIndexStore

    PTY->>FLD: findFileLinks(buffer, lineNumber)
    Note over FLD: Regex matches bare filenames<br/>+ path-prefixed references.<br/>Denylist filters Node.js etc.
    FLD-->>FLP: FileLinkMatch[]
    FLP->>OFE: open(filePath)
    OFE->>RPC: workspace.files.fileExists(resolvedPath)
    RPC-->>OFE: "exists = false"
    alt isBareFilename(filePath)
        OFE->>RPC: "search.findWorkspaceFilesByName({workspaceId, filename})"
        RPC->>Store: findFilesByName(workspaceId, filename)
        Note over Store: Exact match, ordered by<br/>path depth (shallowest first)
        Store-->>RPC: FileHit[]
        RPC-->>OFE: WorkspaceFileHit[]
        OFE->>OFE: resolveWorkspacePath(workspace.path, hits[0].path)
        OFE->>FLP: open file at resolved path
    else path-prefixed or not found
        OFE->>PTY: toast.error("File not found")
    end
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"}}}%%
sequenceDiagram
    participant PTY as PTY Terminal
    participant FLD as file-link-detection
    participant FLP as FileLinkProvider
    participant OFE as openFileInTaskEditor
    participant RPC as RPC (IPC)
    participant Store as WorkspaceFileIndexStore

    PTY->>FLD: findFileLinks(buffer, lineNumber)
    Note over FLD: Regex matches bare filenames<br/>+ path-prefixed references.<br/>Denylist filters Node.js etc.
    FLD-->>FLP: FileLinkMatch[]
    FLP->>OFE: open(filePath)
    OFE->>RPC: workspace.files.fileExists(resolvedPath)
    RPC-->>OFE: "exists = false"
    alt isBareFilename(filePath)
        OFE->>RPC: "search.findWorkspaceFilesByName({workspaceId, filename})"
        RPC->>Store: findFilesByName(workspaceId, filename)
        Note over Store: Exact match, ordered by<br/>path depth (shallowest first)
        Store-->>RPC: FileHit[]
        RPC-->>OFE: WorkspaceFileHit[]
        OFE->>OFE: resolveWorkspacePath(workspace.path, hits[0].path)
        OFE->>FLP: open file at resolved path
    else path-prefixed or not found
        OFE->>PTY: toast.error("File not found")
    end
Loading

Reviews (2): Last reviewed commit: "fix(tasks): improve file link resolution" | Re-trigger Greptile

Comment thread apps/emdash-desktop/src/renderer/lib/pty/file-link-detection.ts
@janburzinski

Copy link
Copy Markdown
Collaborator Author

@greptileai

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