From 235112bf7b2197f67757565a0f2c6a691448ace2 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Mon, 18 Feb 2019 16:43:22 -0800 Subject: [PATCH 1/2] add the ability to install and open DevTools --- gen/icons/FlutterIcons.java | 1 + resources/META-INF/plugin.xml | 8 + resources/META-INF/plugin_template.xml | 8 + resources/icons/dart_16.svg | 8 + resources/icons/dart_16_dark.svg | 8 + src/io/flutter/devtools/DevToolsManager.java | 225 ++++++++++++++++++ src/io/flutter/run/FlutterDebugProcess.java | 8 +- src/io/flutter/run/LaunchState.java | 5 +- src/io/flutter/run/OpenDevToolsAction.java | 93 ++++++++ .../run/bazelTest/BazelTestDebugProcess.java | 2 + src/io/flutter/run/daemon/FlutterApp.java | 14 +- src/io/flutter/run/test/TestDebugProcess.java | 2 + src/io/flutter/run/test/TestLaunchState.java | 2 - src/io/flutter/sdk/FlutterCommand.java | 24 +- .../sdk/FlutterCommandStartResultStatus.java | 2 +- src/io/flutter/sdk/FlutterSdk.java | 4 + src/io/flutter/view/FlutterView.java | 99 ++++---- 17 files changed, 422 insertions(+), 91 deletions(-) create mode 100644 resources/icons/dart_16.svg create mode 100644 resources/icons/dart_16_dark.svg create mode 100644 src/io/flutter/devtools/DevToolsManager.java create mode 100644 src/io/flutter/run/OpenDevToolsAction.java diff --git a/gen/icons/FlutterIcons.java b/gen/icons/FlutterIcons.java index 2f093d776a..73f6e98b57 100644 --- a/gen/icons/FlutterIcons.java +++ b/gen/icons/FlutterIcons.java @@ -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"); diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 3e2c7cd5fe..d66a084514 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -744,6 +744,10 @@ + + + @@ -839,6 +843,10 @@ serviceImplementation="io.flutter.perf.FlutterWidgetPerfManager" overrides="false"/> + + diff --git a/resources/META-INF/plugin_template.xml b/resources/META-INF/plugin_template.xml index dfdbb69e0b..a10f360751 100644 --- a/resources/META-INF/plugin_template.xml +++ b/resources/META-INF/plugin_template.xml @@ -176,6 +176,10 @@ + + + @@ -271,6 +275,10 @@ serviceImplementation="io.flutter.perf.FlutterWidgetPerfManager" overrides="false"/> + + diff --git a/resources/icons/dart_16.svg b/resources/icons/dart_16.svg new file mode 100644 index 0000000000..5ce9cbc9f0 --- /dev/null +++ b/resources/icons/dart_16.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/icons/dart_16_dark.svg b/resources/icons/dart_16_dark.svg new file mode 100644 index 0000000000..2d0e595261 --- /dev/null +++ b/resources/icons/dart_16_dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/io/flutter/devtools/DevToolsManager.java b/src/io/flutter/devtools/DevToolsManager.java new file mode 100644 index 0000000000..ceb4753887 --- /dev/null +++ b/src/io/flutter/devtools/DevToolsManager.java @@ -0,0 +1,225 @@ +/* + * 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; + + DevToolsInstance devToolsInstance; + + private DevToolsManager(@NotNull Project project) { + this.project = project; + } + + + public boolean hasInstalledDevTools() { + return installedDevTools; + } + + public CompletableFuture installDevTools() { + final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + if (sdk == null) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(false); + return result; + } + + final List pubRoots = PubRoots.forProject(project); + if (pubRoots.isEmpty()) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(false); + return result; + } + + final CompletableFuture 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) { + result.complete(false); + } + + processHandler = null; + } + + @Override + public void onCancel() { + if (processHandler != null && !processHandler.isProcessTerminated()) { + processHandler.destroyProcess(); + } + } + }); + + 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) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(false); + return; + } + + final List pubRoots = PubRoots.forProject(project); + if (pubRoots.isEmpty()) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(false); + 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; + }); + } +} + +class DevToolsInstance { + public static void startServer( + Project project, + FlutterSdk sdk, + PubRoot pubRoot, + Callback onSuccess, + Callback 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 { + void call(T value); +} diff --git a/src/io/flutter/run/FlutterDebugProcess.java b/src/io/flutter/run/FlutterDebugProcess.java index 5bcf67ccc2..65fef3a1fb 100644 --- a/src/io/flutter/run/FlutterDebugProcess.java +++ b/src/io/flutter/run/FlutterDebugProcess.java @@ -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; @@ -104,7 +103,7 @@ public void registerAdditionalActions(@NotNull final DefaultActionGroup leftTool // Add actions common to the run and debug windows. final Computable isSessionActive = () -> app.isStarted() && getVmConnected() && !getSession().isStopped(); final Computable canReload = () -> app.getLaunchMode().supportsReload() && isSessionActive.compute() && !app.isReloading(); - final Computable observatoryAvailable = () -> isSessionActive.compute() && app.getConnector().getBrowserUrl() != null; + final Computable debugUrlAvailable = () -> isSessionActive.compute() && app.getConnector().getBrowserUrl() != null; if (app.getMode() == RunMode.DEBUG) { topToolbar.addSeparator(); @@ -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. } diff --git a/src/io/flutter/run/LaunchState.java b/src/io/flutter/run/LaunchState.java index 8e3e203856..60c531947c 100644 --- a/src/io/flutter/run/LaunchState.java +++ b/src/io/flutter/run/LaunchState.java @@ -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; @@ -251,10 +250,10 @@ protected ExecutionResult setUpConsoleAndActions(@NotNull FlutterApp app) throws final List 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])); } diff --git a/src/io/flutter/run/OpenDevToolsAction.java b/src/io/flutter/run/OpenDevToolsAction.java new file mode 100644 index 0000000000..ab3891a725 --- /dev/null +++ b/src/io/flutter/run/OpenDevToolsAction.java @@ -0,0 +1,93 @@ +/* + * 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.run; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.util.Computable; +import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; +import icons.FlutterIcons; +import io.flutter.FlutterInitializer; +import io.flutter.devtools.DevToolsManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.concurrent.CompletableFuture; + +public class OpenDevToolsAction extends DumbAwareAction { + private final @Nullable ObservatoryConnector myConnector; + private final Computable myIsApplicable; + + public OpenDevToolsAction() { + myConnector = null; + myIsApplicable = null; + } + + public OpenDevToolsAction(@NotNull final ObservatoryConnector connector, @NotNull final Computable isApplicable) { + super("Open DevTools", "Open Dart DevTools", FlutterIcons.Dart_16); + + myConnector = connector; + myIsApplicable = isApplicable; + } + + @Override + public void update(@NotNull final AnActionEvent e) { + if (myIsApplicable == null) { + e.getPresentation().setEnabled(true); + } + else { + e.getPresentation().setEnabled(myIsApplicable.compute()); + } + } + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + FlutterInitializer.sendAnalyticsAction(this); + + if (event.getProject() == null) { + return; + } + + final DevToolsManager devToolsManager = DevToolsManager.getInstance(event.getProject()); + + + if (myConnector == null) { + if (devToolsManager.hasInstalledDevTools()) { + devToolsManager.openBrowser(); + } + else { + final CompletableFuture result = devToolsManager.installDevTools(); + result.thenAccept(o -> devToolsManager.openBrowser()); + } + } + else { + final String urlString = myConnector.getBrowserUrl(); + if (urlString == null) { + return; + } + + final URL url; + try { + url = new URL(urlString); + } + catch (MalformedURLException e) { + return; + } + + final int port = url.getPort(); + + if (devToolsManager.hasInstalledDevTools()) { + devToolsManager.openBrowserAndConnect(port); + } + else { + final CompletableFuture result = devToolsManager.installDevTools(); + result.thenAccept(o -> devToolsManager.openBrowserAndConnect(port)); + } + } + } +} diff --git a/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java b/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java index 189bb15369..0440c053a1 100644 --- a/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java +++ b/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java @@ -12,6 +12,7 @@ import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.util.DartUrlResolver; import io.flutter.run.FlutterPopFrameAction; +import io.flutter.run.OpenDevToolsAction; import io.flutter.run.OpenObservatoryAction; import io.flutter.server.vmService.DartVmServiceDebugProcess; import org.jetbrains.annotations.NotNull; @@ -39,6 +40,7 @@ public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar, @NotNull DefaultActionGroup settings) { topToolbar.addSeparator(); topToolbar.addAction(new FlutterPopFrameAction()); + topToolbar.addAction(new OpenDevToolsAction(connector, this::isActive)); topToolbar.addAction(new OpenObservatoryAction(connector, this::isActive)); } diff --git a/src/io/flutter/run/daemon/FlutterApp.java b/src/io/flutter/run/daemon/FlutterApp.java index 483a9f7e86..e0f007e1ae 100644 --- a/src/io/flutter/run/daemon/FlutterApp.java +++ b/src/io/flutter/run/daemon/FlutterApp.java @@ -334,8 +334,7 @@ public CompletableFuture performRestartApp(@NotNull Str LocalHistory.getInstance().putSystemLabel(getProject(), "Flutter hot restart"); - final long reloadTimestamp = System.currentTimeMillis(); - maxFileTimestamp = reloadTimestamp; + maxFileTimestamp = System.currentTimeMillis(); changeState(State.RESTARTING); final CompletableFuture future = @@ -358,7 +357,7 @@ public boolean isSameModule(@Nullable final Module other) { } /** - * * @return whether the latest of the version of the file is running. + * @return whether the latest of the version of the file is running. */ public boolean isLatestVersionRunning(VirtualFile file) { return file != null && file.getTimeStamp() <= maxFileTimestamp; @@ -381,8 +380,7 @@ public CompletableFuture performHotReload(boolean pause LocalHistory.getInstance().putSystemLabel(getProject(), "hot reload #" + userReloadCount); - final long reloadTimestamp = System.currentTimeMillis(); - maxFileTimestamp = reloadTimestamp; + maxFileTimestamp = System.currentTimeMillis(); changeState(State.RELOADING); final CompletableFuture future = @@ -433,7 +431,8 @@ public CompletableFuture callServiceExtension(String methodName, Map return whenFlutterIsolateResumed().thenComposeAsync((ignored) -> myDaemonApi.callAppServiceExtension(myAppId, methodName, params) ); - } else { + } + else { return myDaemonApi.callAppServiceExtension(myAppId, methodName, params); } } @@ -526,7 +525,8 @@ public Future shutdownAsync() { final Future stopDone; if (DaemonEvent.AppStarting.LAUNCH_MODE_ATTACH.equals(myLaunchMode)) { stopDone = myDaemonApi.detachApp(appId); - } else { + } + else { stopDone = myDaemonApi.stopApp(appId); } final Stopwatch watch = Stopwatch.createStarted(); diff --git a/src/io/flutter/run/test/TestDebugProcess.java b/src/io/flutter/run/test/TestDebugProcess.java index 9795fbf0d4..41235c9f39 100644 --- a/src/io/flutter/run/test/TestDebugProcess.java +++ b/src/io/flutter/run/test/TestDebugProcess.java @@ -12,6 +12,7 @@ import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.util.DartUrlResolver; import io.flutter.run.FlutterPopFrameAction; +import io.flutter.run.OpenDevToolsAction; import io.flutter.run.OpenObservatoryAction; import io.flutter.server.vmService.DartVmServiceDebugProcess; import org.jetbrains.annotations.NotNull; @@ -39,6 +40,7 @@ public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar, @NotNull DefaultActionGroup settings) { topToolbar.addSeparator(); topToolbar.addAction(new FlutterPopFrameAction()); + topToolbar.addAction(new OpenDevToolsAction(connector, this::isActive)); topToolbar.addAction(new OpenObservatoryAction(connector, this::isActive)); } diff --git a/src/io/flutter/run/test/TestLaunchState.java b/src/io/flutter/run/test/TestLaunchState.java index 51d917f293..7ba152056e 100644 --- a/src/io/flutter/run/test/TestLaunchState.java +++ b/src/io/flutter/run/test/TestLaunchState.java @@ -98,8 +98,6 @@ protected ProcessHandler startProcess() throws ExecutionException { case OK: assert result.processHandler != null; return result.processHandler; - case ANOTHER_RUNNING: - throw new ExecutionException("Flutter instance already running"); case EXCEPTION: assert result.exception != null; throw new ExecutionException(FlutterBundle.message("flutter.command.exception.message" + result.exception.getMessage())); diff --git a/src/io/flutter/sdk/FlutterCommand.java b/src/io/flutter/sdk/FlutterCommand.java index ee857f6adc..4953a536c4 100644 --- a/src/io/flutter/sdk/FlutterCommand.java +++ b/src/io/flutter/sdk/FlutterCommand.java @@ -25,7 +25,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; /** @@ -40,7 +39,7 @@ public class FlutterCommand { @NotNull private final FlutterSdk sdk; - @NotNull + @Nullable private final VirtualFile workDir; @NotNull @@ -52,7 +51,7 @@ public class FlutterCommand { /** * @see FlutterSdk for methods to create specific commands. */ - FlutterCommand(@NotNull FlutterSdk sdk, @NotNull VirtualFile workDir, @NotNull Type type, String... args) { + FlutterCommand(@NotNull FlutterSdk sdk, @Nullable VirtualFile workDir, @NotNull Type type, String... args) { this.sdk = sdk; this.workDir = workDir; this.type = type; @@ -158,13 +157,6 @@ public String toString() { return "FlutterCommand(" + getDisplayCommand() + ")"; } - /** - * The currently running command. - *

