Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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