Skip to content
Draft
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
6 changes: 6 additions & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ export const globalSettingsSchema = z.object({
* @default "send"
*/
enterBehavior: z.enum(["send", "newline"]).optional(),
/**
* Multiplier for the chat view font size.
* - Min: 0.5, Max: 2.0
* @default 1.0
*/
chatFontSizeMultiplier: z.number().min(0.5).max(2).optional(),
profileThresholds: z.record(z.string(), z.number()).optional(),
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
Expand Down
4 changes: 4 additions & 0 deletions packages/types/src/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export const commandIds = [
"acceptInput",
"focusPanel",
"toggleAutoApprove",

"increaseChatFontSize",
"decreaseChatFontSize",
"resetChatFontSize",
] as const

export type CommandId = (typeof commandIds)[number]
Expand Down
34 changes: 34 additions & 0 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,40 @@ const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOpt
action: "toggleAutoApprove",
})
},
increaseChatFontSize: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

if (!visibleProvider) {
return
}

const currentMultiplier = visibleProvider.contextProxy.getValue("chatFontSizeMultiplier") ?? 1
const newMultiplier = Math.min(2, currentMultiplier + 0.1)
Comment on lines +205 to +206
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Floating point precision issue: using currentMultiplier + 0.1 can produce values like 1.2000000000000002 after repeated increments, which will display in the UI input field. Consider rounding to one decimal place:

Suggested change
const currentMultiplier = visibleProvider.contextProxy.getValue("chatFontSizeMultiplier") ?? 1
const newMultiplier = Math.min(2, currentMultiplier + 0.1)
const currentMultiplier = visibleProvider.contextProxy.getValue("chatFontSizeMultiplier") ?? 1
const newMultiplier = Math.min(2, Math.round((currentMultiplier + 0.1) * 10) / 10)

The same applies to decreaseChatFontSize below.

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

await visibleProvider.contextProxy.setValue("chatFontSizeMultiplier", newMultiplier)
await visibleProvider.postStateToWebview()
},
decreaseChatFontSize: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

if (!visibleProvider) {
return
}

const currentMultiplier = visibleProvider.contextProxy.getValue("chatFontSizeMultiplier") ?? 1
const newMultiplier = Math.max(0.5, currentMultiplier - 0.1)
await visibleProvider.contextProxy.setValue("chatFontSizeMultiplier", newMultiplier)
await visibleProvider.postStateToWebview()
},
resetChatFontSize: async () => {
const visibleProvider = getVisibleProviderOrLog(outputChannel)

if (!visibleProvider) {
return
}

await visibleProvider.contextProxy.setValue("chatFontSizeMultiplier", 1)
await visibleProvider.postStateToWebview()
},
})

export const openClineInNewTab = async ({ context, outputChannel }: Omit<RegisterCommandOptions, "provider">) => {
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1882,6 +1882,7 @@ export class ClineProvider
historyPreviewCollapsed,
reasoningBlockCollapsed,
enterBehavior,
chatFontSizeMultiplier,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
Expand Down Expand Up @@ -2034,6 +2035,7 @@ export class ClineProvider
historyPreviewCollapsed: historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
enterBehavior: enterBehavior ?? "send",
chatFontSizeMultiplier: chatFontSizeMultiplier ?? 1,
cloudUserInfo,
cloudIsAuthenticated: cloudIsAuthenticated ?? false,
cloudAuthSkipModel: this.context.globalState.get<boolean>("roo-auth-skip-model") ?? false,
Expand Down Expand Up @@ -2280,6 +2282,7 @@ export class ClineProvider
historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
reasoningBlockCollapsed: stateValues.reasoningBlockCollapsed ?? true,
enterBehavior: stateValues.enterBehavior ?? "send",
chatFontSizeMultiplier: stateValues.chatFontSizeMultiplier ?? 1,
cloudUserInfo,
cloudIsAuthenticated,
sharingEnabled,
Expand Down
15 changes: 15 additions & 0 deletions src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,21 @@
"command": "roo-cline.toggleAutoApprove",
"title": "%command.toggleAutoApprove.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.increaseChatFontSize",
"title": "%command.increaseChatFontSize.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.decreaseChatFontSize",
"title": "%command.decreaseChatFontSize.title%",
"category": "%configuration.title%"
},
{
"command": "roo-cline.resetChatFontSize",
"title": "%command.resetChatFontSize.title%",
"category": "%configuration.title%"
}
],
"menus": {
Expand Down
3 changes: 3 additions & 0 deletions src/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
"command.terminal.explainCommand.title": "Explain This Command",
"command.acceptInput.title": "Accept Input/Suggestion",
"command.toggleAutoApprove.title": "Toggle Auto-Approve",
"command.increaseChatFontSize.title": "Increase Chat Font Size",
"command.decreaseChatFontSize.title": "Decrease Chat Font Size",
"command.resetChatFontSize.title": "Reset Chat Font Size",
"configuration.title": "Roo Code",
"commands.allowedCommands.description": "Commands that can be auto-executed when 'Always approve execute operations' is enabled",
"commands.deniedCommands.description": "Command prefixes that will be automatically denied without asking for approval. In case of conflicts with allowed commands, the longest prefix match takes precedence. Add * to deny all commands.",
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ export type ExtensionState = Pick<
| "includeTaskHistoryInEnhance"
| "reasoningBlockCollapsed"
| "enterBehavior"
| "chatFontSizeMultiplier"
| "includeCurrentTime"
| "includeCurrentCost"
| "maxGitStatusFiles"
Expand Down
4 changes: 3 additions & 1 deletion webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
cloudIsAuthenticated,
messageQueue = [],
isBrowserSessionActive,
chatFontSizeMultiplier,
} = useExtensionState()

