Skip to content
Merged
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
1 change: 1 addition & 0 deletions gen/icons/FlutterIcons.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ private static Icon load(String path) {

public static final Icon OpenObservatory = load("/icons/observatory.png");
public static final Icon OpenObservatoryGroup = load("/icons/observatory_overflow.png");
public static final Icon Dart_16 = load("/icons/dart_16.svg");

public static final Icon OpenTimeline = load("/icons/timeline.png");

Expand Down
8 changes: 8 additions & 0 deletions resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,10 @@
<action id="io.flutter.RestartDaemon" class="io.flutter.actions.RestartFlutterDaemonAction"
text="Restart Flutter Daemon" description="Restart Flutter Daemon" icon="FlutterIcons.Flutter">
</action>

<action id="io.flutter.OpenDevToolsAction" class="io.flutter.run.OpenDevToolsAction"
text="Open DevTools" description="Open Dart DevTools" icon="FlutterIcons.Dart_16">
</action>
</actions>

<extensions defaultExtensionNs="com.intellij">
Expand Down Expand Up @@ -839,6 +843,10 @@
serviceImplementation="io.flutter.perf.FlutterWidgetPerfManager"
overrides="false"/>

<projectService serviceInterface="io.flutter.devtools.DevToolsManager"
serviceImplementation="io.flutter.devtools.DevToolsManager"
overrides="false"/>

<iconProvider implementation="io.flutter.project.FlutterIconProvider" order="first"/>

<library.type implementation="io.flutter.sdk.FlutterPluginLibraryType"/>
Expand Down
8 changes: 8 additions & 0 deletions resources/META-INF/plugin_template.xml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@
<action id="io.flutter.RestartDaemon" class="io.flutter.actions.RestartFlutterDaemonAction"
text="Restart Flutter Daemon" description="Restart Flutter Daemon" icon="FlutterIcons.Flutter">
</action>

<action id="io.flutter.OpenDevToolsAction" class="io.flutter.run.OpenDevToolsAction"
text="Open DevTools" description="Open Dart DevTools" icon="FlutterIcons.Dart_16">
</action>
</actions>

<extensions defaultExtensionNs="com.intellij">
Expand Down Expand Up @@ -271,6 +275,10 @@
serviceImplementation="io.flutter.perf.FlutterWidgetPerfManager"
overrides="false"/>

<projectService serviceInterface="io.flutter.devtools.DevToolsManager"
serviceImplementation="io.flutter.devtools.DevToolsManager"
overrides="false"/>

<iconProvider implementation="io.flutter.project.FlutterIconProvider" order="first"/>

<library.type implementation="io.flutter.sdk.FlutterPluginLibraryType"/>
Expand Down
8 changes: 8 additions & 0 deletions resources/icons/dart_16.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions resources/icons/dart_16_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
227 changes: 227 additions & 0 deletions src/io/flutter/devtools/DevToolsManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* Copyright 2019 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.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.ide.browsers.BrowserLauncher;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import io.flutter.pub.PubRoot;
import io.flutter.pub.PubRoots;
import io.flutter.sdk.FlutterCommand;
import io.flutter.sdk.FlutterSdk;
import io.flutter.utils.JsonUtils;
import org.jetbrains.annotations.NotNull;

import java.util.List;
import java.util.concurrent.CompletableFuture;

/**
* Manage installing and opening DevTools.
*/
public class DevToolsManager {
public static DevToolsManager getInstance(@NotNull Project project) {
return ServiceManager.getService(project, DevToolsManager.class);
}

private final Project project;

private boolean installedDevTools = false;

private DevToolsInstance devToolsInstance;

private DevToolsManager(@NotNull Project project) {
this.project = project;
}

public boolean hasInstalledDevTools() {
return installedDevTools;
}

public CompletableFuture<Boolean> installDevTools() {
final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
if (sdk == null) {
return createCompletedFuture(false);
}

final List<PubRoot> pubRoots = PubRoots.forProject(project);
if (pubRoots.isEmpty()) {
return createCompletedFuture(false);
}

final CompletableFuture<Boolean> result = new CompletableFuture<>();
final FlutterCommand command = sdk.flutterPackagesPub(pubRoots.get(0), "global", "activate", "devtools");

final ProgressManager progressManager = ProgressManager.getInstance();
progressManager.run(new Task.Backgroundable(project, "Installing DevTools...", true) {
OSProcessHandler processHandler;

@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setText(getTitle());

processHandler = command.startInConsole(project);

try {
final boolean value = processHandler.waitFor();
if (value) {
installedDevTools = true;
}
result.complete(value);
}
catch (RuntimeException re) {
if (!result.isDone()) {
result.complete(false);
}
}

processHandler = null;
}

@Override
public void onCancel() {
if (processHandler != null && !processHandler.isProcessTerminated()) {
processHandler.destroyProcess();
if (!result.isDone()) {
result.complete(false);
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Add "result.complete(false);" ? Can't remember if "processHandler.waitFor()" will return false if canceled.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added this (and guarded against completing something that's already completed).

}
});

return result;
}

public void openBrowser() {
openBrowserImpl(-1);
}

public void openBrowserAndConnect(int port) {
openBrowserImpl(port);
}

private void openBrowserImpl(int port) {
if (devToolsInstance != null) {
devToolsInstance.openBrowserAndConnect(port);
return;
}

final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project);
if (sdk == null) {
return;
}

final List<PubRoot> pubRoots = PubRoots.forProject(project);
if (pubRoots.isEmpty()) {
return;
}

// start the server
DevToolsInstance.startServer(project, sdk, pubRoots.get(0), instance -> {
devToolsInstance = instance;

devToolsInstance.openBrowserAndConnect(port);
}, instance -> {
// Listen for closing, null out the devToolsInstance.
devToolsInstance = null;
});
}

private CompletableFuture<Boolean> createCompletedFuture(boolean value) {
final CompletableFuture<Boolean> result = new CompletableFuture<>();
result.complete(value);
return result;
}
}

