diff --git a/flutter-idea/src/io/flutter/FlutterInitializer.java b/flutter-idea/src/io/flutter/FlutterInitializer.java index 2509fc699..565f0d317 100644 --- a/flutter-idea/src/io/flutter/FlutterInitializer.java +++ b/flutter-idea/src/io/flutter/FlutterInitializer.java @@ -32,6 +32,7 @@ import io.flutter.analytics.UnifiedAnalytics; import io.flutter.android.IntelliJAndroidSdk; import io.flutter.bazel.WorkspaceCache; +import io.flutter.devtools.RemainingDevToolsViewFactory; import io.flutter.editor.FlutterSaveActionsManager; import io.flutter.logging.FlutterConsoleLogManager; import io.flutter.module.FlutterModuleBuilder; @@ -261,6 +262,7 @@ private void initializeToolWindows(@NotNull Project project) { FlutterViewFactory.init(project); FlutterPerformanceViewFactory.init(project); PreviewViewFactory.init(project); + RemainingDevToolsViewFactory.init(project); toolWindowsInitialized = true; } diff --git a/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java b/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java index 2c8abd45d..75ba2dd37 100644 --- a/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java +++ b/flutter-idea/src/io/flutter/devtools/DevToolsUrl.java @@ -18,13 +18,14 @@ public class DevToolsUrl { private String devToolsHost; private int devToolsPort; - private String vmServiceUri; + public String vmServiceUri; private String page; private boolean embed; public String colorHexCode; public Boolean isBright; public String widgetId; public Float fontSize; + public String hide; private final FlutterSdkVersion flutterSdkVersion; private final FlutterSdkUtil sdkUtil; @@ -44,6 +45,7 @@ public static class Builder { private Boolean embed; private String widgetId; private Float fontSize; + private String hide; private FlutterSdkVersion flutterSdkVersion; private WorkspaceCache workspaceCache; @@ -90,6 +92,11 @@ public Builder setFontSize(Float fontSize) { return this; } + public Builder setHide(String hide) { + this.hide = hide; + return this; + } + public Builder setDevToolsUtils(DevToolsUtils devToolsUtils) { this.devToolsUtils = devToolsUtils; return this; @@ -141,6 +148,7 @@ private DevToolsUrl(Builder builder) { this.isBright = builder.devToolsUtils.getIsBackgroundBright(); this.fontSize = builder.devToolsUtils.getFontSize(); } + this.hide = builder.hide; this.widgetId = builder.widgetId; this.flutterSdkVersion = builder.flutterSdkVersion; this.ideFeature = builder.ideFeature; @@ -174,7 +182,18 @@ public String getUrlString() { params.add("theme=" + (isBright ? "light" : "dark")); } if (embed) { - params.add(this.canUseMultiEmbed ? "embedMode=one" : "embed=true"); + if (!this.canUseMultiEmbed) { + // This is for older versions of DevTools that do not support embed= one vs. many. + params.add("embed=true"); + } else { + if (hide != null) { + // If we are using the hide param, we can assume that we are trying to embed multiple tabs. + params.add("embedMode=many"); + params.add("hide=" + hide); + } else { + params.add("embedMode=one"); + } + } } if (fontSize != null) { params.add("fontSize=" + fontSize); diff --git a/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java b/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java new file mode 100644 index 000000000..668b5b897 --- /dev/null +++ b/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright 2024 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.devtools; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.wm.ToolWindow; +import com.intellij.openapi.wm.ToolWindowFactory; +import com.intellij.ui.content.ContentManager; +import io.flutter.FlutterUtils; +import io.flutter.bazel.WorkspaceCache; +import io.flutter.run.daemon.DevToolsService; +import io.flutter.sdk.FlutterSdk; +import io.flutter.sdk.FlutterSdkVersion; +import io.flutter.utils.AsyncUtils; +import io.flutter.view.FlutterViewMessages; +import kotlin.coroutines.Continuation; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Optional; + +public class RemainingDevToolsViewFactory implements ToolWindowFactory { + public static void init(Project project) { + project.getMessageBus().connect().subscribe( + FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (FlutterViewMessages.FlutterDebugNotifier)event -> initView(project, event) + ); + } + + private static void initView(Project project, FlutterViewMessages.FlutterDebugEvent event) { + RemainingDevToolsViewService service = project.getService(RemainingDevToolsViewService.class); + String vmServiceUri = event.app.getConnector().getBrowserUrl(); + if (vmServiceUri == null) return; + service.updateVmServiceUri(vmServiceUri); + } + + + @Override + public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow window) { + final ContentManager contentManager = window.getContentManager(); + FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); + RemainingDevToolsViewService service = project.getService(RemainingDevToolsViewService.class); + + AsyncUtils.whenCompleteUiThread( + DevToolsService.getInstance(project).getDevToolsInstance(), + (instance, error) -> { + // Skip displaying if the project has been closed. + if (!project.isOpen()) { + return; + } + + if (error != null) { + return; + } + + if (instance == null) { + return; + } + + final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder() + .setDevToolsHost(instance.host) + .setDevToolsPort(instance.port) + .setHide("home,inspector,deep-links,extensions") + .setEmbed(true).setFlutterSdkVersion(sdkVersion) + .setWorkspaceCache(WorkspaceCache.getInstance(project)) + .setIdeFeature(DevToolsIdeFeature.TOOL_WINDOW) + .build(); + + ApplicationManager.getApplication().invokeLater(() -> { + Optional.ofNullable( + FlutterUtils.embeddedBrowser(project)) + .ifPresent(embeddedBrowser -> { + service.setEmbeddedBrowser(embeddedBrowser); + embeddedBrowser.openPanel(window, "Flutter DevTools", devToolsUrl, (String err) -> { + System.out.println(err); + }); + }); + }); + } + ); + } + + @Nullable + @Override + public Object isApplicableAsync(@NotNull Project project, @NotNull Continuation $completion) { + FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + FlutterSdkVersion sdkVersion = sdk == null ? null : sdk.getVersion(); + return sdkVersion != null && sdkVersion.canUseDevToolsMultiEmbed(); + } +} diff --git a/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewService.java b/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewService.java new file mode 100644 index 000000000..2f595a29d --- /dev/null +++ b/flutter-idea/src/io/flutter/devtools/RemainingDevToolsViewService.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.devtools; + +import com.intellij.openapi.components.Service; +import com.intellij.openapi.project.Project; +import io.flutter.view.EmbeddedBrowser; +import org.jetbrains.annotations.NotNull; + +@Service(Service.Level.PROJECT) +public final class RemainingDevToolsViewService { + private final Project myProject; + private EmbeddedBrowser embeddedBrowser; + + RemainingDevToolsViewService(Project project) { + this.myProject = project; + } + + public void setEmbeddedBrowser(EmbeddedBrowser embeddedBrowser) { + this.embeddedBrowser = embeddedBrowser; + } + + public void updateVmServiceUri(@NotNull String vmServiceUri) { + if (this.embeddedBrowser == null) return; + this.embeddedBrowser.updateVmServiceUri(vmServiceUri); + } +} diff --git a/flutter-idea/src/io/flutter/view/EmbeddedBrowser.java b/flutter-idea/src/io/flutter/view/EmbeddedBrowser.java index 2f3bb9455..762ac716f 100644 --- a/flutter-idea/src/io/flutter/view/EmbeddedBrowser.java +++ b/flutter-idea/src/io/flutter/view/EmbeddedBrowser.java @@ -53,6 +53,7 @@ public abstract class EmbeddedBrowser { protected final Map<@NotNull String, Map<@NotNull String, @NotNull BrowserTab>> windows = new HashMap<>(); public abstract Logger logger(); + private final Analytics analytics; private DevToolsUrl url; @@ -62,14 +63,15 @@ public EmbeddedBrowser(Project project) { ProjectManager.getInstance().addProjectManagerListener(project, new ProjectManagerListener() { @Override public void projectClosing(@NotNull Project project) { - for (final String window: windows.keySet()) { + for (final String window : windows.keySet()) { final Map tabs = windows.get(window); - for (final String tabName: tabs.keySet()) { + for (final String tabName : tabs.keySet()) { final BrowserTab tab = tabs.get(tabName); if (tab.embeddedTab != null) { try { tab.embeddedTab.close(); - } catch (Exception ex) { + } + catch (Exception ex) { logger().info(ex); } } @@ -89,7 +91,8 @@ public void openPanel(ToolWindow toolWindow, String tabName, DevToolsUrl devTool if (firstTab == null) { try { tabs.put(tabName, openBrowserTabFor(tabName, toolWindow)); - } catch (Exception ex) { + } + catch (Exception ex) { analytics.sendEvent(ANALYTICS_CATEGORY, "openBrowserTabFailed-" + this.getClass()); onBrowserUnavailable.accept(ex.getMessage()); return; @@ -109,7 +112,8 @@ public void openPanel(ToolWindow toolWindow, String tabName, DevToolsUrl devTool try { final String url = devToolsUrl.getUrlString(); tab.embeddedTab.loadUrl(url); - } catch (Exception ex) { + } + catch (Exception ex) { tab.devToolsUrlFuture.completeExceptionally(ex); onBrowserUnavailable.accept(ex.getMessage()); logger().info(ex); @@ -127,7 +131,7 @@ public void openPanel(ToolWindow toolWindow, String tabName, DevToolsUrl devTool tab.contentManager.removeAllContents(false); - for (final String otherTabName: tabs.keySet()) { + for (final String otherTabName : tabs.keySet()) { if (otherTabName.equals(tabName)) { continue; } @@ -149,7 +153,8 @@ private void openLinkInStandardBrowser(ContentManager contentManager) { verifyEventDispatchThread(); if (url == null) { showMessage("The URL is invalid.", contentManager); - } else { + } + else { BrowserLauncher.getInstance().browse( url.getUrlString(), null @@ -160,7 +165,7 @@ private void openLinkInStandardBrowser(ContentManager contentManager) { } protected void verifyEventDispatchThread() { - assert(SwingUtilities.isEventDispatchThread()); + assert (SwingUtilities.isEventDispatchThread()); } protected void showMessageWithUrlLink(@NotNull String message, ContentManager contentManager) { @@ -192,7 +197,8 @@ protected void showLabels(List labels, ContentManager contentManager descriptionLabel.setBorder(JBUI.Borders.empty(5)); descriptionLabel.setHorizontalAlignment(SwingConstants.CENTER); panel.add(descriptionLabel, BorderLayout.NORTH); - } else { + } + else { final LinkLabel linkLabel = new LinkLabel<>("" + input.text + "", null); linkLabel.setBorder(JBUI.Borders.empty(5)); linkLabel.setListener(input.listener, null); @@ -254,6 +260,16 @@ public void updateFontSize(float newFontSize) { }); } + public void updateVmServiceUri(@NotNull String newVmServiceUri) { + updateUrlAndReload(devToolsUrl -> { + if (newVmServiceUri.equals(devToolsUrl.vmServiceUri)) { + return null; + } + devToolsUrl.vmServiceUri = newVmServiceUri; + return devToolsUrl; + }); + } + private void updateUrlAndReload(Function newDevToolsUrlFn) { this.windows.forEach((window, tabs) -> { tabs.forEach((tabName, tab) -> { diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 76188e4a1..22a6176f0 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -640,6 +640,7 @@ + diff --git a/resources/META-INF/plugin_template.xml b/resources/META-INF/plugin_template.xml index 1c542bc11..cd81ccacd 100644 --- a/resources/META-INF/plugin_template.xml +++ b/resources/META-INF/plugin_template.xml @@ -348,6 +348,7 @@ +