const messagesRef = useRef(messages)
Expand Down Expand Up @@ -1393,7 +1394,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
return (
<div
data-testid="chat-view"
className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}>
className={isHidden ? "hidden" : "fixed top-0 left-0 right-0 bottom-0 flex flex-col overflow-hidden"}
style={{ fontSize: `calc(1em * ${chatFontSizeMultiplier ?? 1})` }}>
{telemetrySetting === "unset" && <TelemetryBanner />}
{(showAnnouncement || showAnnouncementModal) && (
<Announcement
Expand Down
2 changes: 2 additions & 0 deletions webview-ui/src/components/settings/SettingsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
openRouterImageGenerationSelectedModel,
reasoningBlockCollapsed,
enterBehavior,
chatFontSizeMultiplier,
includeCurrentTime,
includeCurrentCost,
maxGitStatusFiles,
Expand Down Expand Up @@ -830,6 +831,7 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
<UISettings
reasoningBlockCollapsed={reasoningBlockCollapsed ?? true}
enterBehavior={enterBehavior ?? "send"}
chatFontSizeMultiplier={chatFontSizeMultiplier ?? 1}
setCachedStateField={setCachedStateField}
/>
)}
Expand Down
85 changes: 83 additions & 2 deletions webview-ui/src/components/settings/UISettings.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HTMLAttributes, useMemo } from "react"
import { HTMLAttributes, useMemo, useState, useCallback, useEffect } from "react"
import { useAppTranslation } from "@/i18n/TranslationContext"
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
import { Glasses } from "lucide-react"
import { Glasses, RotateCcw } from "lucide-react"
import { telemetryClient } from "@/utils/TelemetryClient"

import { SetCachedStateField } from "./types"
Expand All @@ -12,17 +12,27 @@ import { ExtensionStateContextType } from "@/context/ExtensionStateContext"
interface UISettingsProps extends HTMLAttributes<HTMLDivElement> {
reasoningBlockCollapsed: boolean
enterBehavior: "send" | "newline"
chatFontSizeMultiplier: number
setCachedStateField: SetCachedStateField<keyof ExtensionStateContextType>
}

