diff --git a/flutter-idea/src/io/flutter/devtools/DevToolsUtils.java b/flutter-idea/src/io/flutter/devtools/DevToolsUtils.java index 53ac4f4644..2b9c27904b 100644 --- a/flutter-idea/src/io/flutter/devtools/DevToolsUtils.java +++ b/flutter-idea/src/io/flutter/devtools/DevToolsUtils.java @@ -5,12 +5,32 @@ */ package io.flutter.devtools; +import com.intellij.openapi.application.Application; import com.intellij.openapi.editor.colors.EditorColorsManager; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ColorUtil; import com.intellij.ui.JBColor; import com.intellij.util.ui.UIUtil; +import org.dartlang.vm.service.VmService; +import org.dartlang.vm.service.VmServiceListener; import org.jetbrains.annotations.NotNull; +import org.dartlang.vm.service.element.Event; + +import com.google.gson.JsonObject; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.xdebugger.impl.XSourcePositionImpl; +import io.flutter.run.daemon.FlutterApp; +import io.flutter.utils.JsonUtils; +import org.dartlang.vm.service.element.*; +import org.jetbrains.annotations.Nullable; + +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + public class DevToolsUtils { public static String findWidgetId(String url) { final String searchFor = "inspectorRef="; @@ -23,6 +43,75 @@ public static String findWidgetId(String url) { return null; } + /** + * Register a VM listener that listens for devtools "ToolEvent"s. + * @param app the associated app + */ + public static void registerDevToolsVmServiceListener(@NotNull FlutterApp app) { + // This functionality lived originally in the `FlutterInspectorService` introduced in: + // https://github.com/flutter/flutter-intellij/pull/6881 + // + // It was mistakenly removed in: https://github.com/flutter/flutter-intellij/pull/7867 + // + // TODO(pq): some follow-ups: + // * consider a better long-term home for this utility + // * do we need to de-register? + + VmService vmService = app.getVmService(); + if (vmService == null) return; + + vmService.addVmServiceListener(new VmServiceListener() { + @Override + public void connectionOpened() { } + + @Override + public void received(String streamId, Event event) { + if (streamId != null) { + onVmServiceReceived(app, streamId, event); + } + } + + @Override + public void connectionClosed() { } + }); + } + + private static void onVmServiceReceived(@NotNull FlutterApp app, @NotNull String streamId, @Nullable Event event) { + Application application = ApplicationManager.getApplication(); + if (application == null) return; + + if (streamId.equals("ToolEvent")) { + Optional eventOrNull = Optional.ofNullable(event); + if ("navigate".equals(eventOrNull.map(Event::getExtensionKind).orElse(null))) { + JsonObject json = eventOrNull.map(Event::getExtensionData).map(ExtensionData::getJson).orElse(null); + if (json == null) return; + + String fileUri = JsonUtils.getStringMember(json, "fileUri"); + if (fileUri == null) return; + + String path = null; + try { + path = new URI(fileUri).toURL().getFile(); + } + catch (MalformedURLException | URISyntaxException e) { + // A null path will cause an early return. + } + if (path == null) return; + + VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path); + int line = JsonUtils.getIntMember(json, "line"); + int column = JsonUtils.getIntMember(json, "column"); + + application.invokeLater(() -> { + if (file != null && line >= 0 && column >= 0) { + XSourcePositionImpl position = XSourcePositionImpl.create(file, line - 1, column - 1); + position.createNavigatable(app.getProject()).navigate(false); + } + }); + } + } + } + public String getColorHexCode() { return ColorUtil.toHex(UIUtil.getEditorPaneBackground()); } @@ -37,6 +126,6 @@ public Boolean getIsBackgroundBright() { // Return the default normal font size if editor manager is not found. return UIUtil.getFontSize(UIUtil.FontSize.NORMAL); } - return (float) manager.getGlobalScheme().getEditorFontSize(); + return (float)manager.getGlobalScheme().getEditorFontSize(); } } diff --git a/flutter-idea/src/io/flutter/view/FlutterView.java b/flutter-idea/src/io/flutter/view/FlutterView.java index 9e831ba7bf..a7c5be5662 100644 --- a/flutter-idea/src/io/flutter/view/FlutterView.java +++ b/flutter-idea/src/io/flutter/view/FlutterView.java @@ -33,6 +33,7 @@ import io.flutter.bazel.WorkspaceCache; import io.flutter.devtools.DevToolsIdeFeature; import io.flutter.devtools.DevToolsUrl; +import io.flutter.devtools.DevToolsUtils; import io.flutter.jxbrowser.FailureType; import io.flutter.jxbrowser.InstallationFailedReason; import io.flutter.jxbrowser.JxBrowserManager; @@ -49,6 +50,7 @@ import io.flutter.utils.EventStream; import io.flutter.utils.JxBrowserUtils; import io.flutter.utils.LabelInput; +import org.dartlang.vm.service.VmService; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -161,6 +163,14 @@ private void addBrowserInspectorViewContent(FlutterApp app, FlutterSdk flutterSdk = FlutterSdk.getFlutterSdk(app.getProject()); FlutterSdkVersion flutterSdkVersion = flutterSdk == null ? null : flutterSdk.getVersion(); + + // Register for devtools events (required for inspector->editor source linking) + // See: https://github.com/flutter/flutter-intellij/issues/8041 + VmService vmService = app.getVmService(); + if (vmService != null) { + DevToolsUtils.registerDevToolsVmServiceListener(app); + } + if (isEmbedded) { final DevToolsUrl devToolsUrl = new DevToolsUrl.Builder() .setDevToolsHost(devToolsInstance.host())