Merge pull request #714 from RooVetGit/opened_tabs_and_selection_mentions

Mention shortcuts to open tabs
This commit is contained in:
Matt Rubens
2025-02-01 12:23:39 -05:00
committed by GitHub
13 changed files with 117 additions and 222 deletions

View File

@@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Add shortcuts to the currently open tabs in the "Add File" section of @-mentions (thanks @olup!)

View File

@@ -5,9 +5,25 @@ const vscode = {
createTextEditorDecorationType: jest.fn().mockReturnValue({ createTextEditorDecorationType: jest.fn().mockReturnValue({
dispose: jest.fn(), dispose: jest.fn(),
}), }),
tabGroups: {
onDidChangeTabs: jest.fn(() => {
return {
dispose: jest.fn(),
}
}),
all: [],
},
}, },
workspace: { workspace: {
onDidSaveTextDocument: jest.fn(), onDidSaveTextDocument: jest.fn(),
createFileSystemWatcher: jest.fn().mockReturnValue({
onDidCreate: jest.fn().mockReturnValue({ dispose: jest.fn() }),
onDidDelete: jest.fn().mockReturnValue({ dispose: jest.fn() }),
dispose: jest.fn(),
}),
fs: {
stat: jest.fn(),
},
}, },
Disposable: class { Disposable: class {
dispose() {} dispose() {}
@@ -57,6 +73,17 @@ const vscode = {
Development: 2, Development: 2,
Test: 3, Test: 3,
}, },
FileType: {
Unknown: 0,
File: 1,
Directory: 2,
SymbolicLink: 64,
},
TabInputText: class {
constructor(uri) {
this.uri = uri
}
},
} }
module.exports = vscode module.exports = vscode

View File

@@ -128,6 +128,7 @@ jest.mock("vscode", () => {
visibleTextEditors: [mockTextEditor], visibleTextEditors: [mockTextEditor],
tabGroups: { tabGroups: {
all: [mockTabGroup], all: [mockTabGroup],
onDidChangeTabs: jest.fn(() => ({ dispose: jest.fn() })),
}, },
}, },
workspace: { workspace: {

View File

@@ -2,6 +2,7 @@ import * as vscode from "vscode"
import * as path from "path" import * as path from "path"
import { listFiles } from "../../services/glob/list-files" import { listFiles } from "../../services/glob/list-files"
import { ClineProvider } from "../../core/webview/ClineProvider" import { ClineProvider } from "../../core/webview/ClineProvider"
import { toRelativePath } from "../../utils/path"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0) const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
const MAX_INITIAL_FILES = 1_000 const MAX_INITIAL_FILES = 1_000
@@ -48,6 +49,23 @@ class WorkspaceTracker {
) )
this.disposables.push(watcher) this.disposables.push(watcher)
this.disposables.push(vscode.window.tabGroups.onDidChangeTabs(() => this.workspaceDidUpdate()))
}
private getOpenedTabsInfo() {
return vscode.window.tabGroups.all.flatMap((group) =>
group.tabs
.filter((tab) => tab.input instanceof vscode.TabInputText)
.map((tab) => {
const path = (tab.input as vscode.TabInputText).uri.fsPath
return {
label: tab.label,
isActive: tab.isActive,
path: toRelativePath(path, cwd || ""),
}
}),
)
} }
private workspaceDidUpdate() { private workspaceDidUpdate() {
@@ -59,12 +77,12 @@ class WorkspaceTracker {
if (!cwd) { if (!cwd) {
return return
} }
const relativeFilePaths = Array.from(this.filePaths).map((file) => toRelativePath(file, cwd))
this.providerRef.deref()?.postMessageToWebview({ this.providerRef.deref()?.postMessageToWebview({
type: "workspaceUpdated", type: "workspaceUpdated",
filePaths: Array.from(this.filePaths).map((file) => { filePaths: relativeFilePaths,
const relativePath = path.relative(cwd, file).toPosix() openedTabs: this.getOpenedTabsInfo(),
return file.endsWith("/") ? relativePath + "/" : relativePath
}),
}) })
this.updateTimer = null this.updateTimer = null
}, 300) // Debounce for 300ms }, 300) // Debounce for 300ms

View File

@@ -16,6 +16,12 @@ const mockWatcher = {
} }
jest.mock("vscode", () => ({ jest.mock("vscode", () => ({
window: {
tabGroups: {
onDidChangeTabs: jest.fn(() => ({ dispose: jest.fn() })),
all: [],
},
},
workspace: { workspace: {
workspaceFolders: [ workspaceFolders: [
{ {
@@ -61,6 +67,7 @@ describe("WorkspaceTracker", () => {
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "workspaceUpdated", type: "workspaceUpdated",
filePaths: expect.arrayContaining(["file1.ts", "file2.ts"]), filePaths: expect.arrayContaining(["file1.ts", "file2.ts"]),
openedTabs: [],
}) })
expect((mockProvider.postMessageToWebview as jest.Mock).mock.calls[0][0].filePaths).toHaveLength(2) expect((mockProvider.postMessageToWebview as jest.Mock).mock.calls[0][0].filePaths).toHaveLength(2)
}) })
@@ -74,6 +81,7 @@ describe("WorkspaceTracker", () => {
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "workspaceUpdated", type: "workspaceUpdated",
filePaths: ["newfile.ts"], filePaths: ["newfile.ts"],
openedTabs: [],
}) })
}) })
@@ -92,6 +100,7 @@ describe("WorkspaceTracker", () => {
expect(mockProvider.postMessageToWebview).toHaveBeenLastCalledWith({ expect(mockProvider.postMessageToWebview).toHaveBeenLastCalledWith({
type: "workspaceUpdated", type: "workspaceUpdated",
filePaths: [], filePaths: [],
openedTabs: [],
}) })
}) })
@@ -106,6 +115,7 @@ describe("WorkspaceTracker", () => {
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "workspaceUpdated", type: "workspaceUpdated",
filePaths: expect.arrayContaining(["newdir"]), filePaths: expect.arrayContaining(["newdir"]),
openedTabs: [],
}) })
const lastCall = (mockProvider.postMessageToWebview as jest.Mock).mock.calls.slice(-1)[0] const lastCall = (mockProvider.postMessageToWebview as jest.Mock).mock.calls.slice(-1)[0]
expect(lastCall[0].filePaths).toHaveLength(1) expect(lastCall[0].filePaths).toHaveLength(1)
@@ -126,6 +136,7 @@ describe("WorkspaceTracker", () => {
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({ expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "workspaceUpdated", type: "workspaceUpdated",
filePaths: expect.arrayContaining(expectedFiles), filePaths: expect.arrayContaining(expectedFiles),
openedTabs: [],
}) })
expect(calls[0][0].filePaths).toHaveLength(1000) expect(calls[0][0].filePaths).toHaveLength(1000)

