summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-01 16:11:57 +0530
committerGitHub <[email protected]>2026-04-01 16:11:57 +0530
commita3a6cf1c075c40c87980dda181d586a1d06ea304 (patch)
tree71b3c3d7144f7c38eb73d7e173852858313fc4bf /packages/app/src
parent47a676111a3532aebed01110494742e536b7e5b4 (diff)
downloadopencode-a3a6cf1c075c40c87980dda181d586a1d06ea304.tar.gz
opencode-a3a6cf1c075c40c87980dda181d586a1d06ea304.zip
feat(comments): support file mentions (#20447)
Diffstat (limited to 'packages/app/src')
-rw-r--r--packages/app/src/components/prompt-input/build-request-parts.test.ts24
-rw-r--r--packages/app/src/components/prompt-input/build-request-parts.ts26
-rw-r--r--packages/app/src/pages/session.tsx3
-rw-r--r--packages/app/src/pages/session/file-tabs.tsx3
-rw-r--r--packages/app/src/pages/session/review-tab.tsx4
5 files changed, 60 insertions, 0 deletions
diff --git a/packages/app/src/components/prompt-input/build-request-parts.test.ts b/packages/app/src/components/prompt-input/build-request-parts.test.ts
index ce09ae921..06c377331 100644
--- a/packages/app/src/components/prompt-input/build-request-parts.test.ts
+++ b/packages/app/src/components/prompt-input/build-request-parts.test.ts
@@ -100,6 +100,30 @@ describe("buildRequestParts", () => {
expect(synthetic).toHaveLength(1)
})
+ test("adds file parts for @mentions inside comment text", () => {
+ const result = buildRequestParts({
+ prompt: [{ type: "text", content: "look", start: 0, end: 4 }],
+ context: [
+ {
+ key: "ctx:comment-mention",
+ type: "file",
+ path: "src/review.ts",
+ comment: "Compare with @src/shared.ts and @src/review.ts.",
+ },
+ ],
+ images: [],
+ text: "look",
+ messageID: "msg_comment_mentions",
+ sessionID: "ses_comment_mentions",
+ sessionDirectory: "/repo",
+ })
+
+ const files = result.requestParts.filter((part) => part.type === "file")
+ expect(files).toHaveLength(2)
+ expect(files.some((part) => part.type === "file" && part.url === "file:///repo/src/review.ts")).toBe(true)
+ expect(files.some((part) => part.type === "file" && part.url === "file:///repo/src/shared.ts")).toBe(true)
+ })
+
test("handles Windows paths correctly (simulated on macOS)", () => {
const prompt: Prompt = [{ type: "file", path: "src\\foo.ts", content: "@src\\foo.ts", start: 0, end: 11 }]
diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts
index 4146fb484..a1076e60c 100644
--- a/packages/app/src/components/prompt-input/build-request-parts.ts
+++ b/packages/app/src/components/prompt-input/build-request-parts.ts
@@ -39,6 +39,16 @@ const absolute = (directory: string, path: string) => {
const fileQuery = (selection: FileSelection | undefined) =>
selection ? `?start=${selection.startLine}&end=${selection.endLine}` : ""
+const mention = /(^|[\s([{"'])@(\S+)/g
+
+const parseCommentMentions = (comment: string) => {
+ return Array.from(comment.matchAll(mention)).flatMap((match) => {
+ const path = (match[2] ?? "").replace(/[.,!?;:)}\]"']+$/, "")
+ if (!path) return []
+ return [path]
+ })
+}
+
const isFileAttachment = (part: Prompt[number]): part is FileAttachmentPart => part.type === "file"
const isAgentAttachment = (part: Prompt[number]): part is AgentPart => part.type === "agent"
@@ -138,6 +148,21 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
if (!comment) return [filePart]
+ const mentions = parseCommentMentions(comment).flatMap((path) => {
+ const url = `file://${encodeFilePath(absolute(input.sessionDirectory, path))}`
+ if (used.has(url)) return []
+ used.add(url)
+ return [
+ {
+ id: Identifier.ascending("part"),
+ type: "file",
+ mime: "text/plain",
+ url,
+ filename: getFilename(path),
+ } satisfies PromptRequestPart,
+ ]
+ })
+
return [
{
id: Identifier.ascending("part"),
@@ -153,6 +178,7 @@ export function buildRequestParts(input: BuildRequestPartsInput) {
}),
} satisfies PromptRequestPart,
filePart,
+ ...mentions,
]
})
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 917de35b1..18bae6e2d 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1046,6 +1046,9 @@ export default function Page() {
onLineCommentUpdate={updateCommentInContext}
onLineCommentDelete={removeCommentFromContext}
lineCommentActions={reviewCommentActions()}
+ commentMentions={{
+ items: file.searchFilesAndDirectories,
+ }}
comments={comments.all()}
focusedComment={comments.focus()}
onFocusedCommentChange={comments.setFocus}
diff --git a/packages/app/src/pages/session/file-tabs.tsx b/packages/app/src/pages/session/file-tabs.tsx
index 8208b6c99..9430b7025 100644
--- a/packages/app/src/pages/session/file-tabs.tsx
+++ b/packages/app/src/pages/session/file-tabs.tsx
@@ -302,6 +302,9 @@ export function FileTabContent(props: { tab: string }) {
comments: fileComments,
label: language.t("ui.lineComment.submit"),
draftKey: () => path() ?? props.tab,
+ mention: {
+ items: file.searchFilesAndDirectories,
+ },
state: {
opened: () => note.openedComment,
setOpened: (id) => setNote("openedComment", id),
diff --git a/packages/app/src/pages/session/review-tab.tsx b/packages/app/src/pages/session/review-tab.tsx
index c073e6214..76b65a221 100644
--- a/packages/app/src/pages/session/review-tab.tsx
+++ b/packages/app/src/pages/session/review-tab.tsx
@@ -30,6 +30,9 @@ export interface SessionReviewTabProps {
onFocusedCommentChange?: (focus: { file: string; id: string } | null) => void
focusedFile?: string
onScrollRef?: (el: HTMLDivElement) => void
+ commentMentions?: {
+ items: (query: string) => string[] | Promise<string[]>
+ }
classes?: {
root?: string
header?: string
@@ -162,6 +165,7 @@ export function SessionReviewTab(props: SessionReviewTabProps) {
onLineCommentUpdate={props.onLineCommentUpdate}
onLineCommentDelete={props.onLineCommentDelete}
lineCommentActions={props.lineCommentActions}
+ lineCommentMention={props.commentMentions}
comments={props.comments}
focusedComment={props.focusedComment}
onFocusedCommentChange={props.onFocusedCommentChange}