Просмотр исходного кода

Open read files in editor instead of code accordian

Saoud Rizwan 1 год назад
Родитель
Сommit
0522a26fd9

+ 1 - 1
src/ClaudeDev.ts

@@ -1138,7 +1138,7 @@ export class ClaudeDev {
 			const message = JSON.stringify({
 			const message = JSON.stringify({
 				tool: "readFile",
 				tool: "readFile",
 				path: this.getReadablePath(relPath),
 				path: this.getReadablePath(relPath),
-				content,
+				content: absolutePath,
 			} as ClaudeSayTool)
 			} as ClaudeSayTool)
 			if (this.alwaysAllowReadOnly) {
 			if (this.alwaysAllowReadOnly) {
 				await this.say("tool", message)
 				await this.say("tool", message)

+ 4 - 1
src/providers/ClaudeDevProvider.ts

@@ -10,7 +10,7 @@ import fs from "fs/promises"
 import { HistoryItem } from "../shared/HistoryItem"
 import { HistoryItem } from "../shared/HistoryItem"
 import axios from "axios"
 import axios from "axios"
 import { getTheme } from "../utils/getTheme"
 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
 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":
 					case "openImage":
 						openImage(message.text!)
 						openImage(message.text!)
 						break
 						break
+					case "openFile":
+						openFile(message.text!)
+						break
 					// Add more switch case statements here as more webview message commands
 					// Add more switch case statements here as more webview message commands
 					// are created within the webview context (i.e. inside media/main.js)
 					// are created within the webview context (i.e. inside media/main.js)
 				}
 				}

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -18,6 +18,7 @@ export interface WebviewMessage {
 		| "resetState"
 		| "resetState"
 		| "requestOllamaModels"
 		| "requestOllamaModels"
 		| "openImage"
 		| "openImage"
+		| "openFile"
 	text?: string
 	text?: string
 	askResponse?: ClaudeAskResponse
 	askResponse?: ClaudeAskResponse
 	apiConfiguration?: ApiConfiguration
 	apiConfiguration?: ApiConfiguration

+ 50 - 0
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!`)
+	}
+}

+ 0 - 20
src/utils/open-image.ts

@@ -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}`)
-	}
-}

+ 46 - 3
webview-ui/src/components/ChatRow.tsx

@@ -4,9 +4,10 @@ import React, { memo, useMemo } from "react"
 import ReactMarkdown from "react-markdown"
 import ReactMarkdown from "react-markdown"
 import { ClaudeMessage, ClaudeSayTool } from "../../../src/shared/ExtensionMessage"
 import { ClaudeMessage, ClaudeSayTool } from "../../../src/shared/ExtensionMessage"
 import { COMMAND_OUTPUT_STRING } from "../../../src/shared/combineCommandSequences"
 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 CodeBlock, { CODE_BLOCK_BG_COLOR } from "./CodeBlock"
 import Thumbnails from "./Thumbnails"
 import Thumbnails from "./Thumbnails"
+import { vscode } from "../utils/vscode"
 
 
 interface ChatRowProps {
 interface ChatRowProps {
 	message: ClaudeMessage
 	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:"}
 								{message.type === "ask" ? "Claude wants to read this file:" : "Claude read this file:"}
 							</span>
 							</span>
 						</div>
 						</div>
-						<CodeAccordian
+						{/* <CodeAccordian
 							code={tool.content!}
 							code={tool.content!}
 							path={tool.path!}
 							path={tool.path!}
 							isExpanded={isExpanded}
 							isExpanded={isExpanded}
 							onToggleExpand={onToggleExpand}
 							onToggleExpand={onToggleExpand}
-						/>
+						/> */}
+						<div
+							style={{
+								borderRadius: 3,
+								backgroundColor: CODE_BLOCK_BG_COLOR,
+								overflow: "hidden",
+								border: "1px solid var(--vscode-editorGroup-border)",
+							}}>
+							<div
+								style={{
+									color: "var(--vscode-descriptionForeground)",
+									display: "flex",
+									justifyContent: "space-between",
+									alignItems: "center",
+									padding: "6px 10px",
+									cursor: "pointer",
+									userSelect: "none",
+									WebkitUserSelect: "none",
+									MozUserSelect: "none",
+									msUserSelect: "none",
+								}}
+								onClick={() => {
+									vscode.postMessage({ type: "openFile", text: tool.content })
+								}}>
+								<div style={{ display: "flex", alignItems: "center" }}>
+									<span
+										style={{
+											whiteSpace: "nowrap",
+											overflow: "hidden",
+											textOverflow: "ellipsis",
+											marginRight: "8px",
+											fontSize: "11px",
+											direction: "rtl",
+											textAlign: "left",
+										}}>
+										{removeLeadingNonAlphanumeric(tool.path ?? "") + "\u200E"}
+									</span>
+								</div>
+								<span
+									className={`codicon codicon-link-external`}
+									style={{ fontSize: 13, margin: "1.5px 0" }}></span>
+							</div>
+						</div>
 					</>
 					</>
 				)
 				)
 			case "listFilesTopLevel":
 			case "listFilesTopLevel":

+ 1 - 1
webview-ui/src/components/CodeAccordian.tsx

@@ -18,7 +18,7 @@ We need to remove leading non-alphanumeric characters from the path in order for
 [^a-zA-Z0-9]+: Matches one or more characters that are not alphanumeric.
 [^a-zA-Z0-9]+: Matches one or more characters that are not alphanumeric.
 The replace method removes these matched characters, effectively trimming the string up to the first alphanumeric character.
 The replace method removes these matched characters, effectively trimming the string up to the first alphanumeric character.
 */
 */
-const removeLeadingNonAlphanumeric = (path: string): string => path.replace(/^[^a-zA-Z0-9]+/, "")
+export const removeLeadingNonAlphanumeric = (path: string): string => path.replace(/^[^a-zA-Z0-9]+/, "")
 
 
 const CodeAccordian = ({ code, diff, language, path, isFeedback, isExpanded, onToggleExpand }: CodeAccordianProps) => {
 const CodeAccordian = ({ code, diff, language, path, isFeedback, isExpanded, onToggleExpand }: CodeAccordianProps) => {
 	const inferredLanguage = useMemo(
 	const inferredLanguage = useMemo(