View File

@@ -57,6 +57,11 @@ export interface ExtensionMessage {
lmStudioModels?: string[] lmStudioModels?: string[]
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[] vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
filePaths?: string[] filePaths?: string[]
openedTabs?: Array<{
label: string
isActive: boolean
path?: string
}>
partialMessage?: ClineMessage partialMessage?: ClineMessage
glamaModels?: Record<string, ModelInfo> glamaModels?: Record<string, ModelInfo>
openRouterModels?: Record<string, ModelInfo> openRouterModels?: Record<string, ModelInfo>

View File

@@ -99,3 +99,8 @@ export function getReadablePath(cwd: string, relPath?: string): string {
} }
} }
} }
export const toRelativePath = (filePath: string, cwd: string) => {
const relativePath = path.relative(cwd, filePath).toPosix()
return filePath.endsWith("/") ? relativePath + "/" : relativePath
}

View File

@@ -50,7 +50,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}, },
ref, ref,
) => { ) => {
const { filePaths, currentApiConfigName, listApiConfigMeta, customModes } = useExtensionState() const { filePaths, openedTabs, currentApiConfigName, listApiConfigMeta, customModes } = useExtensionState()
const [gitCommits, setGitCommits] = useState<any[]>([]) const [gitCommits, setGitCommits] = useState<any[]>([])
const [showDropdown, setShowDropdown] = useState(false) const [showDropdown, setShowDropdown] = useState(false)
@@ -138,14 +138,21 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
return [ return [
{ type: ContextMenuOptionType.Problems, value: "problems" }, { type: ContextMenuOptionType.Problems, value: "problems" },
...gitCommits, ...gitCommits,
...openedTabs
.filter((tab) => tab.path)
.map((tab) => ({
type: ContextMenuOptionType.OpenedFile,
value: "/" + tab.path,
})),
...filePaths ...filePaths
.map((file) => "/" + file) .map((file) => "/" + file)
.filter((path) => !openedTabs.some((tab) => tab.path && "/" + tab.path === path)) // Filter out paths that are already in openedTabs
.map((path) => ({ .map((path) => ({
type: path.endsWith("/") ? ContextMenuOptionType.Folder : ContextMenuOptionType.File, type: path.endsWith("/") ? ContextMenuOptionType.Folder : ContextMenuOptionType.File,
value: path, value: path,
})), })),
] ]
}, [filePaths, gitCommits]) }, [filePaths, gitCommits, openedTabs])
useEffect(() => { useEffect(() => {
const handleClickOutside = (event: MouseEvent) => { const handleClickOutside = (event: MouseEvent) => {

View File

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

View File

@@ -1,212 +0,0 @@
import { render, fireEvent, screen } from "@testing-library/react"
import { useExtensionState } from "../../../context/ExtensionStateContext"
import AutoApproveMenu from "../AutoApproveMenu"
import { defaultModeSlug, defaultPrompts } from "../../../../../src/shared/modes"
import { experimentDefault } from "../../../../../src/shared/experiments"
// Mock the ExtensionStateContext hook
jest.mock("../../../context/ExtensionStateContext")
const mockUseExtensionState = useExtensionState as jest.MockedFunction<typeof useExtensionState>
describe("AutoApproveMenu", () => {
const defaultMockState = {
// Required state properties
version: "1.0.0",
clineMessages: [],
taskHistory: [],
shouldShowAnnouncement: false,
allowedCommands: [],
soundEnabled: false,
soundVolume: 0.5,
diffEnabled: false,
fuzzyMatchThreshold: 1.0,
preferredLanguage: "English",
writeDelayMs: 1000,
browserViewportSize: "900x600",
screenshotQuality: 75,
terminalOutputLineLimit: 500,
mcpEnabled: true,
requestDelaySeconds: 5,
rateLimitSeconds: 0,
currentApiConfigName: "default",
listApiConfigMeta: [],
mode: defaultModeSlug,
customModePrompts: defaultPrompts,
customSupportPrompts: {},
enhancementApiConfigId: "",
didHydrateState: true,
showWelcome: false,
theme: {},
glamaModels: {},
openRouterModels: {},
openAiModels: [],
mcpServers: [],
filePaths: [],
experiments: experimentDefault,
customModes: [],
enableMcpServerCreation: false,
// Auto-approve specific properties
alwaysAllowReadOnly: false,
alwaysAllowWrite: false,
alwaysAllowExecute: false,
alwaysAllowBrowser: false,
alwaysAllowMcp: false,
alwaysApproveResubmit: false,
alwaysAllowModeSwitch: false,
autoApprovalEnabled: false,
// Required setter functions
setApiConfiguration: jest.fn(),
setCustomInstructions: jest.fn(),
setAlwaysAllowReadOnly: jest.fn(),
setAlwaysAllowWrite: jest.fn(),
setAlwaysAllowExecute: jest.fn(),
setAlwaysAllowBrowser: jest.fn(),
setAlwaysAllowMcp: jest.fn(),
setAlwaysAllowModeSwitch: jest.fn(),
setShowAnnouncement: jest.fn(),
setAllowedCommands: jest.fn(),
setSoundEnabled: jest.fn(),
setSoundVolume: jest.fn(),
setDiffEnabled: jest.fn(),
setBrowserViewportSize: jest.fn(),
setFuzzyMatchThreshold: jest.fn(),
setPreferredLanguage: jest.fn(),
setWriteDelayMs: jest.fn(),
setScreenshotQuality: jest.fn(),
setTerminalOutputLineLimit: jest.fn(),
setMcpEnabled: jest.fn(),
setAlwaysApproveResubmit: jest.fn(),
setRequestDelaySeconds: jest.fn(),
setRateLimitSeconds: jest.fn(),
setCurrentApiConfigName: jest.fn(),
setListApiConfigMeta: jest.fn(),
onUpdateApiConfig: jest.fn(),
setMode: jest.fn(),
setCustomModePrompts: jest.fn(),
setCustomSupportPrompts: jest.fn(),
setEnhancementApiConfigId: jest.fn(),
setAutoApprovalEnabled: jest.fn(),
setExperimentEnabled: jest.fn(),
handleInputChange: jest.fn(),
setCustomModes: jest.fn(),
setEnableMcpServerCreation: jest.fn(),
}
beforeEach(() => {
mockUseExtensionState.mockReturnValue(defaultMockState)
})
afterEach(() => {
jest.clearAllMocks()
})
it("renders with initial collapsed state", () => {
render(<AutoApproveMenu />)
// Check for main checkbox and label
expect(screen.getByText("Auto-approve:")).toBeInTheDocument()
expect(screen.getByText("None")).toBeInTheDocument()
// Verify the menu is collapsed (actions not visible)
expect(screen.queryByText("Read files and directories")).not.toBeInTheDocument()
})
it("expands menu when clicked", () => {
render(<AutoApproveMenu />)
// Click to expand
fireEvent.click(screen.getByText("Auto-approve:"))
// Verify menu items are visible
expect(screen.getByText("Read files and directories")).toBeInTheDocument()
expect(screen.getByText("Edit files")).toBeInTheDocument()
expect(screen.getByText("Execute approved commands")).toBeInTheDocument()
expect(screen.getByText("Use the browser")).toBeInTheDocument()
expect(screen.getByText("Use MCP servers")).toBeInTheDocument()
expect(screen.getByText("Retry failed requests")).toBeInTheDocument()
})
it("toggles main auto-approval checkbox", () => {
render(<AutoApproveMenu />)
const mainCheckbox = screen.getByRole("checkbox")
fireEvent.click(mainCheckbox)
expect(defaultMockState.setAutoApprovalEnabled).toHaveBeenCalledWith(true)
})
it("toggles individual permissions", () => {
render(<AutoApproveMenu />)
// Expand menu
fireEvent.click(screen.getByText("Auto-approve:"))
// Click read files checkbox
fireEvent.click(screen.getByText("Read files and directories"))
expect(defaultMockState.setAlwaysAllowReadOnly).toHaveBeenCalledWith(true)
// Click edit files checkbox
fireEvent.click(screen.getByText("Edit files"))
expect(defaultMockState.setAlwaysAllowWrite).toHaveBeenCalledWith(true)
// Click execute commands checkbox
fireEvent.click(screen.getByText("Execute approved commands"))
expect(defaultMockState.setAlwaysAllowExecute).toHaveBeenCalledWith(true)
})
it("displays enabled actions in summary", () => {
mockUseExtensionState.mockReturnValue({
...defaultMockState,
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
autoApprovalEnabled: true,
})
render(<AutoApproveMenu />)
// Check that enabled actions are shown in summary
expect(screen.getByText("Read, Edit")).toBeInTheDocument()
})
it("preserves checkbox states", () => {
// Mock state with some permissions enabled
const mockState = {
...defaultMockState,
alwaysAllowReadOnly: true,
alwaysAllowWrite: true,
}
// Update mock to return our state
mockUseExtensionState.mockReturnValue(mockState)
render(<AutoApproveMenu />)
// Expand menu
fireEvent.click(screen.getByText("Auto-approve:"))
// Verify read and edit checkboxes are checked
expect(screen.getByLabelText("Read files and directories")).toBeInTheDocument()
expect(screen.getByLabelText("Edit files")).toBeInTheDocument()
// Verify the setters haven't been called yet
expect(mockState.setAlwaysAllowReadOnly).not.toHaveBeenCalled()
expect(mockState.setAlwaysAllowWrite).not.toHaveBeenCalled()
// Collapse menu
fireEvent.click(screen.getByText("Auto-approve:"))
// Expand again
fireEvent.click(screen.getByText("Auto-approve:"))
// Verify checkboxes are still present
expect(screen.getByLabelText("Read files and directories")).toBeInTheDocument()
expect(screen.getByLabelText("Edit files")).toBeInTheDocument()
// Verify the setters still haven't been called
expect(mockState.setAlwaysAllowReadOnly).not.toHaveBeenCalled()
expect(mockState.setAlwaysAllowWrite).not.toHaveBeenCalled()
})
})

View File

@@ -41,6 +41,7 @@ describe("ChatTextArea", () => {
// Default mock implementation for useExtensionState // Default mock implementation for useExtensionState
;(useExtensionState as jest.Mock).mockReturnValue({ ;(useExtensionState as jest.Mock).mockReturnValue({
filePaths: [], filePaths: [],
openedTabs: [],
apiConfiguration: { apiConfiguration: {
apiProvider: "anthropic", apiProvider: "anthropic",
}, },
@@ -51,6 +52,7 @@ describe("ChatTextArea", () => {
it("should be disabled when textAreaDisabled is true", () => { it("should be disabled when textAreaDisabled is true", () => {
;(useExtensionState as jest.Mock).mockReturnValue({ ;(useExtensionState as jest.Mock).mockReturnValue({
filePaths: [], filePaths: [],
openedTabs: [],
}) })
render(<ChatTextArea {...defaultProps} textAreaDisabled={true} />) render(<ChatTextArea {...defaultProps} textAreaDisabled={true} />)
@@ -68,6 +70,7 @@ describe("ChatTextArea", () => {
;(useExtensionState as jest.Mock).mockReturnValue({ ;(useExtensionState as jest.Mock).mockReturnValue({
filePaths: [], filePaths: [],
openedTabs: [],
apiConfiguration, apiConfiguration,
}) })
@@ -85,6 +88,7 @@ describe("ChatTextArea", () => {
it("should not send message when input is empty", () => { it("should not send message when input is empty", () => {
;(useExtensionState as jest.Mock).mockReturnValue({ ;(useExtensionState as jest.Mock).mockReturnValue({
filePaths: [], filePaths: [],
openedTabs: [],
apiConfiguration: { apiConfiguration: {
apiProvider: "openrouter", apiProvider: "openrouter",
}, },
@@ -101,6 +105,7 @@ describe("ChatTextArea", () => {
it("should show loading state while enhancing", () => { it("should show loading state while enhancing", () => {
;(useExtensionState as jest.Mock).mockReturnValue({ ;(useExtensionState as jest.Mock).mockReturnValue({
filePaths: [], filePaths: [],
openedTabs: [],
apiConfiguration: { apiConfiguration: {
apiProvider: "openrouter", apiProvider: "openrouter",
}, },
@@ -123,6 +128,7 @@ describe("ChatTextArea", () => {
// Update apiConfiguration // Update apiConfiguration
;(useExtensionState as jest.Mock).mockReturnValue({ ;(useExtensionState as jest.Mock).mockReturnValue({
filePaths: [], filePaths: [],
openedTabs: [],
apiConfiguration: { apiConfiguration: {
apiProvider: "openrouter", apiProvider: "openrouter",
newSetting: "test", newSetting: "test",

View File

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

View File

@@ -48,6 +48,7 @@ export function removeMention(text: string, position: number): { newText: string
} }
export enum ContextMenuOptionType { export enum ContextMenuOptionType {
OpenedFile = "openedFile",
File = "file", File = "file",
Folder = "folder", Folder = "folder",
Problems = "problems", Problems = "problems",
@@ -80,8 +81,14 @@ export function getContextMenuOptions(
if (query === "") { if (query === "") {
if (selectedType === ContextMenuOptionType.File) { if (selectedType === ContextMenuOptionType.File) {
const files = queryItems const files = queryItems
.filter((item) => item.type === ContextMenuOptionType.File) .filter(
.map((item) => ({ type: ContextMenuOptionType.File, value: item.value })) (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 }] return files.length > 0 ? files : [{ type: ContextMenuOptionType.NoResults }]
} }
@@ -162,12 +169,16 @@ export function getContextMenuOptions(
// Separate matches by type // Separate matches by type
const fileMatches = matchingItems.filter( const fileMatches = matchingItems.filter(
(item) => item.type === ContextMenuOptionType.File || item.type === ContextMenuOptionType.Folder, (item) =>
item.type === ContextMenuOptionType.File ||
item.type === ContextMenuOptionType.OpenedFile ||
item.type === ContextMenuOptionType.Folder,
) )
const gitMatches = matchingItems.filter((item) => item.type === ContextMenuOptionType.Git) const gitMatches = matchingItems.filter((item) => item.type === ContextMenuOptionType.Git)
const otherMatches = matchingItems.filter( const otherMatches = matchingItems.filter(
(item) => (item) =>
item.type !== ContextMenuOptionType.File && item.type !== ContextMenuOptionType.File &&
item.type !== ContextMenuOptionType.OpenedFile &&
item.type !== ContextMenuOptionType.Folder && item.type !== ContextMenuOptionType.Folder &&
item.type !== ContextMenuOptionType.Git, item.type !== ContextMenuOptionType.Git,
) )