Repository navigation
feat(vscode): add filesystem validation protocol for file links #11218
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 1 commit
ed5fc5a
81aad79
d497be6
c2076c8
8cebfb4
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 |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import * as vscode from "vscode" | ||
| import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "../image-preview" | ||
| import { isAbsolutePath } from "../path-utils" | ||
| import { validateFiles } from "./file-links" | ||
| import type { DiffVirtualFile, DiffVirtualProvider } from "../DiffVirtualProvider" | ||
|
|
||
| type EditorOpenMessage = { | ||
|
|
@@ -73,6 +74,7 @@ export function handleEditorAction( | |
| dir: () => string | ||
| diff?: DiffVirtualProvider | ||
| storage?: vscode.Uri | ||
| post?: (msg: unknown) => void | ||
| }, | ||
| ): boolean { | ||
| if (message.type === "openFile") { | ||
|
|
@@ -83,6 +85,17 @@ export function handleEditorAction( | |
| if (message.content) openContent(message.content, message.language) | ||
| return true | ||
| } | ||
| if (message.type === "validateFiles") { | ||
| const id = (message as { id?: string }).id | ||
| const paths = (message as { paths?: string[] }).paths | ||
| if (id && paths && opts.post) { | ||
| validateFiles(opts.dir(), paths).then( | ||
| (existing) => opts.post!({ type: "validateFilesResult", id, existing }), | ||
| (err) => console.error("[Kilo New] KiloProvider: validateFiles failed:", err), | ||
| ) | ||
| } | ||
| return true | ||
| } | ||
| if (message.type === "openExternal") { | ||
| openExternal(message.url) | ||
| return true | ||
|
|
@@ -105,6 +118,46 @@ function openContent(content: string, language?: string): void { | |
| ) | ||
| } | ||
|
|
||
| function show(uri: vscode.Uri, line?: number, column?: number): void { | ||
| 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) | ||
|
markijbema marked this conversation as resolved.
Resolved
|
||
| }, | ||
| (err) => console.error("[Kilo New] KiloProvider: Failed to open file:", uri.fsPath, err), | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Fallback when the exact path does not exist: search the workspace by filename. | ||
| * Opens the file directly on a single match, prompts on multiple, warns on none. | ||
| */ | ||
| function findFallback(filePath: string, line?: number, column?: number): void { | ||
| const name = filePath.split(/[\\/]/).pop() || filePath | ||
| Promise.resolve(vscode.workspace.findFiles(`**/${name}`, "**/node_modules/**", 5)).then( | ||
|
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: Glob-special characters in filenames are not escaped before building the pattern. Filenames common in Next.js / Remix projects — Consider escaping metacharacters before constructing the pattern: const escaped = name.replace(/[\[\]{}?*!()]/g, "\\$&")
vscode.workspace.findFiles(`**/${escaped}`, "**/node_modules/**", 5)Reply with |
||
| (matches) => { | ||
| if (matches.length === 1) { | ||
| show(matches[0], line, column) | ||
| 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((pick) => { | ||
|
markijbema marked this conversation as resolved.
Resolved
|
||
| if (pick) show(pick.uri, line, column) | ||
| }) | ||
| return | ||
| } | ||
| vscode.window.showWarningMessage(`File not found: ${filePath}`) | ||
| }, | ||
| (err: unknown) => console.error("[Kilo New] KiloProvider: findFiles failed:", err), | ||
| ) | ||
| } | ||
|
|
||
| function openFile(dir: string, filePath: string, line?: number, column?: number): void { | ||
| const uri = isAbsolutePath(filePath) ? vscode.Uri.file(filePath) : vscode.Uri.joinPath(vscode.Uri.file(dir), filePath) | ||
| vscode.workspace.fs.stat(uri).then( | ||
|
|
@@ -113,19 +166,8 @@ function openFile(dir: string, filePath: string, line?: number, column?: number) | |
| vscode.commands.executeCommand("revealInExplorer", uri) | ||
| return | ||
| } | ||
| 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), | ||
| ) | ||
| show(uri, line, column) | ||
| }, | ||
| (err) => console.error("[Kilo New] KiloProvider: Path does not exist:", uri.fsPath, err), | ||
| () => findFallback(filePath, line, column), | ||
| ) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import * as vscode from "vscode" | ||
| import { isAbsolutePath } from "../path-utils" | ||
|
|
||
| /** | ||
| * Stat-check candidate paths and return which ones are actual files (not directories). | ||
| * | ||
| * The webview marks every inline code span as a file-link candidate; this confirms | ||
| * which of those candidates resolve to a real file so the webview can promote them | ||
| * to clickable links and leave the rest as plain code. | ||
| */ | ||
| 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) | ||
|
markijbema marked this conversation as resolved.
Resolved
|
||
| return Promise.all( | ||
| paths.map((p) => | ||
| Promise.resolve(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)) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.