mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Better command highlighting (#6336)
This commit is contained in:
parent
e117208c29
commit
00a3738d30
2 changed files with 168 additions and 3 deletions
|
|
@ -714,15 +714,41 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
|
||||
const text = textAreaRef.current.value
|
||||
|
||||
highlightLayerRef.current.innerHTML = text
|
||||
// Helper function to check if a command is valid
|
||||
const isValidCommand = (commandName: string): boolean => {
|
||||
return commands?.some((cmd) => cmd.name === commandName) || false
|
||||
}
|
||||
|
||||
// Process the text to highlight mentions and valid commands
|
||||
let processedText = text
|
||||
.replace(/\n$/, "\n\n")
|
||||
.replace(/[<>&]/g, (c) => ({ "<": "<", ">": ">", "&": "&" })[c] || c)
|
||||
.replace(mentionRegexGlobal, '<mark class="mention-context-textarea-highlight">$&</mark>')
|
||||
.replace(commandRegexGlobal, '<mark class="mention-context-textarea-highlight">$&</mark>')
|
||||
|
||||
// Custom replacement for commands - only highlight valid ones
|
||||
processedText = processedText.replace(commandRegexGlobal, (match, commandName) => {
|
||||
// Only highlight if the command exists in the valid commands list
|
||||
if (isValidCommand(commandName)) {
|
||||
// Check if the match starts with a space
|
||||
const startsWithSpace = match.startsWith(" ")
|
||||
const commandPart = `/${commandName}`
|
||||
|
||||
if (startsWithSpace) {
|
||||
// Keep the space but only highlight the command part
|
||||
return ` <mark class="mention-context-textarea-highlight">${commandPart}</mark>`
|
||||
} else {
|
||||
// Highlight the entire command (starts at beginning of line)
|
||||
return `<mark class="mention-context-textarea-highlight">${commandPart}</mark>`
|
||||
}
|
||||
}
|
||||
return match // Return unhighlighted if command is not valid
|
||||
})
|
||||
|
||||
highlightLayerRef.current.innerHTML = processedText
|
||||
|
||||
highlightLayerRef.current.scrollTop = textAreaRef.current.scrollTop
|
||||
highlightLayerRef.current.scrollLeft = textAreaRef.current.scrollLeft
|
||||
}, [])
|
||||
}, [commands])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
updateHighlights()
|
||||
|
|
@ -973,6 +999,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
)}>
|
||||
<div
|
||||
ref={highlightLayerRef}
|
||||
data-testid="highlight-layer"
|
||||
className={cn(
|
||||
"absolute",
|
||||
"inset-0",
|
||||
|
|
|
|||
|
|
@ -904,6 +904,144 @@ describe("ChatTextArea", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("slash command highlighting", () => {
|
||||
const mockCommands = [
|
||||
{ name: "setup", source: "project", description: "Setup the project" },
|
||||
{ name: "deploy", source: "global", description: "Deploy the application" },
|
||||
{ name: "test-command", source: "project", description: "Test command with dash" },
|
||||
]
|
||||
|
||||
beforeEach(() => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
commands: mockCommands,
|
||||
})
|
||||
})
|
||||
|
||||
it("should highlight valid slash commands", () => {
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/setup the project" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// The highlighting is applied via innerHTML, so we need to check the content
|
||||
// The valid command "/setup" should be highlighted
|
||||
expect(highlightLayer.innerHTML).toContain('<mark class="mention-context-textarea-highlight">/setup</mark>')
|
||||
})
|
||||
|
||||
it("should not highlight invalid slash commands", () => {
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/invalid command" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// The invalid command "/invalid" should not be highlighted
|
||||
expect(highlightLayer.innerHTML).not.toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/invalid</mark>',
|
||||
)
|
||||
// But it should still contain the text without highlighting
|
||||
expect(highlightLayer.innerHTML).toContain("/invalid")
|
||||
})
|
||||
|
||||
it("should highlight only the command portion, not arguments", () => {
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/deploy to production" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// Only "/deploy" should be highlighted, not "to production"
|
||||
expect(highlightLayer.innerHTML).toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/deploy</mark>',
|
||||
)
|
||||
expect(highlightLayer.innerHTML).not.toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/deploy to production</mark>',
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle commands with dashes and underscores", () => {
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/test-command with args" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// The command with dash should be highlighted
|
||||
expect(highlightLayer.innerHTML).toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/test-command</mark>',
|
||||
)
|
||||
})
|
||||
|
||||
it("should be case-sensitive when matching commands", () => {
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/Setup the project" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// "/Setup" (capital S) should not be highlighted since the command is "setup" (lowercase)
|
||||
expect(highlightLayer.innerHTML).not.toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/Setup</mark>',
|
||||
)
|
||||
expect(highlightLayer.innerHTML).toContain("/Setup")
|
||||
})
|
||||
|
||||
it("should highlight multiple valid commands in the same text", () => {
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/setup first then /deploy" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// Both valid commands should be highlighted
|
||||
expect(highlightLayer.innerHTML).toContain('<mark class="mention-context-textarea-highlight">/setup</mark>')
|
||||
expect(highlightLayer.innerHTML).toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/deploy</mark>',
|
||||
)
|
||||
})
|
||||
|
||||
it("should handle mixed valid and invalid commands", () => {
|
||||
const { getByTestId } = render(
|
||||
<ChatTextArea {...defaultProps} inputValue="/setup first then /invalid then /deploy" />,
|
||||
)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// Valid commands should be highlighted
|
||||
expect(highlightLayer.innerHTML).toContain('<mark class="mention-context-textarea-highlight">/setup</mark>')
|
||||
expect(highlightLayer.innerHTML).toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/deploy</mark>',
|
||||
)
|
||||
|
||||
// Invalid command should not be highlighted
|
||||
expect(highlightLayer.innerHTML).not.toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/invalid</mark>',
|
||||
)
|
||||
expect(highlightLayer.innerHTML).toContain("/invalid")
|
||||
})
|
||||
|
||||
it("should work when no commands are available", () => {
|
||||
;(useExtensionState as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
filePaths: [],
|
||||
openedTabs: [],
|
||||
taskHistory: [],
|
||||
cwd: "/test/workspace",
|
||||
commands: undefined,
|
||||
})
|
||||
|
||||
const { getByTestId } = render(<ChatTextArea {...defaultProps} inputValue="/setup the project" />)
|
||||
|
||||
const highlightLayer = getByTestId("highlight-layer")
|
||||
expect(highlightLayer).toBeInTheDocument()
|
||||
|
||||
// No commands should be highlighted when commands array is undefined
|
||||
expect(highlightLayer.innerHTML).not.toContain(
|
||||
'<mark class="mention-context-textarea-highlight">/setup</mark>',
|
||||
)
|
||||
expect(highlightLayer.innerHTML).toContain("/setup")
|
||||
})
|
||||
})
|
||||
|
||||
describe("selectApiConfig", () => {
|
||||
// Helper function to get the API config dropdown
|
||||
const getApiConfigDropdown = () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue