feat: opened tabs and selection in the @ menu

This commit is contained in:
loup
2025-01-31 09:16:43 +01:00
committed by Matt Rubens
parent 90ba9e18e1
commit 064dc4e52f
7 changed files with 131 additions and 10 deletions

View File

@@ -50,7 +50,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
},
ref,
) => {
const { filePaths, currentApiConfigName, listApiConfigMeta, customModes } = useExtensionState()
const { filePaths, openedTabs, activeSelection, currentApiConfigName, listApiConfigMeta, customModes } =
useExtensionState()
const [gitCommits, setGitCommits] = useState<any[]>([])
const [showDropdown, setShowDropdown] = useState(false)
@@ -89,6 +90,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
return () => window.removeEventListener("message", messageHandler)
}, [setInputValue])
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const [thumbnailsHeight, setThumbnailsHeight] = useState(0)
const [textAreaBaseHeight, setTextAreaBaseHeight] = useState<number | undefined>(undefined)
const [showContextMenu, setShowContextMenu] = useState(false)
@@ -135,17 +137,36 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}, [inputValue, textAreaDisabled, setInputValue])
const queryItems = useMemo(() => {
return [
const items = [
{ type: ContextMenuOptionType.Problems, value: "problems" },
...gitCommits,
// Add opened tabs
...openedTabs
.filter((tab) => tab.path)
.map((tab) => ({
type: ContextMenuOptionType.OpenedFile,
value: "/" + tab.path,
})),
// Add regular file paths
...filePaths
.map((file) => "/" + file)
.filter((path) => !openedTabs.some((tab) => tab.path && "/" + tab.path === path)) // Filter out paths that are already in openedTabs
.map((path) => ({
type: path.endsWith("/") ? ContextMenuOptionType.Folder : ContextMenuOptionType.File,
value: path,
})),
]
}, [filePaths, gitCommits])
if (activeSelection) {
items.unshift({
type: ContextMenuOptionType.OpenedFile,
value: `/${activeSelection.file}:${activeSelection.selection.startLine + 1}-${activeSelection.selection.endLine + 1}`,
})
}
return items
}, [filePaths, openedTabs, activeSelection])
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {

View File

@@ -74,6 +74,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
return <span>Git Commits</span>
}
case ContextMenuOptionType.File:
case ContextMenuOptionType.OpenedFile:
case ContextMenuOptionType.Folder:
if (option.value) {
return (
@@ -100,6 +101,8 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
const getIconForOption = (option: ContextMenuQueryItem): string => {
switch (option.type) {
case ContextMenuOptionType.OpenedFile:
return "star-full"
case ContextMenuOptionType.File:
return "file"
case ContextMenuOptionType.Folder:
@@ -194,6 +197,7 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
{(option.type === ContextMenuOptionType.Problems ||
((option.type === ContextMenuOptionType.File ||
option.type === ContextMenuOptionType.Folder ||
option.type === ContextMenuOptionType.OpenedFile ||
option.type === ContextMenuOptionType.Git) &&
option.value)) && (
<i

View File

@@ -27,6 +27,11 @@ export interface ExtensionStateContextType extends ExtensionState {
openAiModels: string[]
mcpServers: McpServer[]
filePaths: string[]
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
activeSelection: {
file: string
selection: { startLine: number; endLine: number }
} | null
setApiConfiguration: (config: ApiConfiguration) => void
setCustomInstructions: (value?: string) => void
setAlwaysAllowReadOnly: (value: boolean) => void
@@ -116,6 +121,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
const [glamaModels, setGlamaModels] = useState<Record<string, ModelInfo>>({
[glamaDefaultModelId]: glamaDefaultModelInfo,
})
const [openedTabs, setOpenedTabs] = useState<Array<{ label: string; isActive: boolean; path?: string }>>([])
const [activeSelection, setActiveSelection] = useState<{
file: string
selection: { startLine: number; endLine: number }
} | null>(null)
const [openRouterModels, setOpenRouterModels] = useState<Record<string, ModelInfo>>({
[openRouterDefaultModelId]: openRouterDefaultModelInfo,
})
@@ -176,7 +186,13 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
break
}
case "workspaceUpdated": {
setFilePaths(message.filePaths ?? [])
const paths = message.filePaths ?? []
const tabs = message.openedTabs ?? []
const selection = message.activeSelection ?? null
setFilePaths(paths)
setOpenedTabs(tabs)
setActiveSelection(selection)
break
}
case "partialMessage": {
@@ -243,6 +259,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
openAiModels,
mcpServers,
filePaths,
openedTabs,
activeSelection,
soundVolume: state.soundVolume,
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
writeDelayMs: state.writeDelayMs,

View File

@@ -48,6 +48,7 @@ export function removeMention(text: string, position: number): { newText: string
}
export enum ContextMenuOptionType {
OpenedFile = "openedFile",
File = "file",
Folder = "folder",
Problems = "problems",
@@ -80,8 +81,14 @@ export function getContextMenuOptions(
if (query === "") {
if (selectedType === ContextMenuOptionType.File) {
const files = queryItems
.filter((item) => item.type === ContextMenuOptionType.File)
.map((item) => ({ type: ContextMenuOptionType.File, value: item.value }))
.filter(
(item) =>
item.type === ContextMenuOptionType.File || item.type === ContextMenuOptionType.OpenedFile,
)
.map((item) => ({
type: item.type,
value: item.value,
}))
return files.length > 0 ? files : [{ type: ContextMenuOptionType.NoResults }]
}
@@ -125,6 +132,12 @@ export function getContextMenuOptions(
}
if (query.startsWith("http")) {
suggestions.push({ type: ContextMenuOptionType.URL, value: query })
} else {
suggestions.push(
...queryItems
.filter((item) => item.type !== ContextMenuOptionType.OpenedFile)
.filter((item) => item.value?.toLowerCase().includes(lowerQuery)),
)
}
// Add exact SHA matches to suggestions