Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
21 changes: 21 additions & 0 deletions src/chat-view-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type ExtToWebviewMessage =
configModel?: string;
}
| { type: "openEditors"; files: FileAttachment[] }
| { type: "activeEditor"; file: FileAttachment | null }
| { type: "workspaceFiles"; files: FileAttachment[] }
| { type: "contextUsage"; usage: { inputTokens: number; contextLimit: number } }
| { type: "toolConfig"; paths: OpenCodePath }
Expand Down Expand Up @@ -124,6 +125,11 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
this.connection.onEvent((event) => {
this.postMessage({ type: "event", event });
});

// アクティブエディタが変わるたびに Webview に通知する
vscode.window.onDidChangeActiveTextEditor((editor) => {
this.postMessage({ type: "activeEditor", file: this.getActiveEditorFile(editor) });
});
}

private async handleWebviewMessage(message: WebviewToExtMessage): Promise<void> {
Expand Down Expand Up @@ -165,6 +171,8 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
configModel,
});
this.postMessage({ type: "toolConfig", paths });
// 初期アクティブエディタを送信する
this.postMessage({ type: "activeEditor", file: this.getActiveEditorFile(vscode.window.activeTextEditor) });
break;
}
case "sendMessage": {
Expand Down Expand Up @@ -420,6 +428,19 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
}
}

/** アクティブなテキストエディタから FileAttachment を生成する。エディタがない場合は null を返す。 */
private getActiveEditorFile(editor: vscode.TextEditor | undefined): FileAttachment | null {
if (!editor) return null;
const uri = editor.document.uri;
// 出力パネルや設定画面など、file スキーム以外は対象外
if (uri.scheme !== "file") return null;
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri;
const relativePath = workspaceFolder
? path.relative(workspaceFolder.fsPath, uri.fsPath)
: path.basename(uri.fsPath);
return { filePath: relativePath, fileName: path.basename(uri.fsPath) };
}

private postMessage(message: ExtToWebviewMessage): void {
this.view?.webview.postMessage(message);
}
Expand Down
5 changes: 5 additions & 0 deletions webview/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function App() {

// Extension Host → Webview メッセージでのみ更新される単純なステート
const [openEditors, setOpenEditors] = useState<FileAttachment[]>([]);
const [activeEditorFile, setActiveEditorFile] = useState<FileAttachment | null>(null);
const [workspaceFiles, setWorkspaceFiles] = useState<FileAttachment[]>([]);
const [todos, setTodos] = useState<Todo[]>([]);
const [childSessions, setChildSessions] = useState<Session[]>([]);
Expand Down Expand Up @@ -136,6 +137,9 @@ export function App() {
case "openEditors":
setOpenEditors(data.files);
break;
case "activeEditor":
setActiveEditorFile(data.file);
break;
case "workspaceFiles":
setWorkspaceFiles(data.files);
break;
Expand Down Expand Up @@ -487,6 +491,7 @@ export function App() {
selectedModel={prov.selectedModel}
onModelSelect={prov.handleModelSelect}
openEditors={openEditors}
activeEditorFile={activeEditorFile}
workspaceFiles={workspaceFiles}
inputTokens={msg.inputTokens}
contextLimit={prov.contextLimit}
Expand Down
44 changes: 41 additions & 3 deletions webview/__tests__/scenarios/07-file-context.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ async function setupWithFiles() {
],
});

// アクティブエディタを設定
await sendExtMessage({
type: "activeEditor",
file: { filePath: "src/main.ts", fileName: "main.ts" },
});

vi.mocked(postMessage).mockClear();
}

Expand Down Expand Up @@ -169,12 +175,12 @@ describe("ファイルコンテキスト", () => {
);
});

// Quick-add button attaches the first open editor file
it("アクティブエディタの quick-add ボタンで先頭ファイルが添付されること", async () => {
// Quick-add button attaches the active editor file
it("アクティブエディタの quick-add ボタンでファイルが添付されること", async () => {
await setupWithFiles();
const user = userEvent.setup();

// quick-add ボタン(最初の openEditor ファイル: main.ts)が表示される
// quick-add ボタン(アクティブエディタ: main.ts)が表示される
const quickAdd = screen.getByTitle("Add src/main.ts");
expect(quickAdd).toBeInTheDocument();

Expand Down Expand Up @@ -230,4 +236,36 @@ describe("ファイルコンテキスト", () => {
// スペースを入力したのでポップアップが閉じる
expect(document.querySelector("[data-testid='hash-popup']")).toBeFalsy();
});

// activeEditor message updates the quick-add button in real-time
it("activeEditor メッセージで quick-add ボタンがリアルタイムに更新されること", async () => {
await setupWithFiles();

// 初期状態: main.ts が表示されている
expect(screen.getByTitle("Add src/main.ts")).toBeInTheDocument();

// アクティブエディタを config.ts に切り替え
await sendExtMessage({
type: "activeEditor",
file: { filePath: "src/config.ts", fileName: "config.ts" },
});

// quick-add ボタンが config.ts に切り替わる
expect(screen.getByTitle("Add src/config.ts")).toBeInTheDocument();
expect(screen.queryByTitle("Add src/main.ts")).not.toBeInTheDocument();
});

// activeEditor null hides the quick-add button
it("activeEditor が null の場合 quick-add ボタンが非表示になること", async () => {
await setupWithFiles();

// 初期状態: main.ts が表示されている
expect(screen.getByTitle("Add src/main.ts")).toBeInTheDocument();

// アクティブエディタを null に
await sendExtMessage({ type: "activeEditor", file: null });

// quick-add ボタンが消える
expect(document.querySelector(".fileButton")).not.toBeInTheDocument();
});
});
4 changes: 2 additions & 2 deletions webview/components/organisms/InputArea/InputArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Props = {
selectedModel: { providerID: string; modelID: string } | null;
onModelSelect: (model: { providerID: string; modelID: string }) => void;
openEditors: FileAttachment[];
activeEditorFile: FileAttachment | null;
workspaceFiles: FileAttachment[];
inputTokens: number;
contextLimit: number;
Expand All @@ -51,6 +52,7 @@ export function InputArea({
selectedModel,
onModelSelect,
openEditors,
activeEditorFile,
workspaceFiles,
inputTokens,
contextLimit,
Expand Down Expand Up @@ -418,8 +420,6 @@ export function InputArea({
(f) => !attachedFiles.some((a) => a.filePath === f.filePath),
);

// 現在アクティブなエディタファイル (リストの先頭)
const activeEditorFile = openEditors.length > 0 ? openEditors[0] : null;
const isActiveAttached = activeEditorFile
? attachedFiles.some((f) => f.filePath === activeEditorFile.filePath)
: false;
Expand Down
1 change: 1 addition & 0 deletions webview/vscode-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type ExtToWebviewMessage =
configModel?: string;
}
| { type: "openEditors"; files: FileAttachment[] }
| { type: "activeEditor"; file: FileAttachment | null }
| { type: "workspaceFiles"; files: FileAttachment[] }
| { type: "contextUsage"; usage: { inputTokens: number; contextLimit: number } }
| { type: "toolConfig"; paths: { home: string; config: string; state: string; directory: string } }
Expand Down