Skip to content
7 changes: 2 additions & 5 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -329,9 +329,11 @@
<toolWindow id="Flutter Deep Links" anchor="right" icon="FlutterIcons.DevToolsDeepLinks" factoryClass="io.flutter.deeplinks.DeepLinksViewFactory" />
<toolWindow id="Flutter DevTools" anchor="right" icon="FlutterIcons.DevTools" factoryClass="io.flutter.devtools.RemainingDevToolsViewFactory" />
<toolWindow id="Flutter DevTools Extensions" anchor="right" icon="FlutterIcons.DevToolsExtensions" factoryClass="io.flutter.devtools.DevToolsExtensionsViewFactory" />
<!-- Do not uncomment until ready to release the Property Editor. -->
<toolWindow id="Flutter Property Editor" anchor="right" icon="FlutterIcons.PropertyEditor" factoryClass="io.flutter.propertyeditor.PropertyEditorViewFactory" />

<toolWindow id="Flutter Widget Preview"
anchor="right"
factoryClass="io.flutter.widgetpreview.WidgetPreviewToolWindowFactory"
icon="FlutterIcons.WidgetPreview" />
<!-- Having the projectService defined after the toolWindows allows them to all be picked up by the platform -->
<!-- See https://github.com/flutter/flutter-intellij/issues/8029 -->
<projectService serviceImplementation="io.flutter.view.InspectorView" overrides="false"/>
Expand Down
4 changes: 4 additions & 0 deletions resources/icons/expui/widgetPreview.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions resources/icons/expui/widgetPreview@20x20.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions resources/icons/expui/widgetPreview@20x20_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions resources/icons/expui/widgetPreview_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/icons/FlutterIcons.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ private static Icon load(String path) {
return IconLoader.getIcon(path, FlutterIcons.class);
}

public static final Icon WidgetPreview = load("/icons/expui/widgetPreview.svg");
public static final Icon DevToolsDeepLinks = load("/icons/expui/deepLinks.svg");
public static final Icon DevTools = load("/icons/expui/devTools.svg");
public static final Icon DevToolsExtensions = load("/icons/expui/extensions.svg");
Expand Down
7 changes: 7 additions & 0 deletions src/io/flutter/FlutterBundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,10 @@ icon.preview.disallow.flutter_vector_icons=Package "flutter_vector_icons" cannot
icon.preview.disallow.material_design_icons_flutter=Package "material_design_icons_flutter" always displays blank icon previews.
icon.preview.disallow.package.title=Unsupported icon package
icon.preview.analysis=Checking icons...

widget.preview.initializing=Initializing Flutter Widget Preview...
widget.preview.starting=Starting Flutter Widget Preview web server...
widget.preview.loading=Loading Flutter Widget Preview at {0}...
widget.preview.error=Error: {0}
widget.preview.sdk.too.old=Flutter SDK version is too old for Flutter Widget Preview. Please update your SDK.
flutter.sdk.not.found=Flutter SDK not found.
3 changes: 2 additions & 1 deletion src/io/flutter/devtools/AbstractDevToolsViewFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.flutter.sdk.FlutterSdkVersion;
import io.flutter.utils.AsyncUtils;
import io.flutter.utils.OpenApiUtils;
import io.flutter.view.DevToolsUrlProvider;
import io.flutter.view.EmbeddedBrowser;
import io.flutter.view.ViewUtils;
import kotlin.coroutines.Continuation;
Expand Down Expand Up @@ -156,7 +157,7 @@ private void loadDevToolsInEmbeddedBrowser(@NotNull Project project,
FlutterUtils.embeddedBrowser(project))
.ifPresent(embeddedBrowser ->
{
embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), getToolWindowIcon(), devToolsUrl, System.out::println,
embeddedBrowser.openPanel(toolWindow, getToolWindowTitle(), getToolWindowIcon(), new DevToolsUrlProvider(devToolsUrl), System.out::println,
warningMessage);
devToolsLoadedInBrowser = true;
doAfterBrowserOpened(project, embeddedBrowser);
Expand Down
5 changes: 3 additions & 2 deletions src/io/flutter/devtools/DevToolsUrl.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,14 @@ public String getUrlString() {
return "http://" + devToolsHost + ":" + devToolsPort + "/" + (page != null ? page : "") + "?" + String.join("&", params);
}

public void maybeUpdateColor() {
public boolean maybeUpdateColor() {
final String newColor = devToolsUtils.getColorHexCode();
if (Objects.equals(colorHexCode, newColor)) {
return;
return false;
}

colorHexCode = newColor;
isBright = devToolsUtils.getIsBackgroundBright();
return true;
}
}
4 changes: 2 additions & 2 deletions src/io/flutter/jxbrowser/EmbeddedJxBrowser.java
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ private EmbeddedJxBrowser(@NotNull Project project) {
};
}