class DevToolsInstance {
public static void startServer(
Project project,
FlutterSdk sdk,
PubRoot pubRoot,
Callback<DevToolsInstance> onSuccess,
Callback<DevToolsInstance> onClose
) {
final FlutterCommand command = sdk.flutterPackagesPub(pubRoot, "global", "run", "devtools", "--machine", "--port=0");
final OSProcessHandler processHandler = command.startInConsole(project);

if (processHandler == null) {
return;
}

processHandler.addProcessListener(new ProcessAdapter() {
@Override
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
final String text = event.getText().trim();

if (text.startsWith("{") && text.endsWith("}")) {
// {"method":"server.started","params":{"host":"127.0.0.1","port":9100}}

try {
final JsonParser jsonParser = new JsonParser();
final JsonElement element = jsonParser.parse(text);

// params.port
final JsonObject obj = element.getAsJsonObject();
final JsonObject params = obj.getAsJsonObject("params");
final String host = JsonUtils.getStringMember(params, "host");
final int port = JsonUtils.getIntMember(params, "port");

if (port != -1) {
final DevToolsInstance instance = new DevToolsInstance(host, port);
onSuccess.call(instance);
}
else {
processHandler.destroyProcess();
}
}
catch (JsonSyntaxException e) {
processHandler.destroyProcess();
}
}
}

@Override
public void processTerminated(@NotNull ProcessEvent event) {
onClose.call(null);
}
});
}

final String devtoolsHost;
final int devtoolsPort;

DevToolsInstance(String devtoolsHost, int devtoolsPort) {
this.devtoolsHost = devtoolsHost;
this.devtoolsPort = devtoolsPort;
}

public void openBrowserAndConnect(int serviceProtocolPort) {
if (serviceProtocolPort == -1) {
BrowserLauncher.getInstance().browse("http://" + devtoolsHost + ":" + devtoolsPort + "/", null);
}
else {
BrowserLauncher.getInstance().browse(
"http://" + devtoolsHost + ":" + devtoolsPort + "/?port=" + serviceProtocolPort,
null
);
}
}
}

interface Callback<T> {
void call(T value);
}
8 changes: 4 additions & 4 deletions src/io/flutter/run/FlutterDebugProcess.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import io.flutter.run.daemon.RunMode;
import io.flutter.server.vmService.DartVmServiceDebugProcess;
import io.flutter.view.FlutterViewMessages;
import io.flutter.view.OpenFlutterViewAction;
import io.flutter.view.ToolbarComboBoxAction;
import org.dartlang.vm.service.VmService;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -104,7 +103,7 @@ public void registerAdditionalActions(@NotNull final DefaultActionGroup leftTool
// Add actions common to the run and debug windows.
final Computable<Boolean> isSessionActive = () -> app.isStarted() && getVmConnected() && !getSession().isStopped();
final Computable<Boolean> canReload = () -> app.getLaunchMode().supportsReload() && isSessionActive.compute() && !app.isReloading();
final Computable<Boolean> observatoryAvailable = () -> isSessionActive.compute() && app.getConnector().getBrowserUrl() != null;
final Computable<Boolean> debugUrlAvailable = () -> isSessionActive.compute() && app.getConnector().getBrowserUrl() != null;

if (app.getMode() == RunMode.DEBUG) {
topToolbar.addSeparator();
Expand All @@ -115,8 +114,9 @@ public void registerAdditionalActions(@NotNull final DefaultActionGroup leftTool
topToolbar.addAction(new ReloadFlutterApp(app, canReload));
topToolbar.addAction(new RestartFlutterApp(app, canReload));
topToolbar.addSeparator();
topToolbar.addAction(new OpenFlutterViewAction(isSessionActive));
topToolbar.addAction(new OverflowAction(app, observatoryAvailable));
topToolbar.addAction(new OpenDevToolsAction(app.getConnector(), debugUrlAvailable));
topToolbar.addSeparator();
topToolbar.addAction(new OverflowAction(app, debugUrlAvailable));

// Don't call super since we have our own observatory action.
}
Expand Down
5 changes: 2 additions & 3 deletions src/io/flutter/run/LaunchState.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import io.flutter.logging.FlutterLogView;
import io.flutter.run.bazel.BazelRunConfig;
import io.flutter.run.daemon.*;
import io.flutter.view.OpenFlutterViewAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand Down Expand Up @@ -251,10 +250,10 @@ protected ExecutionResult setUpConsoleAndActions(@NotNull FlutterApp app) throws
final List<AnAction> actions = new ArrayList<>(Arrays.asList(
super.createActions(console, app.getProcessHandler(), getEnvironment().getExecutor())));
actions.add(new Separator());
actions.add(new OpenDevToolsAction(app.getConnector(), observatoryAvailable));
actions.add(new Separator());
actions.add(new OpenObservatoryAction(app.getConnector(), observatoryAvailable));
actions.add(new OpenTimelineViewAction(app.getConnector(), observatoryAvailable));
actions.add(new Separator());
actions.add(new OpenFlutterViewAction(() -> !app.getProcessHandler().isProcessTerminated()));

return new DefaultExecutionResult(console, app.getProcessHandler(), actions.toArray(new AnAction[0]));
}
Expand Down
Loading