mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Retrieve workspace filepaths for context menu
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { forwardRef, useCallback, useEffect, useRef, useState, useLayoutEffect } from "react"
|
||||
import React, { forwardRef, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||
import DynamicTextArea from "react-textarea-autosize"
|
||||
import { insertMention, shouldShowContextMenu, getContextMenuOptions, removeMention } from "../utils/mention-context"
|
||||
import { useExtensionState } from "../context/ExtensionStateContext"
|
||||
import { getContextMenuOptions, insertMention, removeMention, shouldShowContextMenu } from "../utils/mention-context"
|
||||
import { MAX_IMAGES_PER_MESSAGE } from "./ChatView"
|
||||
import ContextMenu from "./ContextMenu"
|
||||
import Thumbnails from "./Thumbnails"
|
||||
@@ -49,6 +50,18 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
const [intendedCursorPosition, setIntendedCursorPosition] = useState<number | null>(null)
|
||||
const contextMenuContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { filePaths } = useExtensionState()
|
||||
|
||||
const searchPaths = React.useMemo(() => {
|
||||
return [
|
||||
{ type: "problems", path: "problems" },
|
||||
...filePaths.map((path) => ({
|
||||
type: path.endsWith("/") ? "folder" : "file",
|
||||
path: path,
|
||||
})),
|
||||
]
|
||||
}, [filePaths])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
@@ -70,7 +83,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
|
||||
const handleMentionSelect = useCallback(
|
||||
(type: string, value: string) => {
|
||||
if (value === "File" || value === "Folder") {
|
||||
if (value === "file" || value === "folder") {
|
||||
setSelectedType(type.toLowerCase())
|
||||
setSearchQuery("")
|
||||
setSelectedMenuIndex(0)
|
||||
@@ -108,17 +121,18 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
const handleKeyDown = useCallback(
|
||||
(event: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (showContextMenu) {
|
||||
// if (event.key === "Escape") {
|
||||
// // event.preventDefault()
|
||||
// setShowContextMenu(false)
|
||||
// return
|
||||
// }
|
||||
if (event.key === "Escape") {
|
||||
// event.preventDefault()
|
||||
setSelectedType(null)
|
||||
setSelectedMenuIndex(3) // File by default
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
||||
event.preventDefault()
|
||||
setSelectedMenuIndex((prevIndex) => {
|
||||
const direction = event.key === "ArrowUp" ? -1 : 1
|
||||
const options = getContextMenuOptions(searchQuery, selectedType)
|
||||
const options = getContextMenuOptions(searchQuery, selectedType, searchPaths)
|
||||
const optionsLength = options.length
|
||||
|
||||
if (optionsLength === 0) return prevIndex
|
||||
@@ -144,7 +158,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
}
|
||||
if (event.key === "Enter" && selectedMenuIndex !== -1) {
|
||||
event.preventDefault()
|
||||
const selectedOption = getContextMenuOptions(searchQuery, selectedType)[selectedMenuIndex]
|
||||
const selectedOption = getContextMenuOptions(searchQuery, selectedType, searchPaths)[
|
||||
selectedMenuIndex
|
||||
]
|
||||
if (selectedOption && selectedOption.type !== "url") {
|
||||
handleMentionSelect(selectedOption.type, selectedOption.value)
|
||||
}
|
||||
@@ -203,6 +219,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
cursorPosition,
|
||||
setInputValue,
|
||||
justDeletedSpaceAfterMention,
|
||||
searchPaths,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -361,6 +378,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
||||
selectedIndex={selectedMenuIndex}
|
||||
setSelectedIndex={setSelectedMenuIndex}
|
||||
selectedType={selectedType}
|
||||
searchPaths={searchPaths}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState, useRef } from "react"
|
||||
import React, { useEffect, useRef, useState } from "react"
|
||||
import { getContextMenuOptions } from "../utils/mention-context"
|
||||
|
||||
interface ContextMenuProps {
|
||||
@@ -8,6 +8,7 @@ interface ContextMenuProps {
|
||||
selectedIndex: number
|
||||
setSelectedIndex: (index: number) => void
|
||||
selectedType: string | null
|
||||
searchPaths: { type: string; path: string }[]
|
||||
}
|
||||
|
||||
const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
@@ -17,13 +18,16 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
selectedIndex,
|
||||
setSelectedIndex,
|
||||
selectedType,
|
||||
searchPaths,
|
||||
}) => {
|
||||
const [filteredOptions, setFilteredOptions] = useState(getContextMenuOptions(searchQuery, selectedType))
|
||||
const [filteredOptions, setFilteredOptions] = useState(
|
||||
getContextMenuOptions(searchQuery, selectedType, searchPaths)
|
||||
)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredOptions(getContextMenuOptions(searchQuery, selectedType))
|
||||
}, [searchQuery, selectedType])
|
||||
setFilteredOptions(getContextMenuOptions(searchQuery, selectedType, searchPaths))
|
||||
}, [searchQuery, selectedType, searchPaths])
|
||||
|
||||
useEffect(() => {
|
||||
if (menuRef.current) {
|
||||
@@ -84,23 +88,23 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
|
||||
onMouseEnter={() => option.type !== "url" && setSelectedIndex(index)}>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<i className={`codicon codicon-${option.icon}`} style={{ marginRight: "8px" }} />
|
||||
{option.value === "File"
|
||||
{option.value === "file"
|
||||
? "Add File"
|
||||
: option.value === "Folder"
|
||||
: option.value === "folder"
|
||||
? "Add Folder"
|
||||
: option.value === "Problems"
|
||||
? "Workspace Problems"
|
||||
: option.value === "URL"
|
||||
: option.value === "problems"
|
||||
? "Problems"
|
||||
: option.value === "url"
|
||||
? "Paste URL to scrape"
|
||||
: option.value}
|
||||
</div>
|
||||
{(option.value === "File" || option.value === "Folder") && (
|
||||
{(option.value === "file" || option.value === "folder") && (
|
||||
<i className="codicon codicon-chevron-right" style={{ fontSize: "14px" }} />
|
||||
)}
|
||||
{(option.type === "problems" ||
|
||||
((option.type === "file" || option.type === "folder") &&
|
||||
option.value !== "File" &&
|
||||
option.value !== "Folder")) && (
|
||||
option.value !== "file" &&
|
||||
option.value !== "folder")) && (
|
||||
<i className="codicon codicon-add" style={{ fontSize: "14px" }} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ interface ExtensionStateContextType extends ExtensionState {
|
||||
didHydrateState: boolean
|
||||
showWelcome: boolean
|
||||
theme: any
|
||||
filePaths: string[]
|
||||
setApiConfiguration: (config: ApiConfiguration) => void
|
||||
setCustomInstructions: (value?: string) => void
|
||||
setAlwaysAllowReadOnly: (value: boolean) => void
|
||||
@@ -27,6 +28,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
const [theme, setTheme] = useState<any>(undefined)
|
||||
const [filePaths, setFilePaths] = useState<string[]>([])
|
||||
|
||||
const handleMessage = useCallback((event: MessageEvent) => {
|
||||
const message: ExtensionMessage = event.data
|
||||
if (message.type === "state" && message.state) {
|
||||
@@ -50,6 +53,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
if (message.type === "theme" && message.text) {
|
||||
setTheme(convertTextMateToHljs(JSON.parse(message.text)))
|
||||
}
|
||||
if (message.type === "workspaceUpdated" && message.filePaths) {
|
||||
setFilePaths(message.filePaths)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEvent("message", handleMessage)
|
||||
@@ -63,6 +69,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
didHydrateState,
|
||||
showWelcome,
|
||||
theme,
|
||||
filePaths,
|
||||
setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })),
|
||||
setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
|
||||
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
export const mockPaths = [
|
||||
{ type: "problems", path: "Problems" },
|
||||
{ type: "file", path: "/src/components/Header.tsx" },
|
||||
{ type: "file", path: "/src/components/Footer.tsx" },
|
||||
{ type: "file", path: "/src/utils/helpers.ts" },
|
||||
{ type: "folder", path: "/src/components" },
|
||||
{ type: "folder", path: "/src/utils" },
|
||||
{ type: "folder", path: "/public/images" },
|
||||
{ type: "file", path: "/public/index.html" },
|
||||
{ type: "file", path: "/package.json" },
|
||||
{ type: "folder", path: "/node_modules" },
|
||||
{ type: "file", path: "/README.md" },
|
||||
]
|
||||
// export const mockPaths = [
|
||||
// { type: "problems", path: "Problems" },
|
||||
// { type: "file", path: "/src/components/Header.tsx" },
|
||||
// { type: "file", path: "/src/components/Footer.tsx" },
|
||||
// { type: "file", path: "/src/utils/helpers.ts" },
|
||||
// { type: "folder", path: "/src/components" },
|
||||
// { type: "folder", path: "/src/utils" },
|
||||
// { type: "folder", path: "/public/images" },
|
||||
// { type: "file", path: "/public/index.html" },
|
||||
// { type: "file", path: "/package.json" },
|
||||
// { type: "folder", path: "/node_modules" },
|
||||
// { type: "file", path: "/README.md" },
|
||||
// ]
|
||||
|
||||
export function insertMention(text: string, position: number, value: string): string {
|
||||
const beforeCursor = text.slice(0, position)
|
||||
@@ -48,39 +48,42 @@ export function removeMention(text: string, position: number): { newText: string
|
||||
return { newText: text, newPosition: position }
|
||||
}
|
||||
|
||||
export function searchPaths(query: string): { type: string; path: string }[] {
|
||||
const lowerQuery = query.toLowerCase()
|
||||
return mockPaths.filter(
|
||||
(item) => item.path.toLowerCase().includes(lowerQuery) || item.type.toLowerCase().includes(lowerQuery)
|
||||
)
|
||||
}
|
||||
// export function queryPaths(
|
||||
// query: string,
|
||||
// searchPaths: { type: string; path: string }[]
|
||||
// ): { type: string; path: string }[] {
|
||||
// const lowerQuery = query.toLowerCase()
|
||||
// return searchPaths.filter(
|
||||
// (item) => item.path.toLowerCase().includes(lowerQuery) || item.type.toLowerCase().includes(lowerQuery)
|
||||
// )
|
||||
// }
|
||||
|
||||
export function getContextMenuOptions(
|
||||
query: string,
|
||||
selectedType: string | null = null
|
||||
selectedType: string | null = null,
|
||||
searchPaths: { type: string; path: string }[]
|
||||
): { type: string; value: string; icon: string }[] {
|
||||
if (selectedType === "file") {
|
||||
return mockPaths
|
||||
.filter((item) => item.type === "file")
|
||||
.map((item) => ({ type: "file", value: item.path, icon: "file" }))
|
||||
}
|
||||
|
||||
if (selectedType === "folder") {
|
||||
return mockPaths
|
||||
.filter((item) => item.type === "folder")
|
||||
.map((item) => ({ type: "folder", value: item.path, icon: "folder" }))
|
||||
}
|
||||
|
||||
if (query === "") {
|
||||
if (selectedType === "file") {
|
||||
return searchPaths
|
||||
.filter((item) => item.type === "file")
|
||||
.map((item) => ({ type: "file", value: item.path, icon: "file" }))
|
||||
}
|
||||
|
||||
if (selectedType === "folder") {
|
||||
return searchPaths
|
||||
.filter((item) => item.type === "folder")
|
||||
.map((item) => ({ type: "folder", value: item.path, icon: "folder" }))
|
||||
}
|
||||
return [
|
||||
{ type: "url", value: "URL", icon: "link" },
|
||||
{ type: "url", value: "url", icon: "link" },
|
||||
{
|
||||
type: "problems",
|
||||
value: "Problems",
|
||||
value: "problems",
|
||||
icon: "warning",
|
||||
},
|
||||
{ type: "folder", value: "Folder", icon: "folder" },
|
||||
{ type: "file", value: "File", icon: "file" },
|
||||
{ type: "folder", value: "folder", icon: "folder" },
|
||||
{ type: "file", value: "file", icon: "file" },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -91,9 +94,7 @@ export function getContextMenuOptions(
|
||||
return [{ type: "url", value: query, icon: "link" }]
|
||||
} else {
|
||||
// Search for files and folders
|
||||
const matchingPaths = mockPaths.filter(
|
||||
(item) => item.path.toLowerCase().includes(lowerQuery) || item.type.toLowerCase().includes(lowerQuery)
|
||||
)
|
||||
const matchingPaths = searchPaths.filter((item) => item.path.toLowerCase().includes(lowerQuery))
|
||||
|
||||
if (matchingPaths.length > 0) {
|
||||
return matchingPaths.map((item) => ({
|
||||
@@ -104,14 +105,14 @@ export function getContextMenuOptions(
|
||||
} else {
|
||||
// If no matches, show all options
|
||||
return [
|
||||
{ type: "url", value: "URL", icon: "link" },
|
||||
{ type: "url", value: "url", icon: "link" },
|
||||
{
|
||||
type: "problems",
|
||||
value: "Problems",
|
||||
value: "problems",
|
||||
icon: "warning",
|
||||
},
|
||||
{ type: "folder", value: "Folder", icon: "folder" },
|
||||
{ type: "file", value: "File", icon: "file" },
|
||||
{ type: "folder", value: "folder", icon: "folder" },
|
||||
{ type: "file", value: "file", icon: "file" },
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user