private void manageJxBrowserDownload(ContentManager contentManager) {
private void manageJxBrowserDownload(@NotNull ContentManager contentManager) {
final JxBrowserStatus jxBrowserStatus = jxBrowserManager.getStatus();

if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLED)) {
Expand Down Expand Up @@ -289,7 +289,7 @@ else if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_FAILED)) {
}
}

protected void handleJxBrowserInstallationFailed(ContentManager contentManager) {
protected void handleJxBrowserInstallationFailed(@NotNull ContentManager contentManager) {
final List<LabelInput> inputs = new ArrayList<>();

final InstallationFailedReason latestFailureReason = jxBrowserManager.getLatestFailureReason();
Expand Down
2 changes: 1 addition & 1 deletion src/io/flutter/logging/FlutterConsoleLogManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ public void actionPerformed(@NotNull AnActionEvent event) {
Project project = app.getProject();
if (!project.isDisposed()) {
final EmbeddedBrowser browser = FlutterUtils.embeddedBrowser(project);
if (browser != null) {
if (browser != null && widgetId != null) {
browser.updatePanelToWidget(widgetId);
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/io/flutter/sdk/FlutterCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ protected boolean isPubRelatedCommand() {
return pubRelatedCommands.contains(type);
}

/**
* Starts running the command and returns the raw `Process` object.
*
* <p>This is intended for commands that need direct stream access and do not need console integration.
*
* @return the started `Process`, or null if an exception occurred.
*/
@Nullable
public Process start() {
final GeneralCommandLine commandLine = createGeneralCommandLine(null);
final String commandText = safeCommandLog(commandLine);
LOG.info(commandText);
try {
return commandLine.createProcess();
}
catch (ExecutionException e) {
LOG.error("Failed to start Flutter command: " + commandText, e);
return null;
}
}

/**
* Starts running the command, without showing its output in a console.
* <p>
Expand Down Expand Up @@ -307,6 +328,7 @@ enum Type {
RUN("Flutter run", "run"),
UPGRADE("Flutter upgrade", "upgrade"),
VERSION("Flutter version", "--version"),
WIDGET_PREVIEW("Flutter widget preview", "widget-preview"),
TEST("Flutter test", "test");

final public String title;
Expand Down
13 changes: 13 additions & 0 deletions src/io/flutter/sdk/FlutterSdk.java
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,19 @@ public FlutterCommand flutterTest(@NotNull PubRoot root, @NotNull VirtualFile fi
return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.TEST, args.toArray(new String[]{ }));
}

@NotNull
public FlutterCommand widgetPreview(@NotNull PubRoot root, boolean isVerboseMode) {
final List<String> args = new ArrayList<>();
args.add("start");
args.add("--web-server");
args.add("--machine");
Copy link

Choose a reason for hiding this comment

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

We should also pass --dtd-url=<url> here and, if the plugin hosts a DevTools instance, --devtools-server-address=<url>.

Does the Intellij plugin provide the DTD Editor service?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah okay, both should be available. How does the the widget preview interact with DevTools?

IntelliJ is intended to serve as an Editor, at least if we're thinking of the same thing (https://github.com/dart-lang/sdk/blob/bc7e4d9fb583c9edeb90393f65989767e865e56f/pkg/dtd_impl/dtd_common_services_editor.md?plain=1#L4), but looking at the code now I'm not sure if this was implemented.

Copy link

Choose a reason for hiding this comment

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

How does the the widget preview interact with DevTools?

For the current beta, it doesn't directly interact with DevTools and passing this flag just prevents Flutter Tools from serving another DevTools instance. In the near future, this DevTools instance will be embedded in the widget previewer in an iframe which displays the widget inspector.

IntelliJ is intended to serve as an Editor, at least if we're thinking of the same thing (https://github.com/dart-lang/sdk/blob/bc7e4d9fb583c9edeb90393f65989767e865e56f/pkg/dtd_impl/dtd_common_services_editor.md?plain=1#L4), but looking at the code now I'm not sure if this was implemented.

Ah, it'd be great if this was implemented. The widget previewer can provide additional functionality if this service is available, including filtering previews based on the currently selected file and jumping to source from file URIs in stack traces displayed for previews which throw exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

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

@bkonyi could you file an issue to this repo about implementing the editor services and what functionality will be available for each service that's relevant, maybe with examples from VS Code if you know of them?

if (isVerboseMode) {
args.add("--verbose");
}

return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.WIDGET_PREVIEW, args.toArray(new String[]{}));
}

/**
* Runs flutter create and waits for it to finish.
* <p>
Expand Down
7 changes: 7 additions & 0 deletions src/io/flutter/sdk/FlutterSdkVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ public final class FlutterSdkVersion implements Comparable<FlutterSdkVersion> {
@NotNull
public static final FlutterSdkVersion MIN_SUPPORTS_PROPERTY_EDITOR = new FlutterSdkVersion("3.32.0-0.1.pre");

@NotNull
public static final FlutterSdkVersion MIN_SUPPORTS_WIDGET_PREVIEW = new FlutterSdkVersion("3.38.0-0.0.pre");

@NotNull
public static final String UNKNOWN_VERSION = "unknown version";

Expand Down Expand Up @@ -170,6 +173,10 @@ public boolean canUsePropertyEditor() {
return supportsVersion(MIN_SUPPORTS_PROPERTY_EDITOR);
}

public boolean canUseWidgetPreview() {
return supportsVersion(MIN_SUPPORTS_WIDGET_PREVIEW);
}

public boolean canUseDevToolsMultiEmbed() {
return supportsVersion(MIN_SUPPORTS_DEVTOOLS_MULTI_EMBED);
}
Expand Down
21 changes: 21 additions & 0 deletions src/io/flutter/view/BrowserUrlProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2025 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.view;

import org.jetbrains.annotations.NotNull;

/**
* Saves a browser URL that may be editable depending on additional settings from the IDE or running applications.
*/
public interface BrowserUrlProvider {
void setWidgetId(@NotNull String widgetId);

@NotNull String getBrowserUrl();

boolean maybeUpdateColor();

boolean setVmServiceUri(@NotNull String vmServiceUri);
}
35 changes: 35 additions & 0 deletions src/io/flutter/view/DevToolsUrlProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2025 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.view;

import io.flutter.devtools.DevToolsUrl;
import org.jetbrains.annotations.NotNull;

public record DevToolsUrlProvider(@NotNull DevToolsUrl url) implements BrowserUrlProvider {
@Override
public void setWidgetId(@NotNull String widgetId) {
url.widgetId = widgetId;
}

@Override
public @NotNull String getBrowserUrl() {
return url.getUrlString();
}

@Override
public boolean maybeUpdateColor() {
return url.maybeUpdateColor();
}

@Override
public boolean setVmServiceUri(@NotNull String newVmServiceUri) {
if (url.vmServiceUri != null && url.vmServiceUri.equals(newVmServiceUri)) {
return false;
}
url.vmServiceUri = newVmServiceUri;
return true;
}
}
Loading