-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(ui): validate inline code spans against filesystem to enable cli… #10340
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "kilo-code": minor | ||
| --- | ||
|
|
||
| Make file references in agent responses clickable by validating inline code spans against the filesystem. Code spans that match real files in the workspace become clickable links that open the file at the referenced line. Non-existent paths stay as plain code. Also adds fallback workspace search and "File not found" warning when clicking dead links. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| import * as path from "path" | ||
| import * as vscode from "vscode" | ||
| import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "./image-preview" | ||
| import { isAbsolutePath } from "./path-utils" | ||
| import type { | ||
| KiloClient, | ||
| Session, | ||
|
|
@@ -81,6 +80,7 @@ import { openConfig } from "./kilo-provider/open-config" | |
| import * as McpOAuth from "./kilo-provider/mcp-oauth" | ||
| import { retryable, backoff, MAX_RETRIES } from "./util/retry" | ||
| import { hasGit } from "./kilo-provider/git-status" | ||
| import * as fileLinks from "./kilo-provider/file-links" | ||
| // legacy-migration start | ||
| import { | ||
| checkAndShowMigrationWizard, | ||
|
|
@@ -2973,9 +2973,23 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
| if (message.content) this.handleOpenContent(message.content, message.language) | ||
| return true | ||
| } | ||
| if (message.type === "validateFiles") { | ||
| const msg = message as { id: string; paths: string[] } | ||
| this.handleValidateFiles(msg.id, msg.paths) | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| /** Stat-check candidate paths and respond with which ones are real files. */ | ||
| private handleValidateFiles(id: string, paths: string[]): void { | ||
| const root = this.getWorkspaceDirectory(this.currentSession?.id) | ||
| fileLinks | ||
| .validateFiles(root, paths) | ||
| .then((existing) => this.postMessage({ type: "validateFilesResult", id, existing })) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WARNING: Missing If Add a fileLinks
.validateFiles(root, paths)
.then((existing) => this.postMessage({ type: "validateFilesResult", id, existing }))
.catch((err) => console.error("[Kilo New] handleValidateFiles failed:", err)) |
||
| .catch((err) => console.error("[Kilo New] handleValidateFiles failed:", err)) | ||
| } | ||
|
|
||
| /** | ||
| * Handle openContent request - open arbitrary text in an untitled VS Code editor tab. | ||
| */ | ||
|
|
@@ -2988,26 +3002,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper | |
|
|
||
| /** | ||
| * Handle openFile request from the webview — open a file in the VS Code editor. | ||
| * Resolves relative paths against the current session's directory (which may be | ||
| * a worktree path registered via setSessionDirectory), falling back to workspace root. | ||
| * Absolute paths (Unix `/…` or Windows `C:\…`) are used as-is. | ||
| * Delegates to file-links.ts which resolves paths, falls back to workspace search, | ||
| * and shows a warning when the file is not found. | ||
| */ | ||
| private handleOpenFile(filePath: string, line?: number, column?: number): void { | ||
| const uri = isAbsolutePath(filePath) | ||
| ? vscode.Uri.file(filePath) | ||
| : vscode.Uri.joinPath(vscode.Uri.file(this.getWorkspaceDirectory(this.currentSession?.id)), filePath) | ||
| vscode.workspace.openTextDocument(uri).then( | ||
| (doc) => { | ||
| const options: vscode.TextDocumentShowOptions = { preview: true } | ||
| if (line !== undefined && line > 0) { | ||
| const col = column !== undefined && column > 0 ? column - 1 : 0 | ||
| const pos = new vscode.Position(line - 1, col) | ||
| options.selection = new vscode.Range(pos, pos) | ||
| } | ||
| vscode.window.showTextDocument(doc, options) | ||
| }, | ||
| (err) => console.error("[Kilo New] KiloProvider: Failed to open file:", uri.fsPath, err), | ||
| ) | ||
| fileLinks.openFile(this.getWorkspaceDirectory(this.currentSession?.id), filePath, line, column) | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import * as vscode from "vscode" | ||
| import { isAbsolutePath } from "../path-utils" | ||
|
|
||
| /** | ||
| * Stat-check candidate paths and return which ones are actual files (not directories). | ||
| */ | ||
| export function validateFiles(root: string, paths: string[]): Promise<string[]> { | ||
| const resolve = (p: string) => | ||
| isAbsolutePath(p) ? vscode.Uri.file(p) : vscode.Uri.joinPath(vscode.Uri.file(root), p) | ||
| return Promise.all( | ||
| paths.map((p) => | ||
| vscode.workspace.fs.stat(resolve(p)).then( | ||
| (s) => (s.type & vscode.FileType.File ? p : null), | ||
| () => null, | ||
| ), | ||
| ), | ||
| ).then((r) => r.filter((x): x is string => x !== null)) | ||
| } | ||
|
|
||
| /** | ||
| * Open a file in the editor with optional line/column positioning. | ||
| * Falls back to a workspace-wide filename search if the exact path doesn't exist. | ||
| */ | ||
| export function openFile(root: string, filePath: string, line?: number, column?: number): void { | ||
| const uri = isAbsolutePath(filePath) | ||
| ? vscode.Uri.file(filePath) | ||
| : vscode.Uri.joinPath(vscode.Uri.file(root), filePath) | ||
| const opts: vscode.TextDocumentShowOptions = { preview: true } | ||
| if (line !== undefined && line > 0) { | ||
| const pos = new vscode.Position(line - 1, column !== undefined && column > 0 ? column - 1 : 0) | ||
| opts.selection = new vscode.Range(pos, pos) | ||
| } | ||
| const show = (target: vscode.Uri) => | ||
| vscode.workspace.openTextDocument(target).then( | ||
| (doc) => vscode.window.showTextDocument(doc, opts), | ||
| (err) => console.error("[Kilo New] openFile show failed:", err), | ||
| ) | ||
| vscode.workspace.fs.stat(uri).then( | ||
| () => show(uri), | ||
| () => { | ||
| const name = filePath.split(/[\\/]/).pop() || filePath | ||
| Promise.resolve(vscode.workspace.findFiles(`**/${name}`, "**/node_modules/**", 5)) | ||
| .then((matches) => { | ||
| if (matches.length === 1) { | ||
| show(matches[0]) | ||
| return | ||
| } | ||
| if (matches.length > 1) { | ||
| const items = matches.map((m) => ({ label: vscode.workspace.asRelativePath(m), uri: m })) | ||
| vscode.window.showQuickPick(items, { placeHolder: `Multiple matches for "${name}"` }).then((p) => { | ||
| if (p) show(p.uri) | ||
| }) | ||
| return | ||
| } | ||
| vscode.window.showWarningMessage(`File not found: ${filePath}`) | ||
| }) | ||
| .catch((err: unknown) => console.error("[Kilo New] findFiles failed:", err)) | ||
| }, | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING:
cacheis never cleared — unbounded memory growth during long sessionsThe
Map<string, boolean>accumulates one entry per unique candidate path encountered across the lifetime of this component instance. In a long session with many assistant messages referencing many distinct paths, this can grow large. Consider bounding the cache size (e.g. LRU with a cap of ~500 entries), or clearing it when the session changes.