Make mentions clickable

This commit is contained in:
Saoud Rizwan
2024-09-18 14:10:37 -04:00
parent c8050e603d
commit 593b3d6b7c
9 changed files with 55 additions and 18 deletions

View File

@@ -28,6 +28,7 @@ class WorkspaceTracker {
private registerListeners() { private registerListeners() {
// Listen for file creation // Listen for file creation
// .bind(this) ensures the callback refers to class instance when using this, not necessary when using arrow function
this.disposables.push(vscode.workspace.onDidCreateFiles(this.onFilesCreated.bind(this))) this.disposables.push(vscode.workspace.onDidCreateFiles(this.onFilesCreated.bind(this)))
// Listen for file deletion // Listen for file deletion

View File

@@ -12,6 +12,7 @@ import axios from "axios"
import { getTheme } from "../utils/getTheme" import { getTheme } from "../utils/getTheme"
import { openFile, openImage } from "../utils/open-file" import { openFile, openImage } from "../utils/open-file"
import WorkspaceTracker from "../integrations/WorkspaceTracker" import WorkspaceTracker from "../integrations/WorkspaceTracker"
import { openMention } from "../utils/context-mentions"
/* /*
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
@@ -423,6 +424,9 @@ export class ClaudeDevProvider implements vscode.WebviewViewProvider {
case "openFile": case "openFile":
openFile(message.text!) openFile(message.text!)
break break
case "openMention":
openMention(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)
} }

View File

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

View File

@@ -0,0 +1,9 @@
/*
Mention regex
- File and folder paths (starting with '/')
- URLs (containing '://')
- The 'problems' keyword
- Word boundary after 'problems' to avoid partial matches
*/
export const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+|problems\b)/
export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")

View File

@@ -0,0 +1,28 @@
import * as vscode from "vscode"
import * as path from "path"
import { openFile } from "./open-file"
export function openMention(mention?: string): void {
if (!mention) {
return
}
if (mention.startsWith("/")) {
const relPath = mention.slice(1)
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
if (!cwd) {
return
}
const absPath = path.resolve(cwd, relPath)
if (mention.endsWith("/")) {
vscode.commands.executeCommand("revealInExplorer", vscode.Uri.file(absPath))
// vscode.commands.executeCommand("vscode.openFolder", , { forceNewWindow: false }) opens in new window
} else {
openFile(absPath)
}
} else if (mention === "problems") {
vscode.commands.executeCommand("workbench.actions.view.problems")
} else if (mention.startsWith("http")) {
vscode.env.openExternal(vscode.Uri.parse(mention))
}
}

View File

@@ -4,15 +4,14 @@ import { useExtensionState } from "../context/ExtensionStateContext"
import { import {
getContextMenuOptions, getContextMenuOptions,
insertMention, insertMention,
mentionRegex,
mentionRegexGlobal,
removeMention, removeMention,
shouldShowContextMenu, shouldShowContextMenu,
ContextMenuOptionType, ContextMenuOptionType,
} from "../utils/mention-context" } from "../utils/context-mentions"
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
import ContextMenu from "./ContextMenu" import ContextMenu from "./ContextMenu"
import Thumbnails from "./Thumbnails" import Thumbnails from "./Thumbnails"
import { mentionRegex, mentionRegexGlobal } from "../../../src/shared/context-mentions"
interface ChatTextAreaProps { interface ChatTextAreaProps {
inputValue: string inputValue: string

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useRef } from "react" import React, { useEffect, useMemo, useRef } from "react"
import { ContextMenuOptionType, ContextMenuQueryItem, getContextMenuOptions } from "../utils/mention-context" import { ContextMenuOptionType, ContextMenuQueryItem, getContextMenuOptions } from "../utils/context-mentions"
import { formatFilePathForTruncation } from "./CodeAccordian" import { formatFilePathForTruncation } from "./CodeAccordian"
interface ContextMenuProps { interface ContextMenuProps {
@@ -109,14 +109,15 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
ref={menuRef} ref={menuRef}
style={{ style={{
backgroundColor: "var(--vscode-dropdown-background)", backgroundColor: "var(--vscode-dropdown-background)",
border: "1px solid var(--vscode-dropdown-border)", border: "1px solid var(--vscode-editorGroup-border)",
borderRadius: "3px", borderRadius: "3px",
boxShadow: "0 4px 10px rgba(0, 0, 0, 0.25)",
zIndex: 1000, zIndex: 1000,
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
boxShadow: "0 8px 16px rgba(0,0,0,0.24)",
maxHeight: "200px", maxHeight: "200px",
overflowY: "auto", overflowY: "auto",
overflow: "hidden",
}}> }}>
{/* Can't use virtuoso since it requires fixed height and menu height is dynamic based on # of items */} {/* Can't use virtuoso since it requires fixed height and menu height is dynamic based on # of items */}
{filteredOptions.map((option, index) => ( {filteredOptions.map((option, index) => (
@@ -127,7 +128,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
padding: "8px 12px", padding: "8px 12px",
cursor: isOptionSelectable(option) ? "pointer" : "default", cursor: isOptionSelectable(option) ? "pointer" : "default",
color: "var(--vscode-dropdown-foreground)", color: "var(--vscode-dropdown-foreground)",
borderBottom: "1px solid var(--vscode-dropdown-border)", borderBottom: "1px solid var(--vscode-editorGroup-border)",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",

View File

@@ -3,9 +3,9 @@ import React, { memo, useEffect, useMemo, useRef, useState } from "react"
import { useWindowSize } from "react-use" import { useWindowSize } from "react-use"
import { ClaudeMessage } from "../../../src/shared/ExtensionMessage" import { ClaudeMessage } from "../../../src/shared/ExtensionMessage"
import { useExtensionState } from "../context/ExtensionStateContext" import { useExtensionState } from "../context/ExtensionStateContext"
import { mentionRegexGlobal } from "../utils/mention-context"
import { vscode } from "../utils/vscode" import { vscode } from "../utils/vscode"
import Thumbnails from "./Thumbnails" import Thumbnails from "./Thumbnails"
import { mentionRegexGlobal } from "../../../src/shared/context-mentions"
interface TaskHeaderProps { interface TaskHeaderProps {
task: ClaudeMessage task: ClaudeMessage
@@ -351,7 +351,9 @@ export const highlightMentions = (text?: string, withShadow = true) => {
return ( return (
<span <span
key={index} key={index}
className={withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"}> className={withShadow ? "mention-context-highlight-with-shadow" : "mention-context-highlight"}
style={{ cursor: "pointer" }}
onClick={() => vscode.postMessage({ type: "openMention", text: part })}>
@{part} @{part}
</span> </span>
) )

View File

@@ -1,12 +1,4 @@
/* import { mentionRegex } from "../../../src/shared/context-mentions"
Mention regex
- File and folder paths (starting with '/')
- URLs (containing '://')
- The 'problems' keyword
- Word boundary after 'problems' to avoid partial matches
*/
export const mentionRegex = /@((?:\/|\w+:\/\/)[^\s]+|problems\b)/
export const mentionRegexGlobal = new RegExp(mentionRegex.source, "g")
export function insertMention(text: string, position: number, value: string): string { export function insertMention(text: string, position: number, value: string): string {
const beforeCursor = text.slice(0, position) const beforeCursor = text.slice(0, position)