- * We only allow one command to run at a time across all IDEA projects. - */ - private static final AtomicReference inProgress = new AtomicReference<>(null); - /** * Starts a process that runs a flutter command, unless one is already running. *

@@ -198,11 +190,6 @@ public OSProcessHandler startProcess(boolean sendAnalytics) { */ @NotNull public FlutterCommandStartResult startProcess(@Nullable Project project) { - // TODO(devoncarew): Many flutter commands can legitimately be run in parallel. - if (!inProgress.compareAndSet(null, this)) { - return new FlutterCommandStartResult(FlutterCommandStartResultStatus.ANOTHER_RUNNING); - } - if (isPubRelatedCommand()) { DartPlugin.setPubActionInProgress(true); } @@ -215,7 +202,6 @@ public FlutterCommandStartResult startProcess(@Nullable Project project) { handler.addProcessListener(new ProcessAdapter() { @Override public void processTerminated(@NotNull final ProcessEvent event) { - inProgress.compareAndSet(FlutterCommand.this, null); if (isPubRelatedCommand()) { DartPlugin.setPubActionInProgress(false); } @@ -225,7 +211,6 @@ public void processTerminated(@NotNull final ProcessEvent event) { return new FlutterCommandStartResult(handler); } catch (ExecutionException e) { - inProgress.compareAndSet(this, null); if (isPubRelatedCommand()) { DartPlugin.setPubActionInProgress(false); } @@ -269,7 +254,9 @@ public GeneralCommandLine createGeneralCommandLine(@Nullable Project project) { } line.setExePath(FileUtil.toSystemDependentName(sdk.getHomePath() + "/bin/" + FlutterSdkUtil.flutterScriptName())); - line.setWorkDirectory(workDir.getPath()); + if (workDir != null) { + line.setWorkDirectory(workDir.getPath()); + } if (!isDoctorCommand()) { line.addParameter("--no-color"); } @@ -292,6 +279,7 @@ enum Type { MAKE_HOST_APP_EDITABLE("Flutter make-host-app-editable", "make-host-app-editable"), PACKAGES_GET("Flutter packages get", "packages", "get"), PACKAGES_UPGRADE("Flutter packages upgrade", "packages", "upgrade"), + PACKAGES_PUB("Flutter packages pub", "packages", "pub"), RUN("Flutter run", "run"), UPGRADE("Flutter upgrade", "upgrade"), VERSION("Flutter version", "--version"), diff --git a/src/io/flutter/sdk/FlutterCommandStartResultStatus.java b/src/io/flutter/sdk/FlutterCommandStartResultStatus.java index 1552a6247c..562edd32fa 100644 --- a/src/io/flutter/sdk/FlutterCommandStartResultStatus.java +++ b/src/io/flutter/sdk/FlutterCommandStartResultStatus.java @@ -6,5 +6,5 @@ package io.flutter.sdk; public enum FlutterCommandStartResultStatus { - OK, ANOTHER_RUNNING, EXCEPTION + OK, EXCEPTION } diff --git a/src/io/flutter/sdk/FlutterSdk.java b/src/io/flutter/sdk/FlutterSdk.java index 6ee1a53a85..8a4bc7893b 100644 --- a/src/io/flutter/sdk/FlutterSdk.java +++ b/src/io/flutter/sdk/FlutterSdk.java @@ -174,6 +174,10 @@ public FlutterCommand flutterPackagesUpgrade(@NotNull PubRoot root) { return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.PACKAGES_UPGRADE); } + public FlutterCommand flutterPackagesPub(@Nullable PubRoot root, String... args) { + return new FlutterCommand(this, root == null ? null : root.getRoot(), FlutterCommand.Type.PACKAGES_PUB, args); + } + public FlutterCommand flutterMakeHostAppEditable(@NotNull PubRoot root) { return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.MAKE_HOST_APP_EDITABLE); } diff --git a/src/io/flutter/view/FlutterView.java b/src/io/flutter/view/FlutterView.java index 1439e4aead..28cc1209b8 100644 --- a/src/io/flutter/view/FlutterView.java +++ b/src/io/flutter/view/FlutterView.java @@ -13,8 +13,6 @@ import com.intellij.ide.plugins.PluginManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.CustomComponentAction; -import com.intellij.openapi.actionSystem.impl.ActionButton; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; @@ -48,6 +46,7 @@ import io.flutter.FlutterBundle; import io.flutter.FlutterInitializer; import io.flutter.FlutterUtils; +import io.flutter.devtools.DevToolsManager; import io.flutter.inspector.InspectorService; import io.flutter.run.daemon.FlutterApp; import io.flutter.run.daemon.FlutterDevice; @@ -57,7 +56,6 @@ import io.flutter.settings.FlutterSettings; import io.flutter.utils.AsyncUtils; import io.flutter.utils.EventStream; -import io.flutter.utils.UIUtils; import io.flutter.utils.VmServiceListenerAdapter; import org.dartlang.vm.service.VmService; import org.dartlang.vm.service.element.Event; @@ -66,6 +64,8 @@ import javax.swing.*; import java.awt.*; +import java.net.MalformedURLException; +import java.net.URL; import java.util.List; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -594,6 +594,42 @@ private void activateToolWindow() { } } +class OpenDevToolsAction extends FlutterViewAction { + OpenDevToolsAction(@NotNull FlutterApp app) { + super(app, "Open DevTools", "Open Dart DevTools", FlutterIcons.Dart_16); + } + + @Override + public void perform(AnActionEvent event) { + if (app.isSessionActive()) { + final String urlString = app.getConnector().getBrowserUrl(); + if (urlString == null) { + return; + } + + final URL url; + try { + url = new URL(urlString); + } + catch (MalformedURLException e) { + return; + } + + final int port = url.getPort(); + + final DevToolsManager devToolsManager = DevToolsManager.getInstance(app.getProject()); + + if (devToolsManager.hasInstalledDevTools()) { + devToolsManager.openBrowserAndConnect(port); + } + else { + final CompletableFuture result = devToolsManager.installDevTools(); + result.thenAccept(o -> devToolsManager.openBrowserAndConnect(port)); + } + } + } +} + class OpenObservatoryAction extends FlutterViewAction { OpenObservatoryAction(@NotNull FlutterApp app) { super(app, FlutterBundle.message("open.observatory.action.text"), FlutterBundle.message("open.observatory.action.description"), @@ -635,7 +671,7 @@ class RepaintRainbowAction extends FlutterViewToggleableAction { class ToggleInspectModeAction extends FlutterViewToggleableAction { ToggleInspectModeAction(@NotNull FlutterApp app) { - super(app, AllIcons.General.LocateHover, ServiceExtensions.toggleSelectWidgetMode); + super(app, AllIcons.General.Locate, ServiceExtensions.toggleSelectWidgetMode); } @Override @@ -643,8 +679,7 @@ protected void perform(AnActionEvent event) { super.perform(event); if (app.isSessionActive()) { - // If toggling inspect mode on, bring all devices to the foreground. - // TODO(jacobr): consider only bringing the device for the currently open inspector TAB. + // If toggling inspect mode on, bring the app's device to the foreground. if (isSelected()) { final FlutterDevice device = app.device(); if (device != null) { @@ -747,59 +782,11 @@ private static DefaultActionGroup createPopupActionGroup(FlutterView view, Flutt group.add(view.registerAction(new AutoHorizontalScrollAction(app, view.shouldAutoHorizontalScroll))); group.add(view.registerAction(new HighlightNodesShownInBothTrees(app, view.highlightNodesShownInBothTrees))); group.addSeparator(); + group.add(view.registerAction(new OpenDevToolsAction(app))); + group.addSeparator(); group.add(view.registerAction(new OpenTimelineViewAction(app))); group.add(view.registerAction(new OpenObservatoryAction(app))); return group; } } - -class ObservatoryActionGroup extends AnAction implements CustomComponentAction { - private final @NotNull FlutterApp app; - private final DefaultActionGroup myActionGroup; - - public ObservatoryActionGroup(@NotNull FlutterView view, @NotNull FlutterApp app) { - super("Observatory actions", null, FlutterIcons.OpenObservatoryGroup); - - this.app = app; - - myActionGroup = createPopupActionGroup(view, app); - } - - @Override - public final void update(AnActionEvent e) { - e.getPresentation().setEnabled(app.isSessionActive()); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final JComponent button = UIUtils.getComponentOfActionEvent(e); - if (button == null) { - return; - } - final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu( - ActionPlaces.UNKNOWN, - myActionGroup); - popupMenu.getComponent().show(button, button.getWidth(), 0); - } - - @NotNull - @Override - public JComponent createCustomComponent(@NotNull Presentation presentation) { - final ActionButton button = new ActionButton( - this, - presentation, - ActionPlaces.UNKNOWN, - ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE - ); - presentation.putClientProperty("button", button); - return button; - } - - private static DefaultActionGroup createPopupActionGroup(FlutterView view, FlutterApp app) { - final DefaultActionGroup group = new DefaultActionGroup(); - group.add(view.registerAction(new OpenTimelineViewAction(app))); - group.add(view.registerAction(new OpenObservatoryAction(app))); - return group; - } -} From f3b02dfba006311f034aecd5886980cc6fb15f89 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 19 Feb 2019 13:23:11 -0800 Subject: [PATCH 2/2] review comments --- src/io/flutter/devtools/DevToolsManager.java | 28 +++++++++++--------- src/io/flutter/view/FlutterView.java | 6 ++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/io/flutter/devtools/DevToolsManager.java b/src/io/flutter/devtools/DevToolsManager.java index ceb4753887..7b8e8d3a68 100644 --- a/src/io/flutter/devtools/DevToolsManager.java +++ b/src/io/flutter/devtools/DevToolsManager.java @@ -41,13 +41,12 @@ public static DevToolsManager getInstance(@NotNull Project project) { private boolean installedDevTools = false; - DevToolsInstance devToolsInstance; + private DevToolsInstance devToolsInstance; private DevToolsManager(@NotNull Project project) { this.project = project; } - public boolean hasInstalledDevTools() { return installedDevTools; } @@ -55,16 +54,12 @@ public boolean hasInstalledDevTools() { public CompletableFuture installDevTools() { final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); if (sdk == null) { - final CompletableFuture result = new CompletableFuture<>(); - result.complete(false); - return result; + return createCompletedFuture(false); } final List pubRoots = PubRoots.forProject(project); if (pubRoots.isEmpty()) { - final CompletableFuture result = new CompletableFuture<>(); - result.complete(false); - return result; + return createCompletedFuture(false); } final CompletableFuture result = new CompletableFuture<>(); @@ -88,7 +83,9 @@ public void run(@NotNull ProgressIndicator indicator) { result.complete(value); } catch (RuntimeException re) { - result.complete(false); + if (!result.isDone()) { + result.complete(false); + } } processHandler = null; @@ -98,6 +95,9 @@ public void run(@NotNull ProgressIndicator indicator) { public void onCancel() { if (processHandler != null && !processHandler.isProcessTerminated()) { processHandler.destroyProcess(); + if (!result.isDone()) { + result.complete(false); + } } } }); @@ -121,15 +121,11 @@ private void openBrowserImpl(int port) { final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); if (sdk == null) { - final CompletableFuture result = new CompletableFuture<>(); - result.complete(false); return; } final List pubRoots = PubRoots.forProject(project); if (pubRoots.isEmpty()) { - final CompletableFuture result = new CompletableFuture<>(); - result.complete(false); return; } @@ -143,6 +139,12 @@ private void openBrowserImpl(int port) { devToolsInstance = null; }); } + + private CompletableFuture createCompletedFuture(boolean value) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(value); + return result; + } } class DevToolsInstance { diff --git a/src/io/flutter/view/FlutterView.java b/src/io/flutter/view/FlutterView.java index 28cc1209b8..3890f6a31d 100644 --- a/src/io/flutter/view/FlutterView.java +++ b/src/io/flutter/view/FlutterView.java @@ -594,8 +594,8 @@ private void activateToolWindow() { } } -class OpenDevToolsAction extends FlutterViewAction { - OpenDevToolsAction(@NotNull FlutterApp app) { +class FlutterViewDevToolsAction extends FlutterViewAction { + FlutterViewDevToolsAction(@NotNull FlutterApp app) { super(app, "Open DevTools", "Open Dart DevTools", FlutterIcons.Dart_16); } @@ -782,7 +782,7 @@ private static DefaultActionGroup createPopupActionGroup(FlutterView view, Flutt group.add(view.registerAction(new AutoHorizontalScrollAction(app, view.shouldAutoHorizontalScroll))); group.add(view.registerAction(new HighlightNodesShownInBothTrees(app, view.highlightNodesShownInBothTrees))); group.addSeparator(); - group.add(view.registerAction(new OpenDevToolsAction(app))); + group.add(view.registerAction(new FlutterViewDevToolsAction(app))); group.addSeparator(); group.add(view.registerAction(new OpenTimelineViewAction(app))); group.add(view.registerAction(new OpenObservatoryAction(app)));