Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0430998
feat: move slash commands to settings tab with gear icon for discover…
roomote Sep 15, 2025
46ae66c
feat: simplify slash commands popover and add settings navigation
roomote Sep 15, 2025
bd9a499
ux: Makes text area buttons appear only when there's text (#7987)
brunobergher Sep 15, 2025
a1f8b7d
Removes SlashCommand icon from textarea
brunobergher Sep 15, 2025
f4ab67a
Reorders settings for slash commands
brunobergher Sep 15, 2025
620cf3d
Ensures the gear icon in slash commands goes to the right place in se…
brunobergher Sep 15, 2025
d0ce642
Visual tweaks to make the settings cog work in the slashcommands popo…
brunobergher Sep 15, 2025
690f680
Makes the intro copy for slash commands only appear when there's no s…
brunobergher Sep 15, 2025
6173797
test: add comprehensive test coverage for SlashCommandItemSimple and …
roomote Sep 15, 2025
8801439
fix: corrected C# tree-sitter query (#7813)
mubeen-zulfiqar Sep 15, 2025
05540da
Removes unused components
brunobergher Sep 15, 2025
8041639
Adds translations (including old stuff which had never been localized)
brunobergher Sep 15, 2025
c5df5e4
Merge remote-tracking branch 'origin/main' into feat/slash-commands-s…
mrubens Sep 15, 2025
a09893c
Update webview-ui/src/i18n/locales/ca/chat.json
mrubens Sep 15, 2025
9e86f9a
Update webview-ui/src/i18n/locales/fr/settings.json
mrubens Sep 15, 2025
3eac654
Update webview-ui/src/i18n/locales/ja/chat.json
mrubens Sep 15, 2025
4752dfd
Update webview-ui/src/i18n/locales/nl/chat.json
mrubens Sep 15, 2025
365c817
Update webview-ui/src/i18n/locales/zh-TW/chat.json
mrubens Sep 15, 2025
bb43eb2
Delete webview-ui/src/__tests__/SettingsTabNavigation.spec.tsx
mrubens Sep 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions webview-ui/src/components/chat/ContextMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from "react"
import { getIconForFilePath, getIconUrlByName, getIconForDirectoryPath } from "vscode-material-icons"
import { Settings } from "lucide-react"

import type { ModeConfig } from "@roo-code/types"
import type { Command } from "@roo/ExtensionMessage"
Expand All @@ -11,6 +12,7 @@ import {
SearchResult,
} from "@src/utils/context-mentions"
import { removeLeadingNonAlphanumeric } from "@src/utils/removeLeadingNonAlphanumeric"
import { vscode } from "@src/utils/vscode"

interface ContextMenuProps {
onSelect: (type: ContextMenuOptionType, value?: string) => void
Expand Down Expand Up @@ -251,6 +253,15 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
)
}

const handleSettingsClick = () => {
// Switch to settings tab and navigate to slash commands section
vscode.postMessage({
type: "switchTab",
tab: "settings",
values: { section: "slashCommands" },
})
}

return (
<div
style={{
Expand All @@ -275,6 +286,46 @@ const ContextMenu: React.FC<ContextMenuProps> = ({
overflowY: "auto",
overflowX: "hidden",
}}>
{/* Settings button for slash commands */}
{searchQuery.startsWith("/") && (
<div
style={{
padding: "8px 12px",
borderBottom: "1px solid var(--vscode-editorGroup-border)",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: "var(--vscode-dropdown-background)",
}}>
<span style={{ fontSize: "0.85em", opacity: 0.8 }}>Slash Commands</span>
<button
onClick={handleSettingsClick}
style={{
background: "transparent",
border: "none",
cursor: "pointer",
padding: "4px",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "3px",
color: "var(--vscode-foreground)",
opacity: 0.7,
transition: "opacity 0.2s, background-color 0.2s",
}}
onMouseEnter={(e) => {
e.currentTarget.style.opacity = "1"
e.currentTarget.style.backgroundColor = "var(--vscode-list-hoverBackground)"
}}
onMouseLeave={(e) => {
e.currentTarget.style.opacity = "0.7"
e.currentTarget.style.backgroundColor = "transparent"
}}
title="Manage slash commands in settings">
<Settings size={16} />
</button>
</div>
)}
{filteredOptions && filteredOptions.length > 0 ? (
filteredOptions.map((option, index) => (
<div
Expand Down
28 changes: 28 additions & 0 deletions webview-ui/src/components/chat/SlashCommandItemSimple.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from "react"

import type { Command } from "@roo/ExtensionMessage"

interface SlashCommandItemSimpleProps {
command: Command
onClick?: (command: Command) => void
}

export const SlashCommandItemSimple: React.FC<SlashCommandItemSimpleProps> = ({ command, onClick }) => {
return (
<div
className="px-4 py-2 text-sm flex items-center hover:bg-vscode-list-hoverBackground cursor-pointer"
onClick={() => onClick?.(command)}>
{/* Command name */}
<div className="flex-1 min-w-0">
<div>
<span className="truncate text-vscode-foreground">/{command.name}</span>
{command.description && (
<div className="text-xs text-vscode-descriptionForeground truncate mt-0.5">
{command.description}
</div>
)}
</div>
</div>
</div>
)
}
162 changes: 29 additions & 133 deletions webview-ui/src/components/chat/SlashCommandsList.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,34 @@
import React, { useState } from "react"
import { Plus, Globe, Folder, Settings } from "lucide-react"
import React from "react"
import { Globe, Folder, Settings } from "lucide-react"

import type { Command } from "@roo/ExtensionMessage"

import { useAppTranslation } from "@/i18n/TranslationContext"
import { useExtensionState } from "@/context/ExtensionStateContext"
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
Button,
} from "@/components/ui"
import { Button, StandardTooltip } from "@/components/ui"
import { vscode } from "@/utils/vscode"

import { SlashCommandItem } from "./SlashCommandItem"
import { SlashCommandItemSimple } from "./SlashCommandItemSimple"

interface SlashCommandsListProps {
commands: Command[]
onRefresh: () => void
}

export const SlashCommandsList: React.FC<SlashCommandsListProps> = ({ commands, onRefresh }) => {
export const SlashCommandsList: React.FC<SlashCommandsListProps> = ({ commands }) => {
const { t } = useAppTranslation()
const { cwd } = useExtensionState()
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
const [commandToDelete, setCommandToDelete] = useState<Command | null>(null)
const [globalNewName, setGlobalNewName] = useState("")
const [workspaceNewName, setWorkspaceNewName] = useState("")

// Check if we're in a workspace/project
const hasWorkspace = Boolean(cwd)

const handleDeleteClick = (command: Command) => {
setCommandToDelete(command)
setDeleteDialogOpen(true)
}

const handleDeleteConfirm = () => {
if (commandToDelete) {
vscode.postMessage({
type: "deleteCommand",
text: commandToDelete.name,
values: { source: commandToDelete.source },
})
setDeleteDialogOpen(false)
setCommandToDelete(null)
// Refresh the commands list after deletion
setTimeout(onRefresh, 100)
}
}

const handleDeleteCancel = () => {
setDeleteDialogOpen(false)
setCommandToDelete(null)
}

const handleCreateCommand = (source: "global" | "project", name: string) => {
if (!name.trim()) return

// Append .md if not already present
const fileName = name.trim().endsWith(".md") ? name.trim() : `${name.trim()}.md`

const handleOpenSettings = () => {
// Send message to open settings with the slashCommands tab
vscode.postMessage({
type: "createCommand",
text: fileName,
values: { source },
type: "switchTab",
tab: "settings",
values: { section: "slashCommands" },
})

// Clear the input and refresh
if (source === "global") {
setGlobalNewName("")
} else {
setWorkspaceNewName("")
}
setTimeout(onRefresh, 500)
}

const handleCommandClick = (command: Command) => {
Expand All @@ -96,6 +46,22 @@ export const SlashCommandsList: React.FC<SlashCommandsListProps> = ({ commands,

return (
<>
{/* Header with settings button */}
<div className="flex items-center justify-between px-3 py-2 border-b border-vscode-dropdown-border">
<span className="text-xs font-medium text-vscode-descriptionForeground">
{t("chat:slashCommands.title")}
</span>
<StandardTooltip content={t("chat:slashCommands.manageCommands")}>
<Button
variant="ghost"
size="icon"
onClick={handleOpenSettings}
className="h-6 w-6 p-0 opacity-60 hover:opacity-100">
<Settings className="w-3.5 h-3.5" />
</Button>
</StandardTooltip>
</div>

{/* Commands list */}
<div className="max-h-[300px] overflow-y-auto">
<div className="py-1">
Expand All @@ -105,37 +71,12 @@ export const SlashCommandsList: React.FC<SlashCommandsListProps> = ({ commands,
{t("chat:slashCommands.globalCommands")}
</div>
{globalCommands.map((command) => (
<SlashCommandItem
<SlashCommandItemSimple
key={`global-${command.name}`}
command={command}
onDelete={handleDeleteClick}
onClick={handleCommandClick}
/>
))}
{/* New global command input */}
<div className="px-4 py-2 flex items-center gap-2 hover:bg-vscode-list-hoverBackground">
<input
type="text"
value={globalNewName}
onChange={(e) => setGlobalNewName(e.target.value)}
placeholder={t("chat:slashCommands.newGlobalCommandPlaceholder")}
className="flex-1 bg-transparent text-vscode-input-foreground placeholder-vscode-input-placeholderForeground border-none outline-none focus:outline-0 text-sm"
tabIndex={-1}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleCreateCommand("global", globalNewName)
}
}}
/>
<Button
variant="ghost"
size="icon"
onClick={() => handleCreateCommand("global", globalNewName)}
disabled={!globalNewName.trim()}
className="size-6 flex items-center justify-center opacity-60 hover:opacity-100">
<Plus className="w-4 h-4" />
</Button>
</div>

{/* Workspace Commands Section - Only show if in a workspace */}
{hasWorkspace && (
Expand All @@ -145,37 +86,12 @@ export const SlashCommandsList: React.FC<SlashCommandsListProps> = ({ commands,
{t("chat:slashCommands.workspaceCommands")}
</div>
{projectCommands.map((command) => (
<SlashCommandItem
<SlashCommandItemSimple
key={`project-${command.name}`}
command={command}
onDelete={handleDeleteClick}
onClick={handleCommandClick}
/>
))}
{/* New workspace command input */}
<div className="px-4 py-2 flex items-center gap-2 hover:bg-vscode-list-hoverBackground">
<input
type="text"
value={workspaceNewName}
onChange={(e) => setWorkspaceNewName(e.target.value)}
placeholder={t("chat:slashCommands.newWorkspaceCommandPlaceholder")}
className="flex-1 bg-transparent text-vscode-input-foreground placeholder-vscode-input-placeholderForeground border-none outline-none focus:outline-0 text-sm"
tabIndex={-1}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleCreateCommand("project", workspaceNewName)
}
}}
/>
<Button
variant="ghost"
size="icon"
onClick={() => handleCreateCommand("project", workspaceNewName)}
disabled={!workspaceNewName.trim()}
className="size-6 flex items-center justify-center opacity-60 hover:opacity-100">
<Plus className="w-4 h-4" />
</Button>
</div>
</>
)}

Expand All @@ -187,36 +103,16 @@ export const SlashCommandsList: React.FC<SlashCommandsListProps> = ({ commands,
{t("chat:slashCommands.builtInCommands")}
</div>
{builtInCommands.map((command) => (
<SlashCommandItem
<SlashCommandItemSimple
key={`built-in-${command.name}`}
command={command}
onDelete={handleDeleteClick}
onClick={handleCommandClick}
/>
))}
</>
)}
</div>
</div>

<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("chat:slashCommands.deleteDialog.title")}</AlertDialogTitle>
<AlertDialogDescription>
{t("chat:slashCommands.deleteDialog.description", { name: commandToDelete?.name })}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleDeleteCancel}>
{t("chat:slashCommands.deleteDialog.cancel")}
</AlertDialogCancel>
<AlertDialogAction onClick={handleDeleteConfirm}>
{t("chat:slashCommands.deleteDialog.confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}
7 changes: 7 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
Info,
MessageSquare,
LucideIcon,
SquareSlash,
} from "lucide-react"

import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
Expand Down Expand Up @@ -64,6 +65,7 @@ import { LanguageSettings } from "./LanguageSettings"
import { About } from "./About"
import { Section } from "./Section"
import PromptsSettings from "./PromptsSettings"
import { SlashCommandsSettings } from "./SlashCommandsSettings"

export const settingsTabsContainer = "flex flex-1 overflow-hidden [&.narrow_.tab-label]:hidden"
export const settingsTabList =
Expand All @@ -84,6 +86,7 @@ const sectionNames = [
"notifications",
"contextManagement",
"terminal",
"slashCommands",
"prompts",
"experimental",
"language",
Expand Down Expand Up @@ -453,6 +456,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
{ id: "notifications", icon: Bell },
{ id: "contextManagement", icon: Database },
{ id: "terminal", icon: SquareTerminal },
{ id: "slashCommands", icon: SquareSlash },
{ id: "prompts", icon: MessageSquare },
{ id: "experimental", icon: FlaskConical },
{ id: "language", icon: Globe },
Expand Down Expand Up @@ -738,6 +742,9 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
/>
)}

{/* Slash Commands Section */}
{activeTab === "slashCommands" && <SlashCommandsSettings />}

{/* Prompts Section */}
{activeTab === "prompts" && (
<PromptsSettings
Expand Down
Loading
Loading