Skip to content

Commit 0430998

Browse files
committed
feat: move slash commands to settings tab with gear icon for discoverability
- Created new SlashCommandsSettings component in settings - Added Slash Commands tab to Settings with SquareSlash icon - Moved slash commands management functionality from popover to settings - Added gear icon in slash commands dropdown for quick access to settings - Updated translations for new UI elements
1 parent d54ff8a commit 0430998

File tree

4 files changed

+320
-0
lines changed

4 files changed

+320
-0
lines changed

webview-ui/src/components/chat/ContextMenu.tsx

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useEffect, useMemo, useRef, useState } from "react"
22
import { getIconForFilePath, getIconUrlByName, getIconForDirectoryPath } from "vscode-material-icons"
3+
import { Settings } from "lucide-react"
34

45
import type { ModeConfig } from "@roo-code/types"
56
import type { Command } from "@roo/ExtensionMessage"
@@ -11,6 +12,7 @@ import {
1112
SearchResult,
1213
} from "@src/utils/context-mentions"
1314
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
15+
import { vscode } from "@src/utils/vscode"
1416

1517
interface ContextMenuProps {
1618
onSelect: (type: ContextMenuOptionType, value?: string) => void
@@ -251,6 +253,15 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
251253
)
252254
}
253255

256+
const handleSettingsClick = () => {
257+
// Switch to settings tab and navigate to slash commands section
258+
vscode.postMessage({
259+
type: "switchTab",
260+
tab: "settings",
261+
values: { section: "slashCommands" },
262+
})
263+
}
264+
254265
return (
255266
<div
256267
style={{
@@ -275,6 +286,46 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
275286
overflowY: "auto",
276287
overflowX: "hidden",
277288
}}>
289+
{/* Settings button for slash commands */}
290+
{searchQuery.startsWith("/") && (
291+
<div
292+
style={{
293+
padding: "8px 12px",
294+
borderBottom: "1px solid var(--vscode-editorGroup-border)",
295+
display: "flex",
296+
alignItems: "center",
297+
justifyContent: "space-between",
298+
backgroundColor: "var(--vscode-dropdown-background)",
299+
}}>
300+
<span style={{ fontSize: "0.85em", opacity: 0.8 }}>Slash Commands</span>
301+
<button
302+
onClick={handleSettingsClick}
303+
style={{
304+
background: "transparent",
305+
border: "none",
306+
cursor: "pointer",
307+
padding: "4px",
308+
display: "flex",
309+
alignItems: "center",
310+
justifyContent: "center",
311+
borderRadius: "3px",
312+
color: "var(--vscode-foreground)",
313+
opacity: 0.7,
314+
transition: "opacity 0.2s, background-color 0.2s",
315+
}}
316+
onMouseEnter={(e) => {
317+
e.currentTarget.style.opacity = "1"
318+
e.currentTarget.style.backgroundColor = "var(--vscode-list-hoverBackground)"
319+
}}
320+
onMouseLeave={(e) => {
321+
e.currentTarget.style.opacity = "0.7"
322+
e.currentTarget.style.backgroundColor = "transparent"
323+
}}
324+
title="Manage slash commands in settings">
325+
<Settings size={16} />
326+
</button>
327+
</div>
328+
)}
278329
{filteredOptions && filteredOptions.length > 0 ? (
279330
filteredOptions.map((option, index) => (
280331
<div

webview-ui/src/components/settings/SettingsView.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
Info,
2424
MessageSquare,
2525
LucideIcon,
26+
SquareSlash,
2627
} from "lucide-react"
2728

2829
import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
@@ -64,6 +65,7 @@ import { LanguageSettings } from "./LanguageSettings"
6465
import { About } from "./About"
6566
import { Section } from "./Section"
6667
import PromptsSettings from "./PromptsSettings"
68+
import { SlashCommandsSettings } from "./SlashCommandsSettings"
6769

6870
export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
6971
export const settingsTabList =
@@ -84,6 +86,7 @@ const sectionNames = [
8486
"notifications",
8587
"contextManagement",
8688
"terminal",
89+
"slashCommands",
8790
"prompts",
8891
"experimental",
8992
"language",
@@ -453,6 +456,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
453456
{ id: "notifications", icon: Bell },
454457
{ id: "contextManagement", icon: Database },
455458
{ id: "terminal", icon: SquareTerminal },
459+
{ id: "slashCommands", icon: SquareSlash },
456460
{ id: "prompts", icon: MessageSquare },
457461
{ id: "experimental", icon: FlaskConical },
458462
{ id: "language", icon: Globe },
@@ -738,6 +742,9 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
738742
/>
739743
)}
740744

745+
{/* Slash Commands Section */}
746+
{activeTab === "slashCommands" && <SlashCommandsSettings />}
747+
741748
{/* Prompts Section */}
742749
{activeTab === "prompts" && (
743750
<PromptsSettings
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import React, { useState, useEffect } from "react"
2+
import { Plus, Globe, Folder, Settings, SquareSlash } from "lucide-react"
3+
import { Trans } from "react-i18next"
4+
5+
import type { Command } from "@roo/ExtensionMessage"
6+
7+
import { useAppTranslation } from "@/i18n/TranslationContext"
8+
import { useExtensionState } from "@/context/ExtensionStateContext"
9+
import {
10+
AlertDialog,
11+
AlertDialogAction,
12+
AlertDialogCancel,
13+
AlertDialogContent,
14+
AlertDialogDescription,
15+
AlertDialogFooter,
16+
AlertDialogHeader,
17+
AlertDialogTitle,
18+
Button,
19+
} from "@/components/ui"
20+
import { vscode } from "@/utils/vscode"
21+
import { buildDocLink } from "@/utils/docLinks"
22+
23+
import { SectionHeader } from "./SectionHeader"
24+
import { Section } from "./Section"
25+
import { SlashCommandItem } from "../chat/SlashCommandItem"
26+
27+
export const SlashCommandsSettings: React.FC = () => {
28+
const { t } = useAppTranslation()
29+
const { commands, cwd } = useExtensionState()
30+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
31+
const [commandToDelete, setCommandToDelete] = useState<Command | null>(null)
32+
const [globalNewName, setGlobalNewName] = useState("")
33+
const [workspaceNewName, setWorkspaceNewName] = useState("")
34+
35+
// Check if we're in a workspace/project
36+
const hasWorkspace = Boolean(cwd)
37+
38+
// Request commands when component mounts
39+
useEffect(() => {
40+
handleRefresh()
41+
}, [])
42+
43+
const handleRefresh = () => {
44+
vscode.postMessage({ type: "requestCommands" })
45+
}
46+
47+
const handleDeleteClick = (command: Command) => {
48+
setCommandToDelete(command)
49+
setDeleteDialogOpen(true)
50+
}
51+
52+
const handleDeleteConfirm = () => {
53+
if (commandToDelete) {
54+
vscode.postMessage({
55+
type: "deleteCommand",
56+
text: commandToDelete.name,
57+
values: { source: commandToDelete.source },
58+
})
59+
setDeleteDialogOpen(false)
60+
setCommandToDelete(null)
61+
// Refresh the commands list after deletion
62+
setTimeout(handleRefresh, 100)
63+
}
64+
}
65+
66+
const handleDeleteCancel = () => {
67+
setDeleteDialogOpen(false)
68+
setCommandToDelete(null)
69+
}
70+
71+
const handleCreateCommand = (source: "global" | "project", name: string) => {
72+
if (!name.trim()) return
73+
74+
// Append .md if not already present
75+
const fileName = name.trim().endsWith(".md") ? name.trim() : `${name.trim()}.md`
76+
77+
vscode.postMessage({
78+
type: "createCommand",
79+
text: fileName,
80+
values: { source },
81+
})
82+
83+
// Clear the input and refresh
84+
if (source === "global") {
85+
setGlobalNewName("")
86+
} else {
87+
setWorkspaceNewName("")
88+
}
89+
setTimeout(handleRefresh, 500)
90+
}
91+
92+
const handleCommandClick = (command: Command) => {
93+
// For now, we'll just show the command name - editing functionality can be added later
94+
// This could be enhanced to open the command file in the editor
95+
console.log(`Command clicked: ${command.name} (${command.source})`)
96+
}
97+
98+
// Group commands by source
99+
const builtInCommands = commands?.filter((cmd) => cmd.source === "built-in") || []
100+
const globalCommands = commands?.filter((cmd) => cmd.source === "global") || []
101+
const projectCommands = commands?.filter((cmd) => cmd.source === "project") || []
102+
103+
return (
104+
<div>
105+
<SectionHeader>
106+
<div className="flex items-center gap-2">
107+
<SquareSlash className="w-4" />
108+
<div>{t("settings:sections.slashCommands")}</div>
109+
</div>
110+
</SectionHeader>
111+
112+
<Section>
113+
{/* Description section */}
114+
<div className="mb-4">
115+
<p className="text-sm text-vscode-descriptionForeground">
116+
<Trans
117+
i18nKey="settings:slashCommands.description"
118+
components={{
119+
DocsLink: (
120+
<a
121+
href={buildDocLink("features/slash-commands", "slash_commands_settings")}
122+
target="_blank"
123+
rel="noopener noreferrer"
124+
className="text-vscode-textLink-foreground hover:underline">
125+
Docs
126+
</a>
127+
),
128+
}}
129+
/>
130+
</p>
131+
</div>
132+
133+
{/* Global Commands Section */}
134+
<div className="mb-6">
135+
<div className="flex items-center gap-1.5 mb-2">
136+
<Globe className="w-3 h-3" />
137+
<h4 className="text-sm font-medium m-0">{t("chat:slashCommands.globalCommands")}</h4>
138+
</div>
139+
<div className="border border-vscode-panel-border rounded-md">
140+
{globalCommands.map((command) => (
141+
<SlashCommandItem
142+
key={`global-${command.name}`}
143+
command={command}
144+
onDelete={handleDeleteClick}
145+
onClick={handleCommandClick}
146+
/>
147+
))}
148+
{/* New global command input */}
149+
<div className="px-4 py-2 flex items-center gap-2 hover:bg-vscode-list-hoverBackground border-t border-vscode-panel-border">
150+
<input
151+
type="text"
152+
value={globalNewName}
153+
onChange={(e) => setGlobalNewName(e.target.value)}
154+
placeholder={t("chat:slashCommands.newGlobalCommandPlaceholder")}
155+
className="flex-1 bg-vscode-input-background text-vscode-input-foreground placeholder-vscode-input-placeholderForeground border border-vscode-input-border rounded px-2 py-1 text-sm focus:outline-none focus:border-vscode-focusBorder"
156+
onKeyDown={(e) => {
157+
if (e.key === "Enter") {
158+
handleCreateCommand("global", globalNewName)
159+
}
160+
}}
161+
/>
162+
<Button
163+
variant="ghost"
164+
size="icon"
165+
onClick={() => handleCreateCommand("global", globalNewName)}
166+
disabled={!globalNewName.trim()}
167+
className="size-6 flex items-center justify-center opacity-60 hover:opacity-100">
168+
<Plus className="w-4 h-4" />
169+
</Button>
170+
</div>
171+
</div>
172+
</div>
173+
174+
{/* Workspace Commands Section - Only show if in a workspace */}
175+
{hasWorkspace && (
176+
<div className="mb-6">
177+
<div className="flex items-center gap-1.5 mb-2">
178+
<Folder className="w-3 h-3" />
179+
<h4 className="text-sm font-medium m-0">{t("chat:slashCommands.workspaceCommands")}</h4>
180+
</div>
181+
<div className="border border-vscode-panel-border rounded-md">
182+
{projectCommands.map((command) => (
183+
<SlashCommandItem
184+
key={`project-${command.name}`}
185+
command={command}
186+
onDelete={handleDeleteClick}
187+
onClick={handleCommandClick}
188+
/>
189+
))}
190+
{/* New workspace command input */}
191+
<div className="px-4 py-2 flex items-center gap-2 hover:bg-vscode-list-hoverBackground border-t border-vscode-panel-border">
192+
<input
193+
type="text"
194+
value={workspaceNewName}
195+
onChange={(e) => setWorkspaceNewName(e.target.value)}
196+
placeholder={t("chat:slashCommands.newWorkspaceCommandPlaceholder")}
197+
className="flex-1 bg-vscode-input-background text-vscode-input-foreground placeholder-vscode-input-placeholderForeground border border-vscode-input-border rounded px-2 py-1 text-sm focus:outline-none focus:border-vscode-focusBorder"
198+
onKeyDown={(e) => {
199+
if (e.key === "Enter") {
200+
handleCreateCommand("project", workspaceNewName)
201+
}
202+
}}
203+
/>
204+
<Button
205+
variant="ghost"
206+
size="icon"
207+
onClick={() => handleCreateCommand("project", workspaceNewName)}
208+
disabled={!workspaceNewName.trim()}
209+
className="size-6 flex items-center justify-center opacity-60 hover:opacity-100">
210+
<Plus className="w-4 h-4" />
211+
</Button>
212+
</div>
213+
</div>
214+
</div>
215+
)}
216+
217+
{/* Built-in Commands Section */}
218+
{builtInCommands.length > 0 && (
219+
<div className="mb-6">
220+
<div className="flex items-center gap-1.5 mb-2">
221+
<Settings className="w-3 h-3" />
222+
<h4 className="text-sm font-medium m-0">{t("chat:slashCommands.builtInCommands")}</h4>
223+
</div>
224+
<div className="border border-vscode-panel-border rounded-md">
225+
{builtInCommands.map((command) => (
226+
<SlashCommandItem
227+
key={`built-in-${command.name}`}
228+
command={command}
229+
onDelete={handleDeleteClick}
230+
onClick={handleCommandClick}
231+
/>
232+
))}
233+
</div>
234+
</div>
235+
)}
236+
</Section>
237+
238+
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
239+
<AlertDialogContent>
240+
<AlertDialogHeader>
241+
<AlertDialogTitle>{t("chat:slashCommands.deleteDialog.title")}</AlertDialogTitle>
242+
<AlertDialogDescription>
243+
{t("chat:slashCommands.deleteDialog.description", { name: commandToDelete?.name })}
244+
</AlertDialogDescription>
245+
</AlertDialogHeader>
246+
<AlertDialogFooter>
247+
<AlertDialogCancel onClick={handleDeleteCancel}>
248+
{t("chat:slashCommands.deleteDialog.cancel")}
249+
</AlertDialogCancel>
250+
<AlertDialogAction onClick={handleDeleteConfirm}>
251+
{t("chat:slashCommands.deleteDialog.confirm")}
252+
</AlertDialogAction>
253+
</AlertDialogFooter>
254+
</AlertDialogContent>
255+
</AlertDialog>
256+
</div>
257+
)
258+
}

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@
2828
"notifications": "Notifications",
2929
"contextManagement": "Context",
3030
"terminal": "Terminal",
31+
"slashCommands": "Slash Commands",
3132
"prompts": "Prompts",
3233
"experimental": "Experimental",
3334
"language": "Language",
3435
"about": "About Roo Code"
3536
},
37+
"slashCommands": {
38+
"description": "Manage your slash commands to quickly execute custom workflows and actions. <DocsLink>Learn more</DocsLink>"
39+
},
3640
"prompts": {
3741
"description": "Configure support prompts that are used for quick actions like enhancing prompts, explaining code, and fixing issues. These prompts help Roo provide better assistance for common development tasks."
3842
},

0 commit comments

Comments
 (0)