mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-07 08:26:51 +00:00
feat: add terminal instance selection for @terminal context mention
- Add TerminalInfo interface and terminalSearchResults message type - Update context-mentions.ts to show terminal sub-selection menu - Add searchTerminals handler in webviewMessageHandler.ts - Update ChatTextArea.tsx to fetch terminals when Terminal type selected - Update parseMentions to handle @terminal:name syntax - Update getLatestTerminalOutput to support specific terminal selection - Update openMention to focus specific terminal when clicked Addresses #10622
This commit is contained in:
parent
632b86cfbe
commit
a6253d69cd
5 changed files with 104 additions and 4 deletions
|
|
@ -36,6 +36,7 @@ export interface ExtensionMessage {
|
|||
| "mcpServers"
|
||||
| "enhancedPrompt"
|
||||
| "commitSearchResults"
|
||||
| "terminalSearchResults"
|
||||
| "listApiConfig"
|
||||
| "routerModels"
|
||||
| "openAiModels"
|
||||
|
|
@ -144,6 +145,7 @@ export interface ExtensionMessage {
|
|||
}>
|
||||
mcpServers?: McpServer[]
|
||||
commits?: GitCommit[]
|
||||
terminals?: TerminalInfo[] // For terminalSearchResults
|
||||
listApiConfig?: ProviderSettingsEntry[]
|
||||
mode?: string
|
||||
customMode?: ModeConfig
|
||||
|
|
@ -336,6 +338,15 @@ export interface Command {
|
|||
argumentHint?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminal instance information for terminal selection
|
||||
*/
|
||||
export interface TerminalInfo {
|
||||
id: number
|
||||
name: string
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* WebviewMessage
|
||||
* Webview | CLI -> Extension
|
||||
|
|
@ -424,6 +435,7 @@ export interface WebviewMessage {
|
|||
| "remoteControlEnabled"
|
||||
| "taskSyncEnabled"
|
||||
| "searchCommits"
|
||||
| "searchTerminals"
|
||||
| "setApiConfigPassword"
|
||||
| "mode"
|
||||
| "updatePrompt"
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ export async function openMention(cwd: string, mention?: string): Promise<void>
|
|||
vscode.commands.executeCommand("workbench.actions.view.problems")
|
||||
} else if (mention === "terminal") {
|
||||
vscode.commands.executeCommand("workbench.action.terminal.focus")
|
||||
} else if (mention.startsWith("terminal:")) {
|
||||
const terminalName = mention.slice(9)
|
||||
const targetTerminal = vscode.window.terminals.find((t) => t.name === terminalName)
|
||||
if (targetTerminal) {
|
||||
targetTerminal.show()
|
||||
} else {
|
||||
vscode.commands.executeCommand("workbench.action.terminal.focus")
|
||||
}
|
||||
} else if (mention.startsWith("http")) {
|
||||
vscode.env.openExternal(vscode.Uri.parse(mention))
|
||||
}
|
||||
|
|
@ -144,6 +152,9 @@ export async function parseMentions(
|
|||
return `Git commit '${mention}' (see below for commit info)`
|
||||
} else if (mention === "terminal") {
|
||||
return `Terminal Output (see below for output)`
|
||||
} else if (mention.startsWith("terminal:")) {
|
||||
const terminalName = mention.slice(9)
|
||||
return `Terminal Output from '${terminalName}' (see below for output)`
|
||||
}
|
||||
return match
|
||||
})
|
||||
|
|
@ -242,6 +253,14 @@ export async function parseMentions(
|
|||
} catch (error) {
|
||||
parsedText += `\n\n<terminal_output>\nError fetching terminal output: ${error.message}\n</terminal_output>`
|
||||
}
|
||||
} else if (mention.startsWith("terminal:")) {
|
||||
const terminalName = mention.slice(9)
|
||||
try {
|
||||
const terminalOutput = await getLatestTerminalOutput(terminalName)
|
||||
parsedText += `\n\n<terminal_output name="${terminalName}">\n${terminalOutput}\n</terminal_output>`
|
||||
} catch (error) {
|
||||
parsedText += `\n\n<terminal_output name="${terminalName}">\nError fetching terminal output: ${error.message}\n</terminal_output>`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,14 +397,27 @@ async function getWorkspaceProblems(
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the contents of the active terminal
|
||||
* Gets the contents of a terminal
|
||||
* @param terminalName Optional name of the terminal to get output from. If not provided, gets output from the active terminal.
|
||||
* @returns The terminal contents as a string
|
||||
*/
|
||||
export async function getLatestTerminalOutput(): Promise<string> {
|
||||
export async function getLatestTerminalOutput(terminalName?: string): Promise<string> {
|
||||
// Store original clipboard content to restore later
|
||||
const originalClipboard = await vscode.env.clipboard.readText()
|
||||
|
||||
try {
|
||||
// If a specific terminal name is provided, find and focus it first
|
||||
if (terminalName) {
|
||||
const targetTerminal = vscode.window.terminals.find((t) => t.name === terminalName)
|
||||
if (!targetTerminal) {
|
||||
return `Terminal '${terminalName}' not found`
|
||||
}
|
||||
// Show the terminal to make it active
|
||||
targetTerminal.show()
|
||||
// Small delay to ensure terminal is focused
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
|
||||
// Select terminal content
|
||||
await vscode.commands.executeCommand("workbench.action.terminal.selectAll")
|
||||
|
||||
|
|
|
|||
|
|
@ -1714,6 +1714,26 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
break
|
||||
}
|
||||
case "searchTerminals": {
|
||||
try {
|
||||
// Get all VS Code terminal instances
|
||||
const vsCodeTerminals = vscode.window.terminals
|
||||
const terminals = vsCodeTerminals.map((terminal, index) => ({
|
||||
id: index,
|
||||
name: terminal.name,
|
||||
isActive: vscode.window.activeTerminal === terminal,
|
||||
}))
|
||||
await provider.postMessageToWebview({
|
||||
type: "terminalSearchResults",
|
||||
terminals,
|
||||
})
|
||||
} catch (error) {
|
||||
provider.log(
|
||||
`Error searching terminals: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
case "searchFiles": {
|
||||
const workspacePath = getCurrentCwd()
|
||||
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
}, [listApiConfigMeta, currentApiConfigName])
|
||||
|
||||
const [gitCommits, setGitCommits] = useState<any[]>([])
|
||||
const [terminalInstances, setTerminalInstances] = useState<any[]>([])
|
||||
const [showDropdown, setShowDropdown] = useState(false)
|
||||
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>([])
|
||||
const [searchLoading, setSearchLoading] = useState(false)
|
||||
|
|
@ -198,6 +199,16 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
if (message.requestId === searchRequestId) {
|
||||
setFileSearchResults(message.results || [])
|
||||
}
|
||||
} else if (message.type === "terminalSearchResults") {
|
||||
const terminals =
|
||||
message.terminals?.map((terminal: { id: number; name: string; isActive: boolean }) => ({
|
||||
type: ContextMenuOptionType.Terminal,
|
||||
value: `terminal:${terminal.name}`,
|
||||
label: terminal.name,
|
||||
description: terminal.isActive ? "Active terminal" : "Terminal instance",
|
||||
icon: "$(terminal)",
|
||||
})) || []
|
||||
setTerminalInstances(terminals)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,6 +252,16 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
}
|
||||
}, [selectedType, searchQuery])
|
||||
|
||||
// Fetch terminal instances when Terminal is selected.
|
||||
useEffect(() => {
|
||||
if (selectedType === ContextMenuOptionType.Terminal) {
|
||||
const message: WebviewMessage = {
|
||||
type: "searchTerminals",
|
||||
} as const
|
||||
vscode.postMessage(message)
|
||||
}
|
||||
}, [selectedType])
|
||||
|
||||
const handleEnhancePrompt = useCallback(() => {
|
||||
const trimmedInput = inputValue.trim()
|
||||
|
||||
|
|
@ -275,6 +296,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
{ type: ContextMenuOptionType.Problems, value: "problems" },
|
||||
{ type: ContextMenuOptionType.Terminal, value: "terminal" },
|
||||
...gitCommits,
|
||||
...terminalInstances,
|
||||
...openedTabs
|
||||
.filter((tab) => tab.path)
|
||||
.map((tab) => ({
|
||||
|
|
@ -289,7 +311,7 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
value: path,
|
||||
})),
|
||||
]
|
||||
}, [filePaths, gitCommits, openedTabs])
|
||||
}, [filePaths, gitCommits, terminalInstances, openedTabs])
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
|
|
@ -349,7 +371,8 @@ export const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
|
|||
if (
|
||||
type === ContextMenuOptionType.File ||
|
||||
type === ContextMenuOptionType.Folder ||
|
||||
type === ContextMenuOptionType.Git
|
||||
type === ContextMenuOptionType.Git ||
|
||||
type === ContextMenuOptionType.Terminal
|
||||
) {
|
||||
if (!value) {
|
||||
setSelectedType(type)
|
||||
|
|
|
|||
|
|
@ -248,6 +248,19 @@ export function getContextMenuOptions(
|
|||
return commits.length > 0 ? [workingChanges, ...commits] : [workingChanges]
|
||||
}
|
||||
|
||||
if (selectedType === ContextMenuOptionType.Terminal) {
|
||||
const terminals = queryItems.filter((item) => item.type === ContextMenuOptionType.Terminal)
|
||||
// Always include "All Terminals" option first, then specific terminals
|
||||
const allTerminalsOption: ContextMenuQueryItem = {
|
||||
type: ContextMenuOptionType.Terminal,
|
||||
value: "terminal",
|
||||
label: "All Terminals",
|
||||
description: "Output from all terminal instances",
|
||||
icon: "$(terminal)",
|
||||
}
|
||||
return terminals.length > 0 ? [allTerminalsOption, ...terminals] : [allTerminalsOption]
|
||||
}
|
||||
|
||||
return [
|
||||
{ type: ContextMenuOptionType.Problems },
|
||||
{ type: ContextMenuOptionType.Terminal },
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue