fix(pty): link bare filenames#2774
Conversation
Greptile SummaryThis PR extends terminal file-link detection to cover bare filenames (e.g.
Confidence Score: 5/5Safe 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.
|
| 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
%%{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
Reviews (2): Last reviewed commit: "fix(tasks): improve file link resolution" | Re-trigger Greptile
Description
Screenshot/Recording (if applicable)
https://cap.link/6y6qk1r7k2f56r8
Checklist
messages and, when possible, the PR title