Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kilo-vscode/src/KiloProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
dir: () => this.getWorkspaceDirectory(this.currentSession?.id),
diff: this.diffVirtualProvider,
storage: this.extensionContext?.globalStorageUri,
post: (msg) => this.postMessage(msg),
})
}

Expand Down
68 changes: 55 additions & 13 deletions packages/kilo-vscode/src/kilo-provider/editor-actions.ts
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 = {
Expand Down Expand Up @@ -73,6 +74,7 @@ export function handleEditorAction(
dir: () => string
diff?: DiffVirtualProvider
storage?: vscode.Uri
post?: (msg: unknown) => void
},
): boolean {
if (message.type === "openFile") {
Expand All @@ -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
Comment thread
markijbema marked this conversation as resolved.
Resolved
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
Expand All @@ -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)
Comment thread
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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 — [id].tsx, [...slug].tsx — contain [, ] which are glob metacharacters. Passing them unescaped in **/${name} will produce an invalid glob and findFiles will return zero matches, falling through to the "File not found" warning even when the file exists.

Consider escaping metacharacters before constructing the pattern:

const escaped = name.replace(/[\[\]{}?*!()]/g, "\\$&")
vscode.workspace.findFiles(`**/${escaped}`, "**/node_modules/**", 5)

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

(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) => {
Comment thread
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(
Expand All @@ -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),
)
}
22 changes: 22 additions & 0 deletions packages/kilo-vscode/src/kilo-provider/file-links.ts
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)
Comment thread
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))
}
30 changes: 30 additions & 0 deletions packages/kilo-vscode/webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,35 @@ export const DataBridge: Component<{ children: any }> = (props) => {
vscode.postMessage({ type: "openContent", content, language })
}

// File existence validation for code span candidates
const pending = new Map<string, (existing: string[]) => void>()
const counter = { n: 0 }
const validateFiles = (paths: string[]): Promise<string[]> => {
const id = `vf-${++counter.n}`
return new Promise((resolve) => {
pending.set(id, resolve)
vscode.postMessage({ type: "validateFiles", id, paths })
setTimeout(() => {
if (pending.has(id)) {
pending.delete(id)
resolve([])
}
}, 3000)
})
}
const handler = (event: MessageEvent) => {
const msg = event.data
if (msg?.type === "validateFilesResult" && msg.id) {
const cb = pending.get(msg.id)
if (cb) {
pending.delete(msg.id)
cb(msg.existing ?? [])
}
}
}
onMount(() => window.addEventListener("message", handler))
onCleanup(() => window.removeEventListener("message", handler))

const directory = () => {
const dir = server.workspaceDirectory()
if (!dir) return ""
Expand All @@ -167,6 +196,7 @@ export const DataBridge: Component<{ children: any }> = (props) => {
onOpenDiff={openDiff}
onOpenUrl={openUrl}
onOpenContent={openContent}
onValidateFiles={validateFiles}
>
{props.children}
</DataProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,12 @@ export interface RemoteStatusMessage {
connected: boolean
}

export interface ValidateFilesResultMessage {
type: "validateFilesResult"
id: string
existing: string[]
}

export type ExtensionMessage =
| ReadyMessage
| FontSizeChangedMessage
Expand Down Expand Up @@ -1108,3 +1114,4 @@ export type ExtensionMessage =
| ExtensionDataReadyMessage
| TelemetryStateMessage
| RemoteStatusMessage
| ValidateFilesResultMessage
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ export interface OpenContentRequest {
language?: string
}

export interface ValidateFilesRequest {
type: "validateFiles"
id: string
paths: string[]
}

export interface CancelLoginRequest {
type: "cancelLogin"
}
Expand Down Expand Up @@ -1107,6 +1113,7 @@ export type WebviewMessage =
| OpenAgentManagerRequest
| OpenAdvancedWorktreeRequest
| OpenFileRequest
| ValidateFilesRequest
| CancelLoginRequest
| SetOrganizationRequest
| WebviewReadyRequest
Expand Down
4 changes: 4 additions & 0 deletions packages/ui/src/context/data.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export type OpenDiffFn = (diff: {
export type OpenUrlFn = (url: string) => void

export type OpenContentFn = (content: string, language?: string) => void // kilocode_change

export type ValidateFilesFn = (paths: string[]) => Promise<string[]> // kilocode_change
// kilocode_change end

export const { use: useData, provider: DataProvider } = createSimpleContext({
Expand All @@ -61,6 +63,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
onOpenDiff?: OpenDiffFn // kilocode_change
onOpenUrl?: OpenUrlFn // kilocode_change
onOpenContent?: OpenContentFn // kilocode_change
onValidateFiles?: ValidateFilesFn // kilocode_change
}) => {
return {
get store() {
Expand All @@ -75,6 +78,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
openDiff: props.onOpenDiff, // kilocode_change
openUrl: props.onOpenUrl, // kilocode_change
openContent: props.onOpenContent, // kilocode_change
validateFiles: props.onValidateFiles, // kilocode_change
}
},
})
Loading