Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export const globalSettingsSchema = z.object({
terminalZshP10k: z.boolean().optional(),
terminalZdotdir: z.boolean().optional(),
terminalCompressProgressBar: z.boolean().optional(),
terminalPreferredProfile: z.string().optional(),

diagnosticsEnabled: z.boolean().optional(),

Expand Down
5 changes: 5 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ export class ClineProvider
terminalZshP10k = false,
terminalPowershellCounter = false,
terminalZdotdir = false,
terminalPreferredProfile,
}) => {
Terminal.setShellIntegrationTimeout(terminalShellIntegrationTimeout)
Terminal.setShellIntegrationDisabled(terminalShellIntegrationDisabled)
Expand All @@ -764,6 +765,7 @@ export class ClineProvider
Terminal.setTerminalZshP10k(terminalZshP10k)
Terminal.setPowershellCounter(terminalPowershellCounter)
Terminal.setTerminalZdotdir(terminalZdotdir)
Terminal.setTerminalPreferredProfile(terminalPreferredProfile)
},
)

Expand Down Expand Up @@ -1813,6 +1815,7 @@ export class ClineProvider
terminalZshOhMy,
terminalZshP10k,
terminalZdotdir,
terminalPreferredProfile,
fuzzyMatchThreshold,
mcpEnabled,
enableMcpServerCreation,
Expand Down Expand Up @@ -1942,6 +1945,7 @@ export class ClineProvider
terminalZshOhMy: terminalZshOhMy ?? false,
terminalZshP10k: terminalZshP10k ?? false,
terminalZdotdir: terminalZdotdir ?? false,
terminalPreferredProfile: terminalPreferredProfile ?? "",
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
mcpEnabled: mcpEnabled ?? true,
enableMcpServerCreation: enableMcpServerCreation ?? true,
Expand Down Expand Up @@ -2167,6 +2171,7 @@ export class ClineProvider
terminalZshP10k: stateValues.terminalZshP10k ?? false,
terminalZdotdir: stateValues.terminalZdotdir ?? false,
terminalCompressProgressBar: stateValues.terminalCompressProgressBar ?? true,
terminalPreferredProfile: stateValues.terminalPreferredProfile ?? "",
mode: stateValues.mode ?? defaultModeSlug,
language: stateValues.language ?? formatLanguage(vscode.env.language),
mcpEnabled: stateValues.mcpEnabled ?? true,
Expand Down
26 changes: 26 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { experimentDefault } from "../../shared/experiments"
import { Terminal } from "../../integrations/terminal/Terminal"
import { TerminalProfileService } from "../../integrations/terminal/TerminalProfileService"
import { openFile } from "../../integrations/misc/open-file"
import { openImage, saveImage } from "../../integrations/misc/image-handler"
import { selectImages } from "../../integrations/misc/process-images"
Expand Down Expand Up @@ -1524,6 +1525,31 @@ export const webviewMessageHandler = async (
Terminal.setCompressProgressBar(message.bool)
}
break
case "terminalPreferredProfile":
await updateGlobalState("terminalPreferredProfile", message.text)
await provider.postStateToWebview()
if (message.text !== undefined) {
Terminal.setTerminalPreferredProfile(message.text)
}
break
case "getTerminalProfiles":
try {
const profiles = TerminalProfileService.getAllSelectableProfiles()
await provider.postMessageToWebview({
type: "terminalProfiles",
profiles,
})
} catch (error) {
provider.log(
`Error getting terminal profiles: ${error instanceof Error ? error.message : String(error)}`,
)
// Send empty array on error
await provider.postMessageToWebview({
type: "terminalProfiles",
profiles: [],
})
}
break
case "mode":
await provider.handleModeSwitch(message.text as Mode)
break
Expand Down
17 changes: 17 additions & 0 deletions src/integrations/terminal/BaseTerminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export abstract class BaseTerminal implements RooTerminal {
private static terminalZshP10k: boolean = false
private static terminalZdotdir: boolean = false
private static compressProgressBar: boolean = true
private static terminalPreferredProfile: string | undefined = undefined

/**
* Compresses terminal output by applying run-length encoding and truncating to line limit
Expand Down Expand Up @@ -314,4 +315,20 @@ export abstract class BaseTerminal implements RooTerminal {
public static getCompressProgressBar(): boolean {
return BaseTerminal.compressProgressBar
}

/**
* Sets the preferred terminal profile
* @param profileName The preferred terminal profile name
*/
public static setTerminalPreferredProfile(profileName: string | undefined): void {
BaseTerminal.terminalPreferredProfile = profileName
}

/**
* Gets the preferred terminal profile
* @returns The preferred terminal profile name
*/
public static getTerminalPreferredProfile(): string | undefined {
return BaseTerminal.terminalPreferredProfile
}
}
30 changes: 29 additions & 1 deletion src/integrations/terminal/Terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { RooTerminalCallbacks, RooTerminalProcessResultPromise } from "./ty
import { BaseTerminal } from "./BaseTerminal"
import { TerminalProcess } from "./TerminalProcess"
import { ShellIntegrationManager } from "./ShellIntegrationManager"
import { TerminalProfileService } from "./TerminalProfileService"
import { mergePromise } from "./mergePromise"

export class Terminal extends BaseTerminal {
Expand All @@ -17,7 +18,34 @@ export class Terminal extends BaseTerminal {

const env = Terminal.getEnv()
const iconPath = new vscode.ThemeIcon("rocket")
this.terminal = terminal ?? vscode.window.createTerminal({ cwd, name: "Roo Code", iconPath, env })

// Get the full profile configuration from the user's preferred profile
const preferredProfile = Terminal.getTerminalPreferredProfile()
const profileOptions = TerminalProfileService.getTerminalOptionsForRoo(preferredProfile)

const terminalOptions: vscode.TerminalOptions = {
cwd,
name: "Roo Code",
iconPath,
env,
}

// Apply profile configuration if available
if (profileOptions) {
// Merge profile options with our base options
Object.assign(terminalOptions, profileOptions)

// Merge environment variables (profile env + our env)
if (profileOptions.env) {
terminalOptions.env = { ...profileOptions.env, ...env }
}

// Keep our icon and name unless profile specifies otherwise
terminalOptions.name = "Roo Code"
terminalOptions.iconPath = iconPath
Comment on lines +43 to +45
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Comment claims conditional override but code unconditionally sets values. The comment states "Keep our icon and name unless profile specifies otherwise" but lines 44-45 always override any profile-specified name/icon from the Object.assign on line 36. Either remove the "unless" clause from the comment or use conditional assignment like terminalOptions.name = profileOptions.name ?? "Roo Code".

Fix it with Roo Code or mention @roomote and request a fix.

}

this.terminal = terminal ?? vscode.window.createTerminal(terminalOptions)

if (Terminal.getTerminalZdotdir()) {
ShellIntegrationManager.terminalTmpDirs.set(id, env.ZDOTDIR)
Expand Down
194 changes: 194 additions & 0 deletions src/integrations/terminal/TerminalProfileService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import * as vscode from "vscode"

export interface TerminalProfile {
name: string
path?: string
args?: string[]
icon?: string
color?: string
env?: Record<string, string>
}

export interface TerminalProfileInfo {
name: string
displayName: string
shellPath?: string
isDefault?: boolean
}

export class TerminalProfileService {
/**
* Gets the current platform identifier for terminal profiles
*/
private static getPlatform(): string {
switch (process.platform) {
case "win32":
return "windows"
case "darwin":
return "osx"
case "linux":
return "linux"
default:
return "linux"
}
}

/**
* Gets all available terminal profiles for the current platform
*/
public static getAvailableProfiles(): TerminalProfileInfo[] {
const platform = this.getPlatform()
const config = vscode.workspace.getConfiguration("terminal.integrated")

// Get profiles for the current platform
const profiles = config.get<Record<string, TerminalProfile>>(`profiles.${platform}`) || {}

// Get the default profile name
const defaultProfile = config.get<string>(`defaultProfile.${platform}`)

// Convert profiles to our format
const profileInfos: TerminalProfileInfo[] = Object.entries(profiles).map(([name, profile]) => ({
name,
displayName: name,
shellPath: profile.path,
isDefault: name === defaultProfile,
}))

// If no profiles are configured, return an empty array
// VSCode will use its built-in defaults
return profileInfos
}

/**
* Gets the default profile for the current platform
*/
public static getDefaultProfile(): TerminalProfileInfo | undefined {
const platform = this.getPlatform()
const config = vscode.workspace.getConfiguration("terminal.integrated")

const defaultProfileName = config.get<string>(`defaultProfile.${platform}`)

if (defaultProfileName) {
const profiles = this.getAvailableProfiles()
return profiles.find((profile) => profile.name === defaultProfileName)
}

return undefined
}

/**
* Gets a specific profile by name
*/
public static getProfileByName(name: string): TerminalProfileInfo | undefined {
const profiles = this.getAvailableProfiles()
return profiles.find((profile) => profile.name === name)
}

/**
* Gets the shell path for a given profile name
* Returns undefined if profile doesn't exist or doesn't specify a path
*/
public static getShellPathForProfile(profileName: string): string | undefined {
if (!profileName) {
return undefined
}

// Check regular profiles
const profile = this.getProfileByName(profileName)
return profile?.shellPath
}

/**
* Gets the full profile configuration from terminal.integrated.profiles.{platform}
*/
public static getProfileConfiguration(profileName: string): Partial<vscode.TerminalOptions> | undefined {
if (!profileName) {
return undefined
}

const platform = this.getPlatform()
const config = vscode.workspace.getConfiguration("terminal.integrated")
const profiles = config.get<Record<string, TerminalProfile>>(`profiles.${platform}`) || {}

const profile = profiles[profileName]
if (!profile) {
return undefined
}

// Convert VSCode terminal profile to vscode.TerminalOptions
const terminalOptions: Partial<vscode.TerminalOptions> = {}

if (profile.path) {
terminalOptions.shellPath = profile.path
}

if (profile.args) {
terminalOptions.shellArgs = profile.args
}

if (profile.env) {
terminalOptions.env = profile.env
}

if (profile.icon) {
terminalOptions.iconPath = new vscode.ThemeIcon(profile.icon)
}

if (profile.color) {
terminalOptions.color = new vscode.ThemeColor(profile.color)
}

return terminalOptions
}

/**
* Gets the terminal options that Roo should use for terminals
* Priority: 1. User's preferred profile, 2. Default profile, 3. undefined (VSCode default)
*/
public static getTerminalOptionsForRoo(preferredProfile?: string): Partial<vscode.TerminalOptions> | undefined {
// 1. Check user's preferred profile
if (preferredProfile) {
const profileConfig = this.getProfileConfiguration(preferredProfile)
if (profileConfig) {
return profileConfig
}
}

// 2. Check default profile
const defaultProfile = this.getDefaultProfile()
if (defaultProfile) {
const profileConfig = this.getProfileConfiguration(defaultProfile.name)
if (profileConfig) {
return profileConfig
}
}

// 3. Let VSCode use its default
return undefined
}

/**
* Gets the shell path that Roo should use for terminals (backward compatibility)
* Priority: 1. User's preferred profile, 2. Default profile, 3. undefined (VSCode default)
*/
public static getShellPathForRoo(preferredProfile?: string): string | undefined {
const terminalOptions = this.getTerminalOptionsForRoo(preferredProfile)
return terminalOptions?.shellPath
}

/**
* Gets all profiles that should be shown in the UI
*/
public static getAllSelectableProfiles(): TerminalProfileInfo[] {
const profiles = this.getAvailableProfiles()

// Add a "Default" option that represents using VSCode's default behavior
profiles.unshift({
name: "",
displayName: "Default (VSCode Default)",
isDefault: false,
})

return profiles
}
}
Loading
Loading