export const UISettings = ({
reasoningBlockCollapsed,
enterBehavior,
chatFontSizeMultiplier,
setCachedStateField,
...props
}: UISettingsProps) => {
const { t } = useAppTranslation()

// Local state for the input value to allow typing freely
const [localMultiplier, setLocalMultiplier] = useState(chatFontSizeMultiplier.toString())

// Sync local state when prop changes (e.g., from commands)
useEffect(() => {
setLocalMultiplier(chatFontSizeMultiplier.toString())
}, [chatFontSizeMultiplier])

// Detect platform for dynamic modifier key display
const primaryMod = useMemo(() => {
const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0
Expand All @@ -48,6 +58,45 @@ export const UISettings = ({
})
}

const handleFontSizeMultiplierChange = useCallback(
(value: string) => {
setLocalMultiplier(value)

// Parse and validate the value
const numValue = parseFloat(value)
if (!isNaN(numValue)) {
// Clamp the value between 0.5 and 2
const clampedValue = Math.max(0.5, Math.min(2, numValue))
setCachedStateField("chatFontSizeMultiplier", clampedValue)

// Track telemetry event
telemetryClient.capture("ui_settings_chat_font_size_changed", {
multiplier: clampedValue,
})
}
},
[setCachedStateField],
)

const handleFontSizeMultiplierBlur = useCallback(() => {
// On blur, ensure the display value matches the clamped value
const numValue = parseFloat(localMultiplier)
if (isNaN(numValue)) {
setLocalMultiplier(chatFontSizeMultiplier.toString())
} else {
const clampedValue = Math.max(0.5, Math.min(2, numValue))
setLocalMultiplier(clampedValue.toString())
}
}, [localMultiplier, chatFontSizeMultiplier])

const handleResetFontSize = useCallback(() => {
setCachedStateField("chatFontSizeMultiplier", 1)
setLocalMultiplier("1")

// Track telemetry event
telemetryClient.capture("ui_settings_chat_font_size_reset", {})
}, [setCachedStateField])

return (
<div {...props}>
<SectionHeader>
Expand Down Expand Up @@ -86,6 +135,38 @@ export const UISettings = ({
{t("settings:ui.requireCtrlEnterToSend.description", { primaryMod })}
</div>
</div>

{/* Chat Font Size Multiplier Setting */}
<div className="flex flex-col gap-1">
<label className="font-medium" htmlFor="chat-font-size-input">
{t("settings:ui.chatFontSizeMultiplier.label")}
</label>
<div className="flex items-center gap-2">
<input
id="chat-font-size-input"
type="number"
min="0.5"
max="2"
step="0.1"
value={localMultiplier}
onChange={(e) => handleFontSizeMultiplierChange(e.target.value)}
onBlur={handleFontSizeMultiplierBlur}
className="w-20 px-2 py-1 bg-vscode-input-background text-vscode-input-foreground border border-vscode-input-border rounded"
data-testid="chat-font-size-input"
/>
<button
onClick={handleResetFontSize}
className="flex items-center gap-1 px-2 py-1 text-sm bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground rounded"
title={t("settings:ui.chatFontSizeMultiplier.reset")}
data-testid="chat-font-size-reset-button">
<RotateCcw className="w-3 h-3" />
{t("settings:ui.chatFontSizeMultiplier.reset")}
</button>
</div>
<div className="text-vscode-descriptionForeground text-sm mt-1">
{t("settings:ui.chatFontSizeMultiplier.description")}
</div>
</div>
</div>
</Section>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ describe("UISettings", () => {
const defaultProps = {
reasoningBlockCollapsed: false,
enterBehavior: "send" as const,
chatFontSizeMultiplier: 1,
setCachedStateField: vi.fn(),
}

Expand Down
5 changes: 5 additions & 0 deletions webview-ui/src/context/ExtensionStateContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ export interface ExtensionStateContextType extends ExtensionState {
setReasoningBlockCollapsed: (value: boolean) => void
enterBehavior?: "send" | "newline"
setEnterBehavior: (value: "send" | "newline") => void
chatFontSizeMultiplier?: number
setChatFontSizeMultiplier: (value: number) => void
autoCondenseContext: boolean
setAutoCondenseContext: (value: boolean) => void
autoCondenseContextPercent: number
Expand Down Expand Up @@ -249,6 +251,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
historyPreviewCollapsed: false, // Initialize the new state (default to expanded)
reasoningBlockCollapsed: true, // Default to collapsed
enterBehavior: "send", // Default: Enter sends, Shift+Enter creates newline
chatFontSizeMultiplier: 1, // Default: 1x multiplier (no scaling)
cloudUserInfo: null,
cloudIsAuthenticated: false,
cloudOrganizations: [],
Expand Down Expand Up @@ -570,6 +573,8 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setState((prevState) => ({ ...prevState, reasoningBlockCollapsed: value })),
enterBehavior: state.enterBehavior ?? "send",
setEnterBehavior: (value) => setState((prevState) => ({ ...prevState, enterBehavior: value })),
chatFontSizeMultiplier: state.chatFontSizeMultiplier ?? 1,
setChatFontSizeMultiplier: (value) => setState((prevState) => ({ ...prevState, chatFontSizeMultiplier: value })),
setHasOpenedModeSelector: (value) => setState((prevState) => ({ ...prevState, hasOpenedModeSelector: value })),
setAutoCondenseContext: (value) => setState((prevState) => ({ ...prevState, autoCondenseContext: value })),
setAutoCondenseContextPercent: (value) =>
Expand Down
5 changes: 5 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
"requireCtrlEnterToSend": {
"label": "Require {{primaryMod}}+Enter to send messages",
"description": "When enabled, you must press {{primaryMod}}+Enter to send messages instead of just Enter"
},
"chatFontSizeMultiplier": {
"label": "Chat Font Size Multiplier",
"description": "Adjust the font size in the chat view. Values range from 0.5 (smaller) to 2 (larger). Default is 1.",
"reset": "Reset"
}
},
"prompts": {
Expand Down
Loading