From 6799cf9b2a2e806fc0fffac22df9687b3401e36a Mon Sep 17 00:00:00 2001 From: Helin Shiah Date: Mon, 3 Nov 2025 13:07:44 -0800 Subject: [PATCH 1/4] Pass DTD URI and DevTools URI when available --- src/io/flutter/sdk/FlutterSdk.java | 8 ++- .../widgetpreview/WidgetPreviewPanel.java | 70 +++++++++++++++---- 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/src/io/flutter/sdk/FlutterSdk.java b/src/io/flutter/sdk/FlutterSdk.java index a671f1265..5f85e68f6 100644 --- a/src/io/flutter/sdk/FlutterSdk.java +++ b/src/io/flutter/sdk/FlutterSdk.java @@ -416,7 +416,7 @@ public FlutterCommand flutterTest(@NotNull PubRoot root, @NotNull VirtualFile fi } @NotNull - public FlutterCommand widgetPreview(@NotNull PubRoot root, boolean isVerboseMode) { + public FlutterCommand widgetPreview(@NotNull PubRoot root, boolean isVerboseMode, @Nullable String dtdUri, @Nullable String devToolsUri) { final List args = new ArrayList<>(); args.add("start"); args.add("--web-server"); @@ -424,6 +424,12 @@ public FlutterCommand widgetPreview(@NotNull PubRoot root, boolean isVerboseMode if (isVerboseMode) { args.add("--verbose"); } + if (dtdUri != null) { + args.add("--dtd-url=" + dtdUri); + } + if (devToolsUri != null) { + args.add("--devtools-server-address=" + devToolsUri); + } return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.WIDGET_PREVIEW, args.toArray(new String[]{})); } diff --git a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java index 89052b87f..230f89c54 100644 --- a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java +++ b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java @@ -13,12 +13,18 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.ToolWindow; import com.intellij.util.messages.MessageBusConnection; +import com.jetbrains.lang.dart.ide.devtools.DartDevToolsService; +import com.jetbrains.lang.dart.ide.toolingDaemon.DartToolingDaemonService; import icons.FlutterIcons; import io.flutter.FlutterBundle; import io.flutter.FlutterUtils; +import io.flutter.dart.DtdUtils; +import io.flutter.devtools.DevToolsUrl; import io.flutter.devtools.DevToolsUtils; import io.flutter.logging.PluginLogger; import io.flutter.pub.PubRoot; +import io.flutter.run.daemon.DevToolsInstance; +import io.flutter.run.daemon.DevToolsService; import io.flutter.sdk.FlutterCommand; import io.flutter.sdk.FlutterSdk; import io.flutter.sdk.FlutterSdkVersion; @@ -37,6 +43,8 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; @@ -67,7 +75,7 @@ public WidgetPreviewPanel(@NotNull Project project, @NotNull ToolWindow toolWind } private void startWidgetPreview() { - ApplicationManager.getApplication().executeOnPooledThread(() -> { + OpenApiUtils.safeExecuteOnPooledThread(() -> { try { // Check versioning of Flutter SDK. FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); @@ -102,7 +110,9 @@ private void startWidgetPreview() { } boolean isVerboseMode = FlutterSettings.getInstance().isVerboseLogging(); - final FlutterCommand command = sdk.widgetPreview(root, isVerboseMode); + final String dtdUri = getDtdUri(); + final String devToolsUri = getDevToolsUri(); + final FlutterCommand command = sdk.widgetPreview(root, isVerboseMode, dtdUri, devToolsUri); LOG.info(command.getDisplayCommand()); final ProcessHandler handler = new MostlySilentColoredProcessHandler(command.createGeneralCommandLine(project)); @@ -120,11 +130,48 @@ private void startWidgetPreview() { }); } + private @Nullable String getDevToolsUri() { + try { + final CompletableFuture devToolsFuture = DevToolsService.getInstance(project).getDevToolsInstance(); + if (devToolsFuture == null) { + LOG.error("DevTools future is null."); + return null; + } + + final DevToolsInstance instance = devToolsFuture.get(30, TimeUnit.SECONDS); + if (instance == null) { + LOG.error("DevTools instance is null."); + return null; + } + return new DevToolsUrl.Builder().setDevToolsHost(instance.host()).setDevToolsPort(instance.port()).build().getUrlString(); + } + catch (InterruptedException | java.util.concurrent.ExecutionException | TimeoutException e) { + LOG.error("DevTools service failed: ", e); + } + return null; + } + + private @Nullable String getDtdUri() { + try { + final DartToolingDaemonService dtd = new DtdUtils().readyDtdService(project).get(30, TimeUnit.SECONDS); + if (dtd == null) { + LOG.error("DTD service is null."); + return null; + } + + return dtd.getUri(); + } + catch (TimeoutException | java.util.concurrent.ExecutionException | InterruptedException e) { + LOG.error("DTD service is not available after 30 seconds.", e); + } + return null; + } + // This is intended for the first time we load the panel - save the URL and listen for changes. private void setUrlAndLoad(@NotNull String url) { this.urlProvider = new WidgetPreviewUrlProvider(url, new DevToolsUtils().getIsBackgroundBright()); loadUrl(urlProvider); - listenForReload(); + //listenForReload(); } private void loadUrl(@NotNull BrowserUrlProvider urlProvider) { @@ -144,17 +191,14 @@ private void loadUrl(@NotNull BrowserUrlProvider urlProvider) { private void listenForReload() { MessageBusConnection connection = project.getMessageBus().connect(); - connection.subscribe(EditorColorsManager.TOPIC, new EditorColorsListener() { - @Override - public void globalSchemeChange(@Nullable EditorColorsScheme scheme) { - if (urlProvider == null) { - return; - } + connection.subscribe(EditorColorsManager.TOPIC, (EditorColorsListener)scheme -> { + if (urlProvider == null) { + return; + } - final boolean changed = urlProvider.maybeUpdateColor(); - if (changed) { - loadUrl(urlProvider); - } + final boolean changed = urlProvider.maybeUpdateColor(); + if (changed) { + loadUrl(urlProvider); } }); Disposer.register(toolWindow.getDisposable(), connection); From 5c00ff3d9b8d8a4522f43c66d69375903338f7f1 Mon Sep 17 00:00:00 2001 From: Helin Shiah Date: Mon, 3 Nov 2025 13:10:24 -0800 Subject: [PATCH 2/4] Keep listening for reload --- src/io/flutter/widgetpreview/WidgetPreviewPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java index 230f89c54..d91b33422 100644 --- a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java +++ b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java @@ -171,7 +171,7 @@ private void startWidgetPreview() { private void setUrlAndLoad(@NotNull String url) { this.urlProvider = new WidgetPreviewUrlProvider(url, new DevToolsUtils().getIsBackgroundBright()); loadUrl(urlProvider); - //listenForReload(); + listenForReload(); } private void loadUrl(@NotNull BrowserUrlProvider urlProvider) { From 5c0ebec6950d233d49b4d770ed5847618aeba087 Mon Sep 17 00:00:00 2001 From: Helin Shiah Date: Mon, 3 Nov 2025 13:12:29 -0800 Subject: [PATCH 3/4] Organize imports --- src/io/flutter/widgetpreview/WidgetPreviewPanel.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java index d91b33422..36ced2023 100644 --- a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java +++ b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java @@ -7,13 +7,11 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.colors.EditorColorsListener; import com.intellij.openapi.editor.colors.EditorColorsManager; -import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.SimpleToolWindowPanel; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.wm.ToolWindow; import com.intellij.util.messages.MessageBusConnection; -import com.jetbrains.lang.dart.ide.devtools.DartDevToolsService; import com.jetbrains.lang.dart.ide.toolingDaemon.DartToolingDaemonService; import icons.FlutterIcons; import io.flutter.FlutterBundle; @@ -33,8 +31,8 @@ import io.flutter.utils.OpenApiUtils; import io.flutter.view.BrowserUrlProvider; import io.flutter.view.EmbeddedTab; -import io.flutter.view.WidgetPreviewUrlProvider; import io.flutter.view.ViewUtils; +import io.flutter.view.WidgetPreviewUrlProvider; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; From 94bc27a328d10946cc8fa7dae4fc9c8646a4cbc1 Mon Sep 17 00:00:00 2001 From: Helin Shiah Date: Mon, 3 Nov 2025 13:25:06 -0800 Subject: [PATCH 4/4] Add todo about theme changes --- src/io/flutter/widgetpreview/WidgetPreviewPanel.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java index 36ced2023..ba8cc14ca 100644 --- a/src/io/flutter/widgetpreview/WidgetPreviewPanel.java +++ b/src/io/flutter/widgetpreview/WidgetPreviewPanel.java @@ -187,6 +187,8 @@ private void loadUrl(@NotNull BrowserUrlProvider urlProvider) { }); } + // TODO(https://github.com/flutter/flutter/issues/177945): Ideally widget preview would change colors based on theme changes events, + // which we already send for the DevTools panels. If this is implemented then we can remove this listening code. private void listenForReload() { MessageBusConnection connection = project.getMessageBus().connect(); connection.subscribe(EditorColorsManager.TOPIC, (EditorColorsListener)scheme -> {