Skip to content

Commit c101ed9

Browse files
committed
fix: prevent UI freeze when showing save dialogs
- Created safeDialogs utility with non-blocking wrappers for showSaveDialog and showOpenDialog - Updated all save/open dialog calls to use the safe wrappers - Uses setImmediate to defer dialog calls to next event loop iteration - Prevents VSCode UI from freezing on macOS with certain configurations Fixes #6870
1 parent ad0e33e commit c101ed9

File tree

5 files changed

+60
-10
lines changed

5 files changed

+60
-10
lines changed

src/core/config/importExport.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import fs from "fs/promises"
55

66
import * as vscode from "vscode"
77
import { z, ZodError } from "zod"
8+
import { showSaveDialogSafe, showOpenDialogSafe } from "../../utils/safeDialogs"
89

910
import { globalSettingsSchema } from "@roo-code/types"
1011
import { TelemetryService } from "@roo-code/telemetry"
@@ -109,7 +110,7 @@ export async function importSettingsFromPath(
109110
* @returns Promise resolving to import result
110111
*/
111112
export const importSettings = async ({ providerSettingsManager, contextProxy, customModesManager }: ImportOptions) => {
112-
const uris = await vscode.window.showOpenDialog({
113+
const uris = await showOpenDialogSafe({
113114
filters: { JSON: ["json"] },
114115
canSelectMany: false,
115116
})
@@ -143,7 +144,7 @@ export const importSettingsFromFile = async (
143144
}
144145

145146
export const exportSettings = async ({ providerSettingsManager, contextProxy }: ExportOptions) => {
146-
const uri = await vscode.window.showSaveDialog({
147+
const uri = await showSaveDialogSafe({
147148
filters: { JSON: ["json"] },
148149
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Documents", "roo-code-settings.json")),
149150
})

src/core/webview/webviewMessageHandler.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import * as fs from "fs/promises"
55
import pWaitFor from "p-wait-for"
66
import * as vscode from "vscode"
77
import * as yaml from "yaml"
8+
import { showSaveDialogSafe, showOpenDialogSafe } from "../../utils/safeDialogs"
89

910
import {
1011
type Language,
@@ -1791,8 +1792,8 @@ export const webviewMessageHandler = async (
17911792
}
17921793
}
17931794

1794-
// Show save dialog
1795-
const saveUri = await vscode.window.showSaveDialog({
1795+
// Show save dialog using safe wrapper to prevent UI freezing
1796+
const saveUri = await showSaveDialogSafe({
17961797
defaultUri,
17971798
filters: {
17981799
"YAML files": ["yaml", "yml"],
@@ -1866,8 +1867,8 @@ export const webviewMessageHandler = async (
18661867
}
18671868
}
18681869

1869-
// Show file picker to select YAML file
1870-
const fileUri = await vscode.window.showOpenDialog({
1870+
// Show file picker to select YAML file using safe wrapper to prevent UI freezing
1871+
const fileUri = await showOpenDialogSafe({
18711872
canSelectFiles: true,
18721873
canSelectFolders: false,
18731874
canSelectMany: false,

src/integrations/misc/export-markdown.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Anthropic } from "@anthropic-ai/sdk"
22
import os from "os"
33
import * as path from "path"
44
import * as vscode from "vscode"
5+
import { showSaveDialogSafe } from "../../utils/safeDialogs"
56

67
export async function downloadTask(dateTs: number, conversationHistory: Anthropic.MessageParam[]) {
78
// File name
@@ -28,8 +29,8 @@ export async function downloadTask(dateTs: number, conversationHistory: Anthropi
2829
})
2930
.join("---\n\n")
3031

31-
// Prompt user for save location
32-
const saveUri = await vscode.window.showSaveDialog({
32+
// Prompt user for save location using safe wrapper to prevent UI freezing
33+
const saveUri = await showSaveDialogSafe({
3334
filters: { Markdown: ["md"] },
3435
defaultUri: vscode.Uri.file(path.join(os.homedir(), "Downloads", fileName)),
3536
})

src/integrations/misc/image-handler.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as os from "os"
33
import * as vscode from "vscode"
44
import { getWorkspacePath } from "../../utils/path"
55
import { t } from "../../i18n"
6+
import { showSaveDialogSafe } from "../../utils/safeDialogs"
67

78
export async function openImage(dataUri: string, options?: { values?: { action?: string } }) {
89
const matches = dataUri.match(/^data:image\/([a-zA-Z]+);base64,(.+)$/)
@@ -67,8 +68,8 @@ export async function saveImage(dataUri: string) {
6768
const defaultFileName = `mermaid_diagram_${Date.now()}.${format}`
6869
const defaultUri = vscode.Uri.file(path.join(defaultPath, defaultFileName))
6970

70-
// Show save dialog
71-
const saveUri = await vscode.window.showSaveDialog({
71+
// Show save dialog using safe wrapper to prevent UI freezing
72+
const saveUri = await showSaveDialogSafe({
7273
filters: {
7374
Images: [format],
7475
"All Files": ["*"],

src/utils/safeDialogs.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as vscode from "vscode"
2+
3+
/**
4+
* Wraps vscode.window.showSaveDialog in a non-blocking way to prevent UI freezing
5+
* This addresses the issue where the save dialog can cause the entire VSCode UI to freeze
6+
* on certain systems (particularly macOS with specific configurations).
7+
*
8+
* @param options - The save dialog options
9+
* @returns Promise that resolves to the selected URI or undefined if cancelled
10+
*/
11+
export async function showSaveDialogSafe(options: vscode.SaveDialogOptions): Promise<vscode.Uri | undefined> {
12+
// Use setImmediate to defer the dialog call to the next iteration of the event loop
13+
// This prevents the UI thread from being blocked
14+
return new Promise<vscode.Uri | undefined>((resolve) => {
15+
setImmediate(async () => {
16+
try {
17+
const result = await vscode.window.showSaveDialog(options)
18+
resolve(result)
19+
} catch (error) {
20+
console.error("Error showing save dialog:", error)
21+
resolve(undefined)
22+
}
23+
})
24+
})
25+
}
26+
27+
/**
28+
* Wraps vscode.window.showOpenDialog in a non-blocking way to prevent UI freezing
29+
*
30+
* @param options - The open dialog options
31+
* @returns Promise that resolves to the selected URIs or undefined if cancelled
32+
*/
33+
export async function showOpenDialogSafe(options: vscode.OpenDialogOptions): Promise<vscode.Uri[] | undefined> {
34+
// Use setImmediate to defer the dialog call to the next iteration of the event loop
35+
return new Promise<vscode.Uri[] | undefined>((resolve) => {
36+
setImmediate(async () => {
37+
try {
38+
const result = await vscode.window.showOpenDialog(options)
39+
resolve(result)
40+
} catch (error) {
41+
console.error("Error showing open dialog:", error)
42+
resolve(undefined)
43+
}
44+
})
45+
})
46+
}

0 commit comments

Comments
 (0)