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

feat: clickable code references in model responses navigate to source lines (#3087)

Co-authored-by: Eric Wheeler <[email protected]>
KJ7LNW 8 месяцев назад
Родитель
Сommit
305185cd88

+ 72 - 0
src/core/prompts/__tests__/__snapshots__/system.test.ts.snap

@@ -5,6 +5,12 @@ exports[`SYSTEM_PROMPT should exclude diff strategy tool description when diffEn
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -472,6 +478,12 @@ exports[`SYSTEM_PROMPT should exclude diff strategy tool description when diffEn
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -939,6 +951,12 @@ exports[`SYSTEM_PROMPT should explicitly handle undefined mcpHub 1`] = `
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -1406,6 +1424,12 @@ exports[`SYSTEM_PROMPT should handle different browser viewport sizes 1`] = `
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -1929,6 +1953,12 @@ exports[`SYSTEM_PROMPT should include MCP server info when mcpHub is provided 1`
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -2464,6 +2494,12 @@ exports[`SYSTEM_PROMPT should include browser actions when supportsComputerUse i
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -2987,6 +3023,12 @@ exports[`SYSTEM_PROMPT should include diff strategy tool description when diffEn
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -3544,6 +3586,12 @@ exports[`SYSTEM_PROMPT should maintain consistent system prompt 1`] = `
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -4053,6 +4101,12 @@ exports[`addCustomInstructions should exclude MCP server creation info when disa
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -4597,6 +4651,12 @@ exports[`addCustomInstructions should generate correct prompt for architect mode
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -5055,6 +5115,12 @@ exports[`addCustomInstructions should generate correct prompt for ask mode 1`] =
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
@@ -5430,6 +5496,12 @@ exports[`addCustomInstructions should include MCP server creation info when enab
 
 ====
 
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>
+
+====
+
 TOOL USE
 
 You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.

+ 1 - 0
src/core/prompts/sections/index.ts

@@ -7,3 +7,4 @@ export { getMcpServersSection } from "./mcp-servers"
 export { getToolUseGuidelinesSection } from "./tool-use-guidelines"
 export { getCapabilitiesSection } from "./capabilities"
 export { getModesSection } from "./modes"
+export { markdownFormattingSection } from "./markdown-formatting"

+ 7 - 0
src/core/prompts/sections/markdown-formatting.ts

@@ -0,0 +1,7 @@
+export function markdownFormattingSection(): string {
+	return `====
+
+MARKDOWN RULES
+
+ALL responses MUST show ANY \`language construct\` OR filename reterence as clickable, exactly as [\`filename OR language.declaration()\`](relative/file/path.ext:line); line is required for \`syntax\` and optional for filename links. This applies to ALL markdown responses and ALSO those in <attempt_completion>`
+}

+ 3 - 0
src/core/prompts/system.ts

@@ -24,6 +24,7 @@ import {
 	getCapabilitiesSection,
 	getModesSection,
 	addCustomInstructions,
+	markdownFormattingSection,
 } from "./sections"
 import { loadSystemPromptFile } from "./sections/custom-system-prompt"
 import { formatLanguage } from "../../shared/language"
@@ -65,6 +66,8 @@ async function generatePrompt(
 
 	const basePrompt = `${roleDefinition}
 
+${markdownFormattingSection()}
+
 ${getSharedToolUseSection()}
 
 ${getToolDescriptionsForMode(

+ 1 - 1
src/core/webview/webviewMessageHandler.ts

@@ -336,7 +336,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
 			openImage(message.text!)
 			break
 		case "openFile":
-			openFile(message.text!, message.values as { create?: boolean; content?: string })
+			openFile(message.text!, message.values as { create?: boolean; content?: string; line?: number })
 			break
 		case "openMention":
 			openMention(message.text)

+ 7 - 1
src/integrations/misc/open-file.ts

@@ -23,6 +23,7 @@ export async function openImage(dataUri: string) {
 interface OpenFileOptions {
 	create?: boolean
 	content?: string
+	line?: number
 }
 
 export async function openFile(filePath: string, options: OpenFileOptions = {}) {
@@ -75,7 +76,12 @@ export async function openFile(filePath: string, options: OpenFileOptions = {})
 		} catch {} // not essential, sometimes tab operations fail
 
 		const document = await vscode.workspace.openTextDocument(uri)
-		await vscode.window.showTextDocument(document, { preview: false })
+		const selection =
+			options.line !== undefined ? new vscode.Selection(options.line - 1, 0, options.line - 1, 0) : undefined
+		await vscode.window.showTextDocument(document, {
+			preview: false,
+			selection,
+		})
 	} catch (error) {
 		if (error instanceof Error) {
 			vscode.window.showErrorMessage(`Could not open file: ${error.message}`)

+ 50 - 4
webview-ui/src/components/common/MarkdownBlock.tsx

@@ -3,6 +3,7 @@ import { useRemark } from "react-remark"
 import styled from "styled-components"
 import { visit } from "unist-util-visit"
 
+import { vscode } from "@src/utils/vscode"
 import { useExtensionState } from "@src/context/ExtensionStateContext"
 
 import CodeBlock from "./CodeBlock"
@@ -108,11 +109,14 @@ const StyledMarkdown = styled.div`
 	}
 
 	a {
-		text-decoration: none;
-	}
-	a {
+		color: var(--vscode-textLink-foreground);
+		text-decoration-line: underline;
+		text-decoration-style: dotted;
+		text-decoration-color: var(--vscode-textLink-foreground);
 		&:hover {
-			text-decoration: underline;
+			color: var(--vscode-textLink-activeForeground);
+			text-decoration-style: solid;
+			text-decoration-color: var(--vscode-textLink-activeForeground);
 		}
 	}
 `
@@ -137,6 +141,48 @@ const MarkdownBlock = memo(({ markdown }: MarkdownBlockProps) => {
 		rehypePlugins: [],
 		rehypeReactOptions: {
 			components: {
+				a: ({ href, children }: any) => {
+					return (
+						<a
+							href={href}
+							title={href}
+							onClick={(e) => {
+								// Only process file:// protocol or local file paths
+								const isLocalPath =
+									href.startsWith("file://") || href.startsWith("/") || !href.includes("://")
+
+								if (!isLocalPath) {
+									return
+								}
+
+								e.preventDefault()
+
+								// Handle absolute vs project-relative paths
+								let filePath = href.replace("file://", "")
+
+								// Extract line number if present
+								const match = filePath.match(/(.*):(\d+)(-\d+)?$/)
+								let values = undefined
+								if (match) {
+									filePath = match[1]
+									values = { line: parseInt(match[2]) }
+								}
+
+								// Add ./ prefix if needed
+								if (!filePath.startsWith("/") && !filePath.startsWith("./")) {
+									filePath = "./" + filePath
+								}
+
+								vscode.postMessage({
+									type: "openFile",
+									text: filePath,
+									values,
+								})
+							}}>
+							{children}
+						</a>
+					)
+				},
 				pre: ({ node: _, children }: any) => {
 					// Check for Mermaid diagrams first
 					if (Array.isArray(children) && children.length === 1 && React.isValidElement(children[0])) {