diff --git a/src/ClaudeDev.ts b/src/ClaudeDev.ts
index 90fc385..3c1bbef 100644
--- a/src/ClaudeDev.ts
+++ b/src/ClaudeDev.ts
@@ -1138,7 +1138,7 @@ export class ClaudeDev {
const message = JSON.stringify({
tool: "readFile",
path: this.getReadablePath(relPath),
- content,
+ content: absolutePath,
} as ClaudeSayTool)
if (this.alwaysAllowReadOnly) {
await this.say("tool", message)
diff --git a/src/providers/ClaudeDevProvider.ts b/src/providers/ClaudeDevProvider.ts
index 8d1d513..7414806 100644
--- a/src/providers/ClaudeDevProvider.ts
+++ b/src/providers/ClaudeDevProvider.ts
@@ -10,7 +10,7 @@ import fs from "fs/promises"
import { HistoryItem } from "../shared/HistoryItem"
import axios from "axios"
import { getTheme } from "../utils/getTheme"
-import { openImage } from "../utils/open-image"
+import { openFile, openImage } from "../utils/open-file"
/*
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -402,6 +402,9 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
case "openImage":
openImage(message.text!)
break
+ case "openFile":
+ openFile(message.text!)
+ break
// Add more switch case statements here as more webview message commands
// are created within the webview context (i.e. inside media/main.js)
}
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index c3a35f0..c7bc8e4 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -18,6 +18,7 @@ export interface WebviewMessage {
| "resetState"
| "requestOllamaModels"
| "openImage"
+ | "openFile"
text?: string
askResponse?: ClaudeAskResponse
apiConfiguration?: ApiConfiguration
diff --git a/src/utils/open-file.ts b/src/utils/open-file.ts
new file mode 100644
index 0000000..ce3f590
--- /dev/null
+++ b/src/utils/open-file.ts
@@ -0,0 +1,50 @@
+import * as path from "path"
+import * as os from "os"
+import * as vscode from "vscode"
+
+export async function openImage(dataUri: string) {
+ const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
+ if (!matches) {
+ vscode.window.showErrorMessage("Invalid data URI format")
+ return
+ }
+ const [, format, base64Data] = matches
+ const imageBuffer = Buffer.from(base64Data, "base64")
+ const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
+ try {
+ await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
+ await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
+ } catch (error) {
+ vscode.window.showErrorMessage(`Error opening image: ${error}`)
+ }
+}
+
+export async function openFile(absolutePath: string) {
+ try {
+ const uri = vscode.Uri.file(absolutePath)
+
+ // Check if the document is already open in a tab group that's not in the active editor's column. If it is, then close it (if not dirty) so that we don't duplicate tabs
+ try {
+ for (const group of vscode.window.tabGroups.all) {
+ const existingTab = group.tabs.find(
+ (tab) => tab.input instanceof vscode.TabInputText && tab.input.uri.fsPath === uri.fsPath
+ )
+ if (existingTab) {
+ const activeColumn = vscode.window.activeTextEditor?.viewColumn
+ const tabColumn = vscode.window.tabGroups.all.find((group) =>
+ group.tabs.includes(existingTab)
+ )?.viewColumn
+ if (activeColumn && activeColumn !== tabColumn && !existingTab.isDirty) {
+ await vscode.window.tabGroups.close(existingTab)
+ }
+ break
+ }
+ }
+ } catch {} // not essential, sometimes tab operations fail
+
+ const document = await vscode.workspace.openTextDocument(uri)
+ await vscode.window.showTextDocument(document, { preview: false })
+ } catch (error) {
+ vscode.window.showErrorMessage(`Could not open file!`)
+ }
+}
diff --git a/src/utils/open-image.ts b/src/utils/open-image.ts
deleted file mode 100644
index 42e09ee..0000000
--- a/src/utils/open-image.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import * as path from "path"
-import * as os from "os"
-import * as vscode from "vscode"
-
-export async function openImage(dataUri: string) {
- const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
- if (!matches) {
- vscode.window.showErrorMessage("Invalid data URI format")
- return
- }
- const [, format, base64Data] = matches
- const imageBuffer = Buffer.from(base64Data, "base64")
- const tempFilePath = path.join(os.tmpdir(), `temp_image_${Date.now()}.${format}`)
- try {
- await vscode.workspace.fs.writeFile(vscode.Uri.file(tempFilePath), imageBuffer)
- await vscode.commands.executeCommand("vscode.open", vscode.Uri.file(tempFilePath))
- } catch (error) {
- vscode.window.showErrorMessage(`Error opening image: ${error}`)
- }
-}
diff --git a/webview-ui/src/components/ChatRow.tsx b/webview-ui/src/components/ChatRow.tsx
index 256e95c..8ca4560 100644
--- a/webview-ui/src/components/ChatRow.tsx
+++ b/webview-ui/src/components/ChatRow.tsx
@@ -4,9 +4,10 @@ import React, { memo, useMemo } from "react"
import ReactMarkdown from "react-markdown"
import { ClaudeMessage, ClaudeSayTool } from "../../../src/shared/ExtensionMessage"
import { COMMAND_OUTPUT_STRING } from "../../../src/shared/combineCommandSequences"
-import CodeAccordian from "./CodeAccordian"
+import CodeAccordian, { removeLeadingNonAlphanumeric } from "./CodeAccordian"
import CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
import Thumbnails from "./Thumbnails"
+import { vscode } from "../utils/vscode"
interface ChatRowProps {
message: ClaudeMessage
@@ -190,12 +191,54 @@ const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifiedMessa
{message.type === "ask" ? "Claude wants to read this file:" : "Claude read this file:"}
-