From 1c1389fd10030487fa9802badcb64ca4aeb84688 Mon Sep 17 00:00:00 2001 From: Jaime Wren Date: Tue, 17 Dec 2024 14:01:03 -0800 Subject: [PATCH] Remove the now dead code for the Swing Inspector view (#7867) This resolves https://github.com/flutter/flutter-intellij/issues/7861 --- .../src/io/flutter/editor/ColorField.java | 228 --- .../flutter/editor/PropertyEditorPanel.java | 837 ---------- .../io/flutter/hotui/StableWidgetTracker.java | 163 -- .../inspector/DiagnosticsPathNode.java | 57 - .../io/flutter/inspector/EvalException.java | 26 - .../flutter/inspector/EvalOnDartLibrary.java | 293 ---- .../flutter/inspector/InspectorActions.java | 13 - .../InspectorGroupManagerService.java | 325 ---- .../InspectorObjectGroupManager.java | 111 -- .../flutter/inspector/InspectorService.java | 1468 ----------------- .../inspector/InspectorSourceLocation.java | 74 - .../src/io/flutter/inspector/Screenshot.java | 18 - .../src/io/flutter/inspector/TreeUtils.java | 18 - .../DiagnosticLevel.java | 4 +- .../DiagnosticsNode.java | 201 +-- .../DiagnosticsTreeStyle.java | 4 +- .../logging/FlutterConsoleLogManager.java | 43 +- .../flutter/logging/FlutterErrorHelper.java | 2 - .../InspectorInstanceRef.java | 4 +- .../TransformedRect.java | 4 +- .../src/io/flutter/preview/PreviewView.java | 0 .../io/flutter/preview/WidgetEditToolbar.java | 338 ---- .../io/flutter/run/FlutterDebugProcess.java | 11 - .../src/io/flutter/view/FlutterView.java | 106 +- .../flutter/vmService/VMServiceManager.java | 25 - .../unit/io/flutter/view/FlutterViewTest.java | 14 +- gradle.properties | 14 +- 27 files changed, 62 insertions(+), 4339 deletions(-) delete mode 100644 flutter-idea/src/io/flutter/editor/ColorField.java delete mode 100644 flutter-idea/src/io/flutter/editor/PropertyEditorPanel.java delete mode 100644 flutter-idea/src/io/flutter/hotui/StableWidgetTracker.java delete mode 100644 flutter-idea/src/io/flutter/inspector/DiagnosticsPathNode.java delete mode 100644 flutter-idea/src/io/flutter/inspector/EvalException.java delete mode 100644 flutter-idea/src/io/flutter/inspector/EvalOnDartLibrary.java delete mode 100644 flutter-idea/src/io/flutter/inspector/InspectorActions.java delete mode 100644 flutter-idea/src/io/flutter/inspector/InspectorGroupManagerService.java delete mode 100644 flutter-idea/src/io/flutter/inspector/InspectorObjectGroupManager.java delete mode 100644 flutter-idea/src/io/flutter/inspector/InspectorService.java delete mode 100644 flutter-idea/src/io/flutter/inspector/InspectorSourceLocation.java delete mode 100644 flutter-idea/src/io/flutter/inspector/Screenshot.java delete mode 100644 flutter-idea/src/io/flutter/inspector/TreeUtils.java rename flutter-idea/src/io/flutter/{inspector => logging}/DiagnosticLevel.java (97%) rename flutter-idea/src/io/flutter/{inspector => logging}/DiagnosticsNode.java (73%) rename flutter-idea/src/io/flutter/{inspector => logging}/DiagnosticsTreeStyle.java (96%) rename flutter-idea/src/io/flutter/{inspector => logging}/InspectorInstanceRef.java (92%) rename flutter-idea/src/io/flutter/{inspector => logging}/TransformedRect.java (87%) create mode 100644 flutter-idea/src/io/flutter/preview/PreviewView.java delete mode 100644 flutter-idea/src/io/flutter/preview/WidgetEditToolbar.java diff --git a/flutter-idea/src/io/flutter/editor/ColorField.java b/flutter-idea/src/io/flutter/editor/ColorField.java deleted file mode 100644 index c8b49328da..0000000000 --- a/flutter-idea/src/io/flutter/editor/ColorField.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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.editor; - -import com.intellij.icons.AllIcons; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CustomShortcutSet; -import com.intellij.openapi.project.DumbAwareAction; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.ui.components.JBTextField; -import com.intellij.ui.components.fields.ExtendableTextField; -import io.flutter.inspector.InspectorObjectGroupManager; -import io.flutter.inspector.InspectorService; -import io.flutter.utils.ColorIconMaker; -import org.dartlang.analysis.server.protocol.FlutterOutline; -import org.dartlang.analysis.server.protocol.FlutterWidgetProperty; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.util.concurrent.CompletableFuture; - -/** - * Field that displays a color property including a clickable color icon that - * opens a color picker. - */ -class ColorField extends ExtendableTextField { - private final String originalExpression; - private final String name; - private final Extension setColorExtension; - @Nullable private Color currentColor; - private ColorPickerProvider colorPicker; - private final PropertyEditorPanel panel; - private Color colorAtPopupLaunch; - private String expressionAtPopupLaunch; - - public ColorField(PropertyEditorPanel panel, String name, FlutterWidgetProperty property, Disposable parentDisposable) { - super("", 1); - - this.name = name; - - final String expression = property.getExpression(); - if (expression != null) { - setText(expression); - } - this.originalExpression = expression; - currentColor = parseColorExpression(expression); - - final ColorIconMaker maker = new ColorIconMaker(); - - // InputEvent.SHIFT_DOWN_MASK ? - final KeyStroke keyStroke = KeyStroke.getKeyStroke(10, 64); - setColorExtension = - new Extension() { - @Override - public boolean isIconBeforeText() { - return true; - } - - public Icon getIcon(boolean hovered) { - if (currentColor == null) { - return AllIcons.Actions.Colors; - } - return maker.getCustomIcon(currentColor); - } - - public String getTooltip() { - return "Edit color"; - } - - public Runnable getActionOnClick() { - return () -> showColorFieldPopup(); - } - }; - (new DumbAwareAction() { - public void actionPerformed(@NotNull AnActionEvent e) { - showColorFieldPopup(); - } - }).registerCustomShortcutSet(new CustomShortcutSet(keyStroke), this, parentDisposable); - - addExtension(setColorExtension); - panel.addTextFieldListeners(name, this); - - this.panel = panel; - } - - @Nullable - private static Color parseColorExpression(String expression) { - if (expression == null) return null; - - final String colorsPrefix = "Colors."; - if (expression.startsWith(colorsPrefix)) { - final FlutterColors.FlutterColor flutterColor = FlutterColors.getColor(expression.substring(colorsPrefix.length())); - if (flutterColor != null) { - return flutterColor.getAWTColor(); - } - } - return ExpressionParsingUtils.parseColor(expression); - } - - private static String buildColorExpression(@Nullable Color color) { - if (color == null) { - return ""; - } - - final String flutterColorName = FlutterColors.getColorName(color); - if (flutterColorName != null) { - // TODO(jacobr): only apply this conversion if the material library is already imported in the - // library being edited. We also need to be able to handle cases where the material library is - // imported with a prefix. - return "Colors." + flutterColorName; - } - - return String.format( - "Color(0x%02x%02x%02x%02x)", color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue()); - } - - public void addTextFieldListeners(String name, JBTextField field) { - final FlutterOutline matchingOutline = panel.getCurrentOutline(); - field.addActionListener(e -> panel.setPropertyValue(name, field.getText())); - field.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - // The popup is gone if we have received the focus again. - disposeColorPicker(); - } - - @Override - public void focusLost(FocusEvent e) { - if (panel.getCurrentOutline() != matchingOutline) { - // Don't do anything. The user has moved on to a different outline node. - return; - } - if (e.isTemporary()) { - return; - } - - if (colorPicker != null) { - // We lost focus due to the popup being opened so there is no need to - // update the value now. The popup will update the value when it is - // closed. - return; - } - panel.setPropertyValue(name, field.getText()); - } - }); - } - - void cancelPopup() { - currentColor = colorAtPopupLaunch; - setText(expressionAtPopupLaunch); - panel.setPropertyValue(name, originalExpression, true); - repaint(); - colorPicker.dispose(); - colorPicker = null; - } - - void disposeColorPicker() { - if (colorPicker != null) { - colorPicker.dispose(); - colorPicker = null; - } - } - - void showColorFieldPopup() { - disposeColorPicker(); - assert (colorPicker == null); - colorPicker = ColorPickerProvider.EP_NAME.getExtensionList().get(0); - if (colorPicker != null) { - colorAtPopupLaunch = currentColor; - final Insets insets = this.getInsets(); - - final Point bottomColorIconOffset = - new Point(insets.left + setColorExtension.getIconGap(), - this.getHeight() / 2); - colorPicker - .show(currentColor, this, bottomColorIconOffset, Balloon.Position.atLeft, this::colorListener, this::cancelPopup, this::applyColor); - expressionAtPopupLaunch = getText(); - } - } - - private void colorListener(@Nullable Color color, Object o) { - if (colorPicker == null) { - // This can happen after a call to cancel. - return; - } - currentColor = color; - final InspectorObjectGroupManager groupManager = panel.getGroupManager(); - final String colorExpression = buildColorExpression(color); - - // TODO(jacobr): colorField may no longer be the right field in the UI. - setText(colorExpression); - repaint(); - - if (panel.getNode() != null && groupManager != null) { - // TODO(jacobr): rate limit setting the color property if there is a performance issue. - final InspectorService.ObjectGroup group = groupManager.getCurrent(); - final CompletableFuture valueFuture = groupManager.getCurrent().setColorProperty(panel.getNode(), color); - group.safeWhenComplete(valueFuture, (success, error) -> { - if (success == null || error != null) { - return; - } - // TODO(jacobr): - // If setting the property immediately failed, we may have to set the property value fully to see a result. - // This code is risky because it could cause the outline to change and too many hot reloads cause flaky app behavior. - /* - if (!success && color.equals(picker.getColor())) { - setPropertyValue(colorPropertyName, colorExpression); - }*/ - }); - } - } - - private void applyColor() { - disposeColorPicker(); - final String colorExpression = buildColorExpression(currentColor); - setText(colorExpression); - panel.setPropertyValue(name, colorExpression); - repaint(); - } -} diff --git a/flutter-idea/src/io/flutter/editor/PropertyEditorPanel.java b/flutter-idea/src/io/flutter/editor/PropertyEditorPanel.java deleted file mode 100644 index 0fbe9f4526..0000000000 --- a/flutter-idea/src/io/flutter/editor/PropertyEditorPanel.java +++ /dev/null @@ -1,837 +0,0 @@ -/* - * 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.editor; - -import com.google.common.base.Joiner; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionToolbar; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.ComboBox; -import com.intellij.openapi.ui.SimpleToolWindowPanel; -import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.ui.popup.BalloonBuilder; -import com.intellij.openapi.ui.popup.JBPopupFactory; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.JBColor; -import com.intellij.ui.awt.RelativePoint; -import com.intellij.ui.components.JBLabel; -import com.intellij.ui.components.JBTextField; -import com.intellij.util.ui.PositionTracker; -import com.jetbrains.lang.dart.assists.AssistUtils; -import com.jetbrains.lang.dart.assists.DartSourceEditException; -import io.flutter.FlutterMessages; -import io.flutter.dart.FlutterDartAnalysisServer; -import io.flutter.hotui.StableWidgetTracker; -import io.flutter.inspector.DiagnosticsNode; -import io.flutter.inspector.InspectorGroupManagerService; -import io.flutter.inspector.InspectorObjectGroupManager; -import io.flutter.inspector.InspectorService; -import io.flutter.preview.OutlineOffsetConverter; -import io.flutter.preview.WidgetEditToolbar; -import io.flutter.run.FlutterReloadManager; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.AsyncRateLimiter; -import io.flutter.utils.AsyncUtils; -import io.flutter.utils.EventStream; -import net.miginfocom.swing.MigLayout; -import org.dartlang.analysis.server.protocol.*; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.FocusEvent; -import java.awt.event.FocusListener; -import java.awt.event.ItemEvent; -import java.util.List; -import java.util.*; -import java.util.concurrent.CompletableFuture; - -class EnumValueWrapper { - final FlutterWidgetPropertyValueEnumItem item; - final String expression; - - public EnumValueWrapper(FlutterWidgetPropertyValueEnumItem item) { - this.item = item; - this.expression = item.getName(); - assert (this.expression != null); - } - - public EnumValueWrapper(String expression) { - this.expression = expression; - item = null; - } - - @Override - public String toString() { - if (expression != null) { - return expression; - } - return item != null ? item.getName() : "[null]"; - } -} - -class PropertyEnumComboBoxModel extends AbstractListModel - implements ComboBoxModel { - private final List myList; - private EnumValueWrapper mySelected; - private String expression; - - public PropertyEnumComboBoxModel(FlutterWidgetProperty property) { - final FlutterWidgetPropertyEditor editor = property.getEditor(); - assert (editor != null); - myList = new ArrayList<>(); - for (FlutterWidgetPropertyValueEnumItem item : editor.getEnumItems()) { - myList.add(new EnumValueWrapper(item)); - } - String expression = property.getExpression(); - if (expression == null) { - mySelected = null; - return; - } - if (property.getValue() != null) { - final FlutterWidgetPropertyValue value = property.getValue(); - final FlutterWidgetPropertyValueEnumItem enumValue = value.getEnumValue(); - if (enumValue != null) { - for (EnumValueWrapper e : myList) { - if (e != null && e.item != null && Objects.equals(e.item.getName(), enumValue.getName())) { - mySelected = e; - } - } - } - } - else { - final EnumValueWrapper newItem = new EnumValueWrapper(expression); - myList.add(newItem); - mySelected = newItem; - } - final String kind = editor.getKind(); - } - - @Override - public int getSize() { - return myList.size(); - } - - @Override - public EnumValueWrapper getElementAt(int index) { - return myList.get(index); - } - - @Override - public EnumValueWrapper getSelectedItem() { - return mySelected; - } - - @Override - public void setSelectedItem(Object item) { - if (item instanceof String expression) { - for (EnumValueWrapper e : myList) { - if (Objects.equals(e.expression, expression)) { - mySelected = e; - return; - } - } - final EnumValueWrapper wrapper = new EnumValueWrapper(expression); - myList.add(wrapper); - this.fireIntervalAdded(this, myList.size() - 1, myList.size()); - setSelectedItem(wrapper); - return; - } - setSelectedItem((EnumValueWrapper)item); - } - - public void setSelectedItem(EnumValueWrapper item) { - mySelected = item; - fireContentsChanged(this, 0, getSize()); - } -} - -/** - * Panel that supports editing properties of a specified widget. - *

- * The property panel will update - */ -public class PropertyEditorPanel extends SimpleToolWindowPanel { - protected final AsyncRateLimiter retryLookupPropertiesRateLimiter; - /** - * Whether the property panel is being rendered in a fixed width context such - * as inside a baloon popup window or is within a resizeable window. - */ - private final boolean fixedWidth; - private final InspectorGroupManagerService.Client inspectorStateServiceClient; - private final FlutterDartAnalysisServer flutterDartAnalysisService; - @Nullable private final Project project; - private final boolean showWidgetEditToolbar; - private final Map fields = new HashMap<>(); - private final Map propertyMap = new HashMap<>(); - private final Map currentExpressionMap = new HashMap<>(); - private final ArrayList properties = new ArrayList<>(); - private final Disposable parentDisposable; - // TODO(jacobr): figure out why this is needed. - int numFailures; - String previouslyFocusedProperty = null; - private DiagnosticsNode node; - /** - * Outline node - */ - private FlutterOutline outline; - - private EventStream activeFile; - - /** - * Whether the property panel has already triggered a pending hot reload. - */ - private boolean pendingHotReload; - - /** - * Whether the property panel needs another hot reload to occur after the - * current pending hot reload is complete. - */ - private boolean needHotReload; - private CompletableFuture> propertyFuture; - - public PropertyEditorPanel( - @Nullable InspectorGroupManagerService inspectorGroupManagerService, - @Nullable Project project, - FlutterDartAnalysisServer flutterDartAnalysisService, - boolean showWidgetEditToolbar, - boolean fixedWidth, - Disposable parentDisposable - ) { - super(true, true); - - setFocusable(true); - - this.fixedWidth = fixedWidth; - this.parentDisposable = parentDisposable; - - inspectorStateServiceClient = new InspectorGroupManagerService.Client(parentDisposable) { - @Override - public void onInspectorAvailabilityChanged() { - // The app has terminated or restarted. No way we are still waiting - // for a pending hot reload. - pendingHotReload = false; - } - - @Override - public void notifyAppReloaded() { - pendingHotReload = false; - } - - @Override - public void notifyAppRestarted() { - pendingHotReload = false; - } - }; - - retryLookupPropertiesRateLimiter = new AsyncRateLimiter(10, () -> { - // TODO(jacobr): is this still needed now that we have dealt with timeout - // issues by making the analysis server api async? - maybeLoadProperties(); - return CompletableFuture.completedFuture(null); - }, parentDisposable); - - inspectorGroupManagerService.addListener(inspectorStateServiceClient, parentDisposable); - this.project = project; - this.flutterDartAnalysisService = flutterDartAnalysisService; - this.showWidgetEditToolbar = showWidgetEditToolbar; - } - - /** - * Display a popup containing the property editing panel for the specified widget. - */ - public static Balloon showPopup( - InspectorGroupManagerService inspectorGroupManagerService, - EditorEx editor, - DiagnosticsNode node, - @NotNull InspectorService.Location location, - FlutterDartAnalysisServer service, - Point point - ) { - final Balloon balloon = showPopupHelper(inspectorGroupManagerService, editor.getProject(), node, location, service); - if (point != null) { - balloon.show(new PropertyBalloonPositionTrackerScreenshot(editor, point), Balloon.Position.below); - } - else { - final int offset = location.getOffset(); - final TextRange textRange = new TextRange(offset, offset + 1); - balloon.show(new PropertyBalloonPositionTracker(editor, textRange), Balloon.Position.below); - } - return balloon; - } - - public static Balloon showPopup( - InspectorGroupManagerService inspectorGroupManagerService, - Project project, - Component component, - @Nullable DiagnosticsNode node, - @NonNls InspectorService.Location location, - FlutterDartAnalysisServer service, - Point point - ) { - final Balloon balloon = showPopupHelper(inspectorGroupManagerService, project, node, location, service); - balloon.show(new RelativePoint(component, point), Balloon.Position.above); - return balloon; - } - - public static Balloon showPopupHelper( - InspectorGroupManagerService inspectorService, - Project project, - @Nullable DiagnosticsNode node, - @NotNull InspectorService.Location location, - FlutterDartAnalysisServer service - ) { - final Color GRAPHITE_COLOR = new JBColor(new Color(236, 236, 236, 215), new Color(60, 63, 65, 215)); - - final Disposable panelDisposable = Disposer.newDisposable(); - final PropertyEditorPanel panel = - new PropertyEditorPanel(inspectorService, project, service, true, true, panelDisposable); - - final StableWidgetTracker tracker = new StableWidgetTracker(location, service, project, panelDisposable); - - final EventStream activeFile = new EventStream<>(location.getFile()); - panel.initalize(node, tracker.getCurrentOutlines(), activeFile); - - panel.setBackground(GRAPHITE_COLOR); - panel.setOpaque(false); - final BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createBalloonBuilder(panel); - balloonBuilder.setFadeoutTime(0); - balloonBuilder.setFillColor(GRAPHITE_COLOR); - balloonBuilder.setAnimationCycle(0); - balloonBuilder.setHideOnClickOutside(true); - balloonBuilder.setHideOnKeyOutside(false); - balloonBuilder.setHideOnAction(false); - balloonBuilder.setCloseButtonEnabled(false); - balloonBuilder.setBlockClicksThroughBalloon(true); - balloonBuilder.setRequestFocus(true); - balloonBuilder.setShadow(true); - final Balloon balloon = balloonBuilder.createBalloon(); - Disposer.register(balloon, panelDisposable); - - return balloon; - } - - InspectorObjectGroupManager getGroupManager() { - return inspectorStateServiceClient.getGroupManager(); - } - - int getOffset() { - if (outline == null) return -1; - final VirtualFile file = activeFile.getValue(); - if (file == null) return -1; - final OutlineOffsetConverter converter = new OutlineOffsetConverter(project, file); - return converter.getConvertedOutlineOffset(outline); - } - - InspectorService.Location getInspectorLocation() { - final VirtualFile file = activeFile.getValue(); - if (file == null || outline == null) { - return null; - } - - final Document document = FileDocumentManager.getInstance().getDocument(file); - return InspectorService.Location.outlineToLocation(project, activeFile.getValue(), outline, document); - } - - void updateWidgetDescription() { - final VirtualFile file = activeFile.getValue(); - final int offset = getOffset(); - - if (file == null || - offset < 0 || - outline == null || - outline.getClassName() == null || - (!FlutterOutlineKind.NEW_INSTANCE.equals(outline.getKind()))) { - if (!properties.isEmpty()) { - properties.clear(); - rebuildUi(); - } - return; - } - - final CompletableFuture> future = - flutterDartAnalysisService.getWidgetDescription(file, offset); - propertyFuture = future; - - if (propertyFuture == null) return; - - AsyncUtils.whenCompleteUiThread(propertyFuture, (updatedProperties, throwable) -> { - if (propertyFuture != future || updatedProperties == null || throwable != null) { - // This response is obsolete as there was a newer request. - return; - } - if (offset != getOffset() || !file.equals(activeFile.getValue())) { - return; - } - properties.clear(); - properties.addAll(updatedProperties); - propertyMap.clear(); - currentExpressionMap.clear(); - for (FlutterWidgetProperty property : updatedProperties) { - final String name = property.getName(); - propertyMap.put(name, property); - currentExpressionMap.put(name, property.getExpression()); - } - - if (propertyMap.isEmpty()) { - // TODO(jacobr): is this still needed now that we have dealt with timeout - // issues by making the analysis server api async? - numFailures++; - if (numFailures < 3) { - retryLookupPropertiesRateLimiter.scheduleRequest(); - } - return; - } - numFailures = 0; - rebuildUi(); - }); - } - - public void outlinesChanged(List outlines) { - final FlutterOutline nextOutline = outlines.isEmpty() ? null : outlines.get(0); - if (nextOutline == outline) return; - this.outline = nextOutline; - maybeLoadProperties(); - lookupMatchingElements(); - } - - public void lookupMatchingElements() { - final InspectorObjectGroupManager groupManager = getGroupManager(); - if (groupManager == null || outline == null) return; - groupManager.cancelNext(); - node = null; - final InspectorService.ObjectGroup group = groupManager.getNext(); - final InspectorService.Location location = getInspectorLocation(); - group.safeWhenComplete(group.getElementsAtLocation(location, 10), (elements, error) -> { - if (elements == null || error != null) { - return; - } - node = elements.isEmpty() ? null : elements.get(0); - groupManager.promoteNext(); - }); - } - - public DiagnosticsNode getNode() { - return node; - } - - void maybeLoadProperties() { - updateWidgetDescription(); - } - - public void initalize( - DiagnosticsNode node, - EventStream> currentOutlines, - EventStream activeFile - ) { - this.node = node; - this.activeFile = activeFile; - currentOutlines.listen(this::outlinesChanged, true); - if (showWidgetEditToolbar) { - final WidgetEditToolbar widgetEditToolbar = - new WidgetEditToolbar(true, currentOutlines, activeFile, project, flutterDartAnalysisService); - final ActionToolbar toolbar = widgetEditToolbar.getToolbar(); - toolbar.setShowSeparatorTitles(true); - setToolbar(toolbar.getComponent()); - } - - rebuildUi(); - } - - protected void rebuildUi() { - // TODO(jacobr): be lazier about only rebuilding what changed. - final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); - if (focusOwner != null) { - if (isAncestorOf(focusOwner)) { - for (Map.Entry entry : fields.entrySet()) { - if (entry.getValue().isAncestorOf(focusOwner) || entry.getValue() == focusOwner) { - previouslyFocusedProperty = entry.getKey(); - break; - } - } - } - else { - previouslyFocusedProperty = null; - } - } - removeAll(); - // Layout Constraints - // Column constraints - final MigLayout manager = new MigLayout( - "insets 3", // Layout Constraints - fixedWidth ? "[::120]5[:20:400]" : "[::120]5[grow]", // Column constraints - "[23]4[23]" - ); - setLayout(manager); - int added = 0; - for (FlutterWidgetProperty property : properties) { - final String name = property.getName(); - if (name.equals("child") || name.equals("children")) { - continue; - } - if (name.equals("Container")) { - final List containerProperties = property.getChildren(); - // TODO(jacobr): add support for container properties. - continue; - } - final String documentation = property.getDocumentation(); - JComponent field; - - if (property.getEditor() == null) { - // TODO(jacobr): detect color properties more robustly. - final boolean colorProperty = name.equals("color"); - final String colorPropertyName = name; - if (colorProperty) { - field = buildColorProperty(name, property); - } - else { - String expression = property.getExpression(); - if (expression == null) { - expression = ""; - } - final JBTextField textField = new JBTextField(expression); - // Make sure we show the text at the beginning of the text field. - // The default is to show the end if the content scrolls which looks - // bad in a property editor. - textField.setCaretPosition(0); - addTextFieldListeners(name, textField); - field = textField; - } - } - else { - final FlutterWidgetPropertyEditor editor = property.getEditor(); - if (editor.getEnumItems() != null) { - final ComboBox comboBox = new ComboBox<>(); - comboBox.setEditable(true); - comboBox.setModel(new PropertyEnumComboBoxModel(property)); - - // TODO(jacobr): need a bit more padding around comboBox to make it match the JBTextField. - field = comboBox; - comboBox.addItemListener(e -> { - if (e.getStateChange() == ItemEvent.SELECTED) { - final EnumValueWrapper wrapper = (EnumValueWrapper)e.getItem(); - if (wrapper.item != null) { - setParsedPropertyValue(name, new FlutterWidgetPropertyValue(null, null, null, null, wrapper.item, null), false); - } - else { - setPropertyValue(name, wrapper.expression); - } - } - }); - } - else { - // TODO(jacobr): use IntegerField and friends when appropriate. - // TODO(jacobr): we should probably use if (property.isSafeToUpdate()) - // but that currently it seems to have a bunch of false positives. - final String kind = property.getEditor().getKind(); - if (Objects.equals(kind, FlutterWidgetPropertyEditorKind.BOOL)) { - // TODO(jacobr): show as boolean. - } - final JBTextField textField = new JBTextField(property.getExpression()); - // Make sure we show the text at the beginning of the text field. - // The default is to show the end if the content scrolls which looks - // bad in a property editor. - textField.setCaretPosition(0); - field = textField; - addTextFieldListeners(name, textField); - } - } - - if (name.equals("data")) { - field.setToolTipText(Objects.requireNonNullElse(documentation, "data")); - add(field, "span, growx"); - } - else { - final String propertyName = property.getName(); - final JBLabel label = new JBLabel(propertyName); - // 120 is the max width of the column but that does not appear to be - // applied unless it is also set here. - add(label, "right, wmax 120px"); - final ArrayList tooltipBlocks = new ArrayList<>(); - tooltipBlocks.add("" + propertyName + ""); - - if (documentation != null) { - tooltipBlocks.add(documentation); - } - // Use multiple line breaks so there is a clear separation between blocks. - label.setToolTipText(Joiner.on("\n\n").join(tooltipBlocks)); - - add(field, "wrap, growx"); - } - if (documentation != null) { - field.setToolTipText(documentation); - } - // Hack: set the preferred width of the ui elements to a small value - // so it doesn't cause the overall layout to be wider than it should - // be. - if (!fixedWidth) { - setPreferredFieldSize(field); - } - - fields.put(name, field); - added++; - } - if (previouslyFocusedProperty != null && fields.containsKey(previouslyFocusedProperty)) { - fields.get(previouslyFocusedProperty).requestFocus(); - } - - if (added == 0) { - add(new JBLabel("No editable properties")); - } - // TODO(jacobr): why is this needed? - revalidate(); - repaint(); - } - - private JTextField buildColorProperty(String name, FlutterWidgetProperty property) { - return new ColorField(this, name, property, parentDisposable); - } - - public void addTextFieldListeners(String name, JBTextField field) { - final FlutterOutline matchingOutline = outline; - field.addActionListener(e -> setPropertyValue(name, field.getText())); - field.addFocusListener(new FocusListener() { - @Override - public void focusGained(FocusEvent e) { - } - - @Override - public void focusLost(FocusEvent e) { - if (outline != matchingOutline) { - // Don't do anything. The user has moved on to a different outline node. - return; - } - setPropertyValue(name, field.getText()); - } - }); - } - - private void setPreferredFieldSize(JComponent field) { - field.setPreferredSize(new Dimension(20, (int)field.getPreferredSize().getHeight())); - } - - public String getDescription() { - final List parts = new ArrayList<>(); - if (outline != null && outline.getClassName() != null) { - parts.add(outline.getClassName()); - } - parts.add("Properties"); - return Joiner.on(" ").join(parts); - } - - void setPropertyValue(String propertyName, String expression) { - setPropertyValue(propertyName, expression, false); - } - - void setPropertyValue(String propertyName, String expression, boolean force) { - setParsedPropertyValue(propertyName, new FlutterWidgetPropertyValue(null, null, null, null, null, expression), force); - } - - private void setParsedPropertyValue(String propertyName, FlutterWidgetPropertyValue value, boolean force) { - final boolean updated = setParsedPropertyValueHelper(propertyName, value); - if (!updated && force) { - hotReload(); - } - } - - - private boolean setParsedPropertyValueHelper(String propertyName, FlutterWidgetPropertyValue value) { - // TODO(jacobr): also do simple tracking of how the previous expression maps to the current expression to avoid spurious edits. - - // Treat an empty expression and empty value objects as omitted values - // indicating the property should be removed. - final FlutterWidgetPropertyValue emptyValue = new FlutterWidgetPropertyValue(null, null, null, null, null, null); - - final FlutterWidgetProperty property = propertyMap.get(propertyName); - if (property == null) { - // UI is in the process of updating. Skip this action. - return false; - } - - if (property.getExpression() != null && property.getExpression().equals(value.getExpression())) { - return false; - } - - if (value != null && Objects.equals(value.getExpression(), "") || emptyValue.equals(value)) { - // Normalize empty expressions to simplify calling this api. - value = null; - } - - final String lastExpression = currentExpressionMap.get(propertyName); - if (lastExpression != null && value != null && lastExpression.equals(value.getExpression())) { - return false; - } - currentExpressionMap.put(propertyName, value != null ? value.getExpression() : null); - - final FlutterWidgetPropertyEditor editor = property.getEditor(); - if (editor != null && value != null && value.getExpression() != null) { - final String expression = value.getExpression(); - // Normalize expressions as primitive values. - final String kind = editor.getKind(); - switch (kind) { - case FlutterWidgetPropertyEditorKind.BOOL: { - if (expression.equals("true")) { - value = new FlutterWidgetPropertyValue(true, null, null, null, null, null); - } - else if (expression.equals("false")) { - value = new FlutterWidgetPropertyValue(false, null, null, null, null, null); - } - } - break; - case FlutterWidgetPropertyEditorKind.STRING: { - // TODO(jacobr): there might be non-string literal cases that match this patterned. - if (expression.length() >= 2 && ( - (expression.startsWith("'") && expression.endsWith("'")) || - (expression.startsWith("\"") && expression.endsWith("\"")))) { - value = new FlutterWidgetPropertyValue(null, null, null, expression.substring(1, expression.length() - 1), null, null); - } - } - break; - case FlutterWidgetPropertyEditorKind.DOUBLE: { - try { - final double doubleValue = Double.parseDouble(expression); - if (((double)((int)doubleValue)) == doubleValue) { - // Express doubles that can be expressed as ints as ints. - value = new FlutterWidgetPropertyValue(null, null, (int)doubleValue, null, null, null); - } - else { - value = new FlutterWidgetPropertyValue(null, doubleValue, null, null, null, null); - } - } - catch (NumberFormatException e) { - // Don't convert value. - } - } - break; - case FlutterWidgetPropertyEditorKind.INT: { - try { - final int intValue = Integer.parseInt(expression); - value = new FlutterWidgetPropertyValue(null, null, intValue, null, null, null); - } - catch (NumberFormatException e) { - // Don't convert value. - } - } - break; - } - } - if (Objects.equals(property.getValue(), value)) { - // Short circuit as nothing changed. - return false; - } - - final SourceChange change; - try { - change = flutterDartAnalysisService.setWidgetPropertyValue(property.getId(), value); - } - catch (Exception e) { - if (value != null && value.getExpression() != null) { - FlutterMessages.showInfo("Invalid property value", value.getExpression(), project); - } - else { - FlutterMessages.showError("Unable to set propery value", e.getMessage(), project); - } - return false; - } - - if (change != null && change.getEdits() != null && !change.getEdits().isEmpty()) { - // TODO(jacobr): does running a write action make sense here? We are - // already on the UI thread. - ApplicationManager.getApplication().runWriteAction(() -> { - try { - AssistUtils.applySourceChange(project, change, false); - hotReload(); - } - catch (DartSourceEditException exception) { - FlutterMessages.showInfo("Failed to apply code change", exception.getMessage(), project); - } - }); - return true; - } - return false; - } - - private void hotReload() { - // TODO(jacobr): handle multiple simultaneously running Flutter applications. - final FlutterApp app = inspectorStateServiceClient.getApp(); - if (app != null) { - final ArrayList apps = new ArrayList<>(); - apps.add(app); - if (pendingHotReload) { - // It is important we don't try to trigger multiple hot reloads - // as that will result in annoying user visible error messages. - needHotReload = true; - } - else { - pendingHotReload = true; - needHotReload = false; - FlutterReloadManager.getInstance(project).saveAllAndReloadAll(apps, "Property Editor"); - } - } - } - - public FlutterOutline getCurrentOutline() { - return outline; - } -} - -class PropertyBalloonPositionTracker extends PositionTracker { - private final Editor myEditor; - private final TextRange myRange; - - PropertyBalloonPositionTracker(Editor editor, TextRange range) { - super(editor.getContentComponent()); - myEditor = editor; - myRange = range; - } - - static boolean insideVisibleArea(Editor e, TextRange r) { - final int textLength = e.getDocument().getTextLength(); - if (r.getStartOffset() > textLength) return false; - if (r.getEndOffset() > textLength) return false; - final Rectangle visibleArea = e.getScrollingModel().getVisibleArea(); - final Point point = e.logicalPositionToXY(e.offsetToLogicalPosition(r.getStartOffset())); - - return visibleArea.contains(point); - } - - @Override - public RelativePoint recalculateLocation(final Balloon balloon) { - final int startOffset = myRange.getStartOffset(); - final int endOffset = myRange.getEndOffset(); - final Point startPoint = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(startOffset)); - final Point endPoint = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(endOffset)); - final Point point = new Point((startPoint.x + endPoint.x) / 2, startPoint.y + myEditor.getLineHeight()); - - return new RelativePoint(myEditor.getContentComponent(), point); - } -} - -class PropertyBalloonPositionTrackerScreenshot extends PositionTracker { - private final Editor myEditor; - private final Point point; - - PropertyBalloonPositionTrackerScreenshot(Editor editor, Point point) { - super(editor.getComponent()); - myEditor = editor; - this.point = point; - } - - @Override - public RelativePoint recalculateLocation(final Balloon balloon) { - return new RelativePoint(myEditor.getComponent(), point); - } -} - diff --git a/flutter-idea/src/io/flutter/hotui/StableWidgetTracker.java b/flutter-idea/src/io/flutter/hotui/StableWidgetTracker.java deleted file mode 100644 index c9c381f3a8..0000000000 --- a/flutter-idea/src/io/flutter/hotui/StableWidgetTracker.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * 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.hotui; - -import com.google.common.collect.ImmutableList; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.io.FileUtil; -import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; -import io.flutter.dart.FlutterDartAnalysisServer; -import io.flutter.dart.FlutterOutlineListener; -import io.flutter.inspector.InspectorService; -import io.flutter.preview.OutlineOffsetConverter; -import io.flutter.utils.EventStream; -import org.dartlang.analysis.server.protocol.FlutterOutline; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Class that uses the FlutterOutline to maintain the source location for a - * Widget even when code edits that would otherwise confuse location tracking - * occur. - */ -public class StableWidgetTracker implements Disposable { - private final String currentFilePath; - private final InspectorService.Location initialLocation; - private final FlutterDartAnalysisServer flutterAnalysisServer; - - private final OutlineOffsetConverter converter; - // Path to the current outline - private ArrayList lastPath; - - FlutterOutline root; - - private final FlutterOutlineListener outlineListener = new FlutterOutlineListener() { - @Override - public void outlineUpdated(@NotNull String filePath, @NotNull FlutterOutline outline, @Nullable String instrumentedCode) { - if (Objects.equals(currentFilePath, filePath)) { - ApplicationManager.getApplication().invokeLater(() -> outlineChanged(outline)); - } - } - }; - - private void outlineChanged(FlutterOutline outline) { - this.root = outline; - FlutterOutline match; - if (lastPath == null) { - // First outline. - lastPath = new ArrayList<>(); - findOutlineAtOffset(root, initialLocation.getOffset(), lastPath); - } - else { - lastPath = findSimilarPath(root, lastPath); - } - currentOutlines.setValue(lastPath.isEmpty() ? ImmutableList.of() : ImmutableList.of(lastPath.get(lastPath.size() - 1))); - } - - private static int findChildIndex(FlutterOutline node, FlutterOutline child) { - final List children = node.getChildren(); - for (int i = 0; i < children.size(); i++) { - if (children.get(i) == child) return i; - } - return -1; - } - - private ArrayList findSimilarPath(FlutterOutline root, ArrayList lastPath) { - final ArrayList path = new ArrayList<>(); - FlutterOutline node = root; - path.add(node); - int i = 1; - while (i < lastPath.size() && node != null && !node.getChildren().isEmpty()) { - final FlutterOutline oldChild = lastPath.get(i); - final int expectedIndex = findChildIndex(lastPath.get(i - 1), oldChild); - assert (expectedIndex != -1); - final List children = node.getChildren(); - final int index = Math.min(Math.max(0, expectedIndex), children.size()); - node = children.get(index); - if (!Objects.equals(node.getClassName(), oldChild.getClassName()) && node.getChildren().size() == 1) { - final FlutterOutline child = node.getChildren().get(0); - if (Objects.equals(child.getClassName(), oldChild.getClassName())) { - // We have detected that the previous widget was wrapped by a new widget. - // Add the wrapping widget to the path and otherwise proceed normally. - path.add(node); - node = child; - } - } - // TODO(jacobr): consider doing some additional validation that the children have the same class names, etc. - // We could use that to be reslient to small changes such as adding a new parent widget, etc. - path.add(node); - i++; - } - return path; - } - - private boolean findOutlineAtOffset(FlutterOutline outline, int offset, ArrayList path) { - if (outline == null) { - return false; - } - path.add(outline); - if (converter.getConvertedOutlineOffset(outline) <= offset && offset <= converter.getConvertedOutlineEnd(outline)) { - final List children = outline.getChildren(); - if (children != null) { - for (FlutterOutline child : children) { - final boolean foundChild = findOutlineAtOffset(child, offset, path); - if (foundChild) { - return true; - } - } - } - return true; - } - path.remove(path.size() - 1); - return false; - } - - private final EventStream> currentOutlines; - - public EventStream> getCurrentOutlines() { - return currentOutlines; - } - - public StableWidgetTracker( - InspectorService.Location initialLocation, - FlutterDartAnalysisServer flutterAnalysisServer, - Project project, - Disposable parentDisposable - ) { - Disposer.register(parentDisposable, this); - converter = new OutlineOffsetConverter(project, initialLocation.getFile()); - currentOutlines = new EventStream<>(ImmutableList.of()); - this.flutterAnalysisServer = flutterAnalysisServer; - this.initialLocation = initialLocation; - - final DartAnalysisServerService analysisServerService = DartAnalysisServerService.getInstance(project); - currentFilePath = FileUtil.toSystemDependentName(initialLocation.getFile().getPath()); - flutterAnalysisServer.addOutlineListener(currentFilePath, outlineListener); - } - - @Override - public void dispose() { - flutterAnalysisServer.removeOutlineListener(currentFilePath, outlineListener); - } - - public boolean isValid() { - return !getCurrentOutlines().getValue().isEmpty(); - } - - public int getOffset() { - final List outlines = getCurrentOutlines().getValue(); - if (outlines.isEmpty()) return 0; - final FlutterOutline outline = outlines.get(0); - return converter.getConvertedOutlineOffset(outline); - } -} diff --git a/flutter-idea/src/io/flutter/inspector/DiagnosticsPathNode.java b/flutter-idea/src/io/flutter/inspector/DiagnosticsPathNode.java deleted file mode 100644 index 027c7cdbca..0000000000 --- a/flutter-idea/src/io/flutter/inspector/DiagnosticsPathNode.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2017 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.inspector; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import java.util.ArrayList; - -/** - * Represents a path in a DiagnosticsNode tree used to quickly display a path - * in the DiagnosticsNode tree in response to a change in the selected object - * on the device. - */ -public class DiagnosticsPathNode { - private final InspectorService.ObjectGroup inspectorService; - private final JsonObject json; - - public DiagnosticsPathNode(JsonObject json, InspectorService.ObjectGroup inspectorService) { - this.inspectorService = inspectorService; - this.json = json; - } - - public DiagnosticsNode getNode() { - // We are lazy about getting the diagnosticNode instanceRef so that no additional round trips using the observatory protocol - // are yet triggered for the typical case where properties of a node are not inspected. - return new DiagnosticsNode(json.getAsJsonObject("node"), inspectorService, false, null); - } - - public ArrayList getChildren() { - final ArrayList children = new ArrayList<>(); - final JsonElement childrenElement = json.get("children"); - if (childrenElement.isJsonNull()) { - return children; - } - final JsonArray childrenJson = childrenElement.getAsJsonArray(); - for (int i = 0; i < childrenJson.size(); ++i) { - children.add(new DiagnosticsNode(childrenJson.get(i).getAsJsonObject(), inspectorService, false, null)); - } - return children; - } - - /** - * Returns the index of the child that continues the path if any. - */ - public int getChildIndex() { - final JsonElement childIndex = json.get("childIndex"); - if (childIndex.isJsonNull()) { - return -1; - } - return childIndex.getAsInt(); - } -} diff --git a/flutter-idea/src/io/flutter/inspector/EvalException.java b/flutter-idea/src/io/flutter/inspector/EvalException.java deleted file mode 100644 index 7019aaf8d9..0000000000 --- a/flutter-idea/src/io/flutter/inspector/EvalException.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.inspector; - -/** - * An exception that can occur from calls to eval() from the Inspector codebase. - */ -public class EvalException extends Exception { - public final String expression; - public final String errorCode; - public final String errorMessage; - - public EvalException(String expression, String errorCode, String errorMessage) { - this.expression = expression; - this.errorCode = errorCode; - this.errorMessage = errorMessage; - } - - @Override - public String toString() { - return "expression=" + expression + ",errorCode=" + errorCode + ",errorMessage=" + errorMessage; - } -} diff --git a/flutter-idea/src/io/flutter/inspector/EvalOnDartLibrary.java b/flutter-idea/src/io/flutter/inspector/EvalOnDartLibrary.java deleted file mode 100644 index fb8eed7f22..0000000000 --- a/flutter-idea/src/io/flutter/inspector/EvalOnDartLibrary.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2017 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.inspector; - -import com.google.gson.JsonObject; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.util.Alarm; -import com.intellij.xdebugger.XSourcePosition; -import io.flutter.utils.StreamSubscription; -import io.flutter.vmService.DartVmServiceDebugProcess; -import io.flutter.vmService.VMServiceManager; -import org.dartlang.vm.service.VmService; -import org.dartlang.vm.service.consumer.EvaluateConsumer; -import org.dartlang.vm.service.consumer.GetIsolateConsumer; -import org.dartlang.vm.service.consumer.GetObjectConsumer; -import org.dartlang.vm.service.consumer.ServiceExtensionConsumer; -import org.dartlang.vm.service.element.*; -import org.jetbrains.annotations.NotNull; - -import java.util.Map; -import java.util.Set; -import java.util.concurrent.*; -import java.util.function.Supplier; - -/** - * Invoke methods from a specified Dart library using the observatory protocol. - */ -public class EvalOnDartLibrary implements Disposable { - private static final Logger LOG = Logger.getInstance(EvalOnDartLibrary.class); - - private final StreamSubscription subscription; - private final ScheduledThreadPoolExecutor delayer; - private String isolateId; - private final VmService vmService; - @SuppressWarnings("FieldCanBeLocal") private final VMServiceManager vmServiceManager; - private final Set libraryNames; - CompletableFuture libraryRef; - private final Alarm myRequestsScheduler; - - static final int DEFAULT_REQUEST_TIMEOUT_SECONDS = 10; - /** - * For robustness we ensure at most one pending request is issued at a time. - */ - private CompletableFuture allPendingRequestsDone; - private final Object pendingRequestLock = new Object(); - - /** - * Public so that other related classes such as InspectorService can ensure their - * requests are in a consistent order with requests which eliminates otherwise - * surprising timing bugs such as if a request to dispose an - * InspectorService.ObjectGroup was issued after a request to read properties - * from an object in a group but the request to dispose the object group - * occurred first. - *

- * The design is we have at most 1 pending request at a time. This sacrifices - * some throughput with the advantage of predictable semantics and the benefit - * that we are able to skip large numbers of requests if they happen to be - * from groups of objects that should no longer be kept alive. - *

- * The optional ObjectGroup specified by isAlive, indicates whether the - * request is still relevant or should be cancelled. This is an optimization - * for the Inspector to avoid overloading the service with stale requests if - * the user is quickly navigating through the UI generating lots of stale - * requests to view specific details subtrees. - */ - public CompletableFuture addRequest(InspectorService.ObjectGroup isAlive, - String requestName, - Supplier> request) { - if (isAlive != null && isAlive.isDisposed()) { - return CompletableFuture.completedFuture(null); - } - - if (myRequestsScheduler.isDisposed()) { - return CompletableFuture.completedFuture(null); - } - - // Future that completes when the request has finished. - final CompletableFuture response = new CompletableFuture<>(); - // This is an optimization to avoid sending stale requests across the wire. - final Runnable wrappedRequest = () -> { - if (isAlive != null && isAlive.isDisposed()) { - response.complete(null); - return; - } - try { - // No need to timeout until the request has actually started. - timeoutAfter(response, DEFAULT_REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS, requestName); - } catch (CompletionException ex) { - response.completeExceptionally(ex); - } - final CompletableFuture future = request.get(); - future.whenCompleteAsync((v, t) -> { - if (t != null) { - response.completeExceptionally(t); - } - else { - response.complete(v); - } - }); - }; - synchronized (pendingRequestLock) { - if (allPendingRequestsDone == null || allPendingRequestsDone.isDone()) { - allPendingRequestsDone = response; - myRequestsScheduler.addRequest(wrappedRequest, 0); - } - else { - final CompletableFuture previousDone = allPendingRequestsDone; - allPendingRequestsDone = response; - // Actually schedule this request only after the previous request completes. - previousDone.whenCompleteAsync((v, error) -> { - if (myRequestsScheduler.isDisposed()) { - response.complete(null); - } - else { - myRequestsScheduler.addRequest(wrappedRequest, 0); - } - }); - } - } - return response; - } - - public EvalOnDartLibrary(Set libraryNames, VmService vmService, VMServiceManager vmServiceManager) { - this.libraryNames = libraryNames; - this.vmService = vmService; - this.vmServiceManager = vmServiceManager; - this.myRequestsScheduler = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, this); - libraryRef = new CompletableFuture<>(); - - subscription = vmServiceManager.getCurrentFlutterIsolate((isolate) -> { - if (libraryRef.isDone()) { - libraryRef = new CompletableFuture<>(); - } - - if (isolate != null) { - initialize(isolate.getId()); - } - }, true); - delayer = new ScheduledThreadPoolExecutor(1); - } - - public String getIsolateId() { - return isolateId; - } - - public void dispose() { - subscription.dispose(); - // TODO(jacobr): complete all pending futures as cancelled? - } - - public CompletableFuture invokeServiceMethod(String method, JsonObject params) { - final CompletableFuture ret = new CompletableFuture<>(); - timeoutAfter(ret, DEFAULT_REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS, "service method " + method); - vmService.callServiceExtension(isolateId, method, params, new ServiceExtensionConsumer() { - @Override - public void onError(RPCError error) { - ret.completeExceptionally(new RuntimeException(error.getMessage())); - } - - @Override - public void received(JsonObject object) { - ret.complete(object); - } - }); - - return ret; - } - - // TODO(jacobr): remove this method after we switch to Java9+ which supports this method directly on CompletableFuture. - private void timeoutAfter(CompletableFuture future, long timeout, TimeUnit unit, String operationName) { - // Create the timeout exception now, so we can capture the stack trace of the caller. - final TimeoutException timeoutException = new TimeoutException(operationName); - delayer.schedule(() -> future.completeExceptionally(timeoutException), timeout, unit); - } - - public CompletableFuture eval(String expression, Map scope, InspectorService.ObjectGroup isAlive) { - return addRequest(isAlive, "evaluate", () -> { - final CompletableFuture future = new CompletableFuture<>(); - libraryRef.thenAcceptAsync((LibraryRef ref) -> vmService.evaluate( - getIsolateId(), ref.getId(), expression, - scope, true, - new EvaluateConsumer() { - @Override - public void onError(RPCError error) { - future.completeExceptionally( - new EvalException(expression, Integer.toString(error.getCode()), error.getMessage())); - } - - @Override - public void received(ErrorRef response) { - future.completeExceptionally( - new EvalException(expression, response.getKind().name(), response.getMessage())); - } - - @Override - public void received(InstanceRef response) { - future.complete(response); - } - - @Override - public void received(Sentinel response) { - future.completeExceptionally( - new EvalException(expression, "Sentinel", response.getValueAsString())); - } - } - )); - return future; - }); - } - - @SuppressWarnings("unchecked") - public CompletableFuture getObjectHelper(ObjRef instance, InspectorService.ObjectGroup isAlive) { - return addRequest(isAlive, "getObject", () -> { - final CompletableFuture future = new CompletableFuture<>(); - vmService.getObject( - getIsolateId(), instance.getId(), new GetObjectConsumer() { - @Override - public void onError(RPCError error) { - future.completeExceptionally(new RuntimeException("RPCError calling getObject: " + error.toString())); - } - - @Override - public void received(Obj response) { - future.complete((T)response); - } - - @Override - public void received(Sentinel response) { - future.completeExceptionally(new RuntimeException("Sentinel calling getObject: " + response.toString())); - } - } - ); - return future; - }); - } - - @NotNull - public CompletableFuture getSourcePosition(DartVmServiceDebugProcess debugProcess, - ScriptRef script, - int tokenPos, - InspectorService.ObjectGroup isAlive) { - return addRequest(isAlive, "getSourcePosition", - () -> CompletableFuture.completedFuture(debugProcess.getSourcePosition(isolateId, script, tokenPos))); - } - - public CompletableFuture getInstance(InstanceRef instance, InspectorService.ObjectGroup isAlive) { - return getObjectHelper(instance, isAlive); - } - - public CompletableFuture getLibrary(LibraryRef instance, InspectorService.ObjectGroup isAlive) { - return getObjectHelper(instance, isAlive); - } - - public CompletableFuture getClass(ClassRef instance, InspectorService.ObjectGroup isAlive) { - return getObjectHelper(instance, isAlive); - } - - public CompletableFuture getFunc(FuncRef instance, InspectorService.ObjectGroup isAlive) { - return getObjectHelper(instance, isAlive); - } - - private void initialize(String isolateId) { - this.isolateId = isolateId; - - vmService.getIsolate(isolateId, new GetIsolateConsumer() { - @Override - public void received(Isolate response) { - for (LibraryRef library : response.getLibraries()) { - - if (libraryNames.contains(library.getUri())) { - libraryRef.complete(library); - return; - } - } - libraryRef.completeExceptionally(new RuntimeException("No library matching " + libraryNames + " found.")); - } - - @Override - public void onError(RPCError error) { - libraryRef.completeExceptionally(new RuntimeException("RPCError calling getIsolate:" + error.toString())); - } - - @Override - public void received(Sentinel response) { - libraryRef.completeExceptionally(new RuntimeException("Sentinel calling getIsolate:" + response.toString())); - } - }); - } -} diff --git a/flutter-idea/src/io/flutter/inspector/InspectorActions.java b/flutter-idea/src/io/flutter/inspector/InspectorActions.java deleted file mode 100644 index cf2d0ab55b..0000000000 --- a/flutter-idea/src/io/flutter/inspector/InspectorActions.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright 2017 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.inspector; - -import org.jetbrains.annotations.NonNls; - -public interface InspectorActions { - @NonNls String JUMP_TO_TYPE_SOURCE = "Flutter.JumpToTypeSource"; - @NonNls String JUMP_TO_SOURCE = "Flutter.JumpToSource"; -} diff --git a/flutter-idea/src/io/flutter/inspector/InspectorGroupManagerService.java b/flutter-idea/src/io/flutter/inspector/InspectorGroupManagerService.java deleted file mode 100644 index 63d585ad12..0000000000 --- a/flutter-idea/src/io/flutter/inspector/InspectorGroupManagerService.java +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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.inspector; - -import com.google.common.collect.Lists; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -import io.flutter.run.FlutterAppManager; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.AsyncUtils; -import io.flutter.utils.StreamSubscription; -import io.flutter.vmService.ServiceExtensions; -import org.dartlang.vm.service.VmService; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; - -/** - * Service that provides the current inspector state independent of a specific - * application run. - *

- * This class provides a {@link Listener} that notifies consumers when state - * has changed. - *

    - *
  • The inspector is available.
  • - *
  • The inspector selection when it changes
  • - *
  • When the UI displayed in the app has changed
  • - *
- */ -public class InspectorGroupManagerService implements Disposable { - - /** - * Convenience implementation of Listener that tracks the active - * InspectorService and manages the creation of an - * InspectorObjectGroupManager for easy inspector lifecycle management. - */ - public static class Client implements Listener { - private InspectorService service; - private InspectorObjectGroupManager groupManager; - - public Client(Disposable parent) { - Disposer.register(parent, () -> { - if (groupManager != null) { - groupManager.clear(false); - } - }); - } - - public InspectorObjectGroupManager getGroupManager() { - if (groupManager == null && service != null) { - groupManager = new InspectorObjectGroupManager(service, "client"); - } - return groupManager; - } - - public FlutterApp getApp() { - if (service == null) return null; - return service.getApp(); - } - - public InspectorService getInspectorService() { - return service; - } - - @Override - public void onInspectorAvailable(InspectorService service) { - if (this.service == service) return; - if (groupManager != null) { - groupManager.clear(service == null); - } - this.service = service; - groupManager = null; - onInspectorAvailabilityChanged(); - } - - /** - * Clients should override this method to be notified when the inspector - * availabilty changed. - */ - public void onInspectorAvailabilityChanged() { - } - - public InspectorService.ObjectGroup getCurrentObjectGroup() { - groupManager = getGroupManager(); - if (groupManager == null) return null; - return groupManager.getCurrent(); - } - } - - private interface InvokeListener { - void run(Listener listener); - } - - public interface Listener { - void onInspectorAvailable(InspectorService service); - - /** - * Called when something has changed and UI dependent on inspector state - * should re-render. - * - * @param force indicates that the old UI is likely completely obsolete. - */ - default void requestRepaint(boolean force) { - } - - /** - * Called whenever Flutter renders a frame. - */ - default void onFlutterFrame() { - } - - default void notifyAppReloaded() { - } - - default void notifyAppRestarted() { - } - - /** - * Called when the inspector selection has changed. - */ - default void onSelectionChanged(DiagnosticsNode selection) { - } - } - - private final Set listeners = new HashSet<>(); - boolean started = false; - private CompletableFuture inspectorServiceFuture; - private FlutterApp app; - private FlutterApp.FlutterAppListener appListener; - private DiagnosticsNode selection; - private InspectorService inspectorService; - private InspectorObjectGroupManager selectionGroups; - private StreamSubscription onStructuredErrorsStream; - - public InspectorGroupManagerService(Project project) { - FlutterAppManager.getInstance(project).getActiveAppAsStream().listen( - this::updateActiveApp, true); - Disposer.register(project, this); - } - - @NotNull - public static InspectorGroupManagerService getInstance(@NotNull final Project project) { - return Objects.requireNonNull(project.getService(InspectorGroupManagerService.class)); - } - - public InspectorService getInspectorService() { - return inspectorService; - } - - public void addListener(@NotNull Listener listener, Disposable disposable) { - synchronized (listeners) { - listeners.add(listener); - } - // Update the listener with the current active state if any. - if (inspectorService != null) { - listener.onInspectorAvailable(inspectorService); - } - if (selection != null) { - listener.onSelectionChanged(selection); - } - Disposer.register(disposable, () -> removeListener(listener)); - } - - private void removeListener(@NotNull Listener listener) { - synchronized (listeners) { - listeners.remove(listener); - } - } - - private void loadSelection() { - selectionGroups.cancelNext(); - - final CompletableFuture pendingSelectionFuture = - selectionGroups.getNext().getSelection(null, InspectorService.FlutterTreeType.widget, true); - selectionGroups.getNext().safeWhenComplete(pendingSelectionFuture, (selection, error) -> { - if (error != null) { - // TODO(jacobr): should we report an error here? - selectionGroups.cancelNext(); - return; - } - - selectionGroups.promoteNext(); - invokeOnAllListeners((listener) -> listener.onSelectionChanged(selection)); - }); - } - - private void requestRepaint(boolean force) { - invokeOnAllListeners((listener) -> listener.requestRepaint(force)); - } - - private void updateActiveApp(FlutterApp app) { - if (app == this.app) { - return; - } - selection = null; - - if (this.app != null && appListener != null) { - this.app.removeStateListener(appListener); - appListener = null; - } - this.app = app; - if (app == null) { - return; - } - started = false; - // start listening for frames, reload and restart events - appListener = new FlutterApp.FlutterAppListener() { - - @Override - public void stateChanged(FlutterApp.State newState) { - if (!started && app.isStarted()) { - started = true; - requestRepaint(false); - } - if (newState == FlutterApp.State.TERMINATING) { - inspectorService = null; - - invokeOnAllListeners((listener) -> listener.onInspectorAvailable(inspectorService)); - } - } - - @Override - public void notifyAppReloaded() { - invokeOnAllListeners(Listener::notifyAppReloaded); - - requestRepaint(true); - } - - @Override - public void notifyAppRestarted() { - invokeOnAllListeners(Listener::notifyAppRestarted); - - requestRepaint(true); - } - - public void notifyVmServiceAvailable(VmService vmService) { - assert (app.getFlutterDebugProcess() != null); - inspectorServiceFuture = app.getFlutterDebugProcess().getInspectorService(); - - AsyncUtils.whenCompleteUiThread(inspectorServiceFuture, (service, error) -> { - invokeOnAllListeners((listener) -> listener.onInspectorAvailable(service)); - - if (inspectorServiceFuture == null || inspectorServiceFuture.getNow(null) != service) return; - inspectorService = service; - selection = null; - selectionGroups = new InspectorObjectGroupManager(inspectorService, "selection"); - // TODO (helin24): The specific stream we're checking here doesn't matter; we need a frame to be available before running - // loadSelection and other tasks. - if (onStructuredErrorsStream != null) { - Disposer.dispose(onStructuredErrorsStream); - } - onStructuredErrorsStream = - app.getVMServiceManager().hasServiceExtension(ServiceExtensions.toggleShowStructuredErrors.getExtension(), (hasData) -> { - if (hasData) { - loadSelection(); - - if (app != InspectorGroupManagerService.this.app) return; - - service.addClient(new InspectorService.InspectorServiceClient() { - @Override - public void onInspectorSelectionChanged(boolean uiAlreadyUpdated, boolean textEditorUpdated) { - loadSelection(); - } - - @Override - public void onFlutterFrame() { - invokeOnAllListeners(Listener::onFlutterFrame); - } - - @Override - public CompletableFuture onForceRefresh() { - requestRepaint(true); - // Return null instead of a CompletableFuture as the - // InspectorService should not wait for our client to be ready. - return null; - } - }); - } - }); - }); - } - }; - - app.addStateListener(appListener); - if (app.getFlutterDebugProcess() != null) { - appListener.notifyVmServiceAvailable(null); - } - } - - private void invokeOnAllListeners(InvokeListener callback) { - AsyncUtils.invokeLater(() -> { - final ArrayList cachedListeners; - synchronized (listeners) { - cachedListeners = Lists.newArrayList(listeners); - } - for (Listener listener : cachedListeners) { - callback.run(listener); - } - }); - } - - @Override - public void dispose() { - synchronized (listeners) { - listeners.clear(); - } - if (app != null) { - this.app.removeStateListener(appListener); - } - this.app = null; - appListener = null; - if (onStructuredErrorsStream != null) { - Disposer.dispose(onStructuredErrorsStream); - onStructuredErrorsStream = null; - } - } -} diff --git a/flutter-idea/src/io/flutter/inspector/InspectorObjectGroupManager.java b/flutter-idea/src/io/flutter/inspector/InspectorObjectGroupManager.java deleted file mode 100644 index ec722d2983..0000000000 --- a/flutter-idea/src/io/flutter/inspector/InspectorObjectGroupManager.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2018 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.inspector; - -import io.flutter.inspector.InspectorService.ObjectGroup; - -import java.util.concurrent.CompletableFuture; - -/** - * Manager that simplifies preventing memory leaks when using the - * InspectorService. - *

- * This class is designed for the use case where you want to manage - * object references associated with the current displayed UI and object - * references associated with the candidate next frame of UI to display. Once - * the next frame is ready, you determine whether you want to display it and - * discard the current frame and promote the next frame to the the current - * frame if you want to display the next frame otherwise you discard the next - * frame. - *

- * To use this class load all data you want for the next frame by using - * the object group specified by getNext() and then if you decide to switch - * to display that frame, call promoteNext() otherwise call clearNext(). - */ -public class InspectorObjectGroupManager { - private final InspectorService inspectorService; - private final String debugName; - private ObjectGroup current; - private ObjectGroup next; - - private CompletableFuture pendingNextFuture; - - public InspectorService getInspectorService() { - return inspectorService; - } - - public InspectorObjectGroupManager(InspectorService inspectorService, String debugName) { - this.inspectorService = inspectorService; - this.debugName = debugName; - } - - public CompletableFuture getPendingUpdateDone() { - if (pendingNextFuture != null) { - return pendingNextFuture; - } - if (next == null) { - // There is no pending update. - return CompletableFuture.completedFuture(null); - } - - pendingNextFuture = new CompletableFuture<>(); - return pendingNextFuture; - } - - public ObjectGroup getCurrent() { - if (current == null) { - current = inspectorService.createObjectGroup(debugName); - } - return current; - } - - public ObjectGroup getNext() { - if (next == null) { - next = inspectorService.createObjectGroup(debugName); - } - return next; - } - - public void clear(boolean isolateStopped) { - if (isolateStopped) { - // The Dart VM will handle GCing the underlying memory. - current = null; - setNextNull(); - } - else { - clearCurrent(); - cancelNext(); - } - } - - public void promoteNext() { - clearCurrent(); - current = next; - setNextNull(); - } - - private void clearCurrent() { - if (current != null) { - current.dispose(); - current = null; - } - } - - public void cancelNext() { - if (next != null) { - next.dispose(); - setNextNull(); - } - } - - private void setNextNull() { - next = null; - if (pendingNextFuture != null) { - pendingNextFuture.complete(null); - pendingNextFuture = null; - } - } -} diff --git a/flutter-idea/src/io/flutter/inspector/InspectorService.java b/flutter-idea/src/io/flutter/inspector/InspectorService.java deleted file mode 100644 index 5a120d3456..0000000000 --- a/flutter-idea/src/io/flutter/inspector/InspectorService.java +++ /dev/null @@ -1,1468 +0,0 @@ -/* - * Copyright 2017 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.inspector; - -import com.google.common.base.Joiner; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.xdebugger.XSourcePosition; -import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; -import com.intellij.xdebugger.impl.XSourcePositionImpl; -import com.jetbrains.lang.dart.psi.DartCallExpression; -import com.jetbrains.lang.dart.psi.DartExpression; -import com.jetbrains.lang.dart.psi.DartReferenceExpression; -import io.flutter.bazel.Workspace; -import io.flutter.bazel.WorkspaceCache; -import io.flutter.pub.PubRoot; -import io.flutter.run.FlutterDebugProcess; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.JsonUtils; -import io.flutter.utils.StreamSubscription; -import io.flutter.utils.VmServiceListenerAdapter; -import io.flutter.vmService.ServiceExtensions; -import io.flutter.vmService.VmServiceConsumers; -import io.flutter.vmService.frame.DartVmServiceValue; -import org.dartlang.analysis.server.protocol.FlutterOutline; -import org.dartlang.vm.service.VmService; -import org.dartlang.vm.service.consumer.ServiceExtensionConsumer; -import org.dartlang.vm.service.element.Event; -import org.dartlang.vm.service.element.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.List; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.BiConsumer; -import java.util.function.Supplier; - -/** - * Manages all communication between inspector code running on the DartVM and inspector code running in the IDE. - */ -public class InspectorService implements Disposable { - - public static class Location { - - public Location(@NotNull VirtualFile file, int line, int column, int offset) { - this.file = file; - this.line = line; - this.column = column; - this.offset = offset; - } - - @NotNull private final VirtualFile file; - private final int line; - private final int column; - private final int offset; - - public int getLine() { - return line; - } - - public int getColumn() { - return column; - } - - public int getOffset() { - return offset; - } - - @NotNull - public VirtualFile getFile() { - return file; - } - - @NotNull - public String getPath() { - return toSourceLocationUri(file.getPath()); - } - - @Nullable - public XSourcePosition getXSourcePosition() { - final int line = getLine(); - final int column = getColumn(); - if (line < 0 || column < 0) { - return null; - } - return XSourcePositionImpl.create(file, line - 1, column - 1); - } - - public static InspectorService.Location outlineToLocation(Project project, - VirtualFile file, - FlutterOutline outline, - Document document) { - if (file == null) return null; - if (document == null) return null; - if (outline == null || outline.getClassName() == null) return null; - final int documentLength = document.getTextLength(); - int nodeOffset = Math.max(0, Math.min(outline.getCodeOffset(), documentLength)); - final int nodeEndOffset = Math.max(0, Math.min(outline.getCodeOffset() + outline.getCodeLength(), documentLength)); - - // The DartOutline will give us the offset of - // 'child: Foo.bar(...)' - // but we need the offset of 'bar(...)' for consistentency with the - // Flutter kernel transformer. - if (outline.getClassName() != null) { - final PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); - if (psiFile != null) { - final PsiElement element = psiFile.findElementAt(nodeOffset); - final DartCallExpression callExpression = PsiTreeUtil.getParentOfType(element, DartCallExpression.class); - PsiElement match = null; - if (callExpression != null) { - final DartExpression expression = callExpression.getExpression(); - if (expression instanceof DartReferenceExpression referenceExpression) { - final PsiElement[] children = referenceExpression.getChildren(); - if (children.length > 1) { - // This case handles expressions like 'ClassName.namedConstructor' - // and 'libraryPrefix.ClassName.namedConstructor' - match = children[children.length - 1]; - } - else { - // this case handles the simple 'ClassName' case. - match = referenceExpression; - } - } - } - if (match != null) { - nodeOffset = match.getTextOffset(); - } - } - } - final int line = document.getLineNumber(nodeOffset); - final int lineStartOffset = document.getLineStartOffset(line); - final int column = nodeOffset - lineStartOffset; - return new InspectorService.Location(file, line + 1, column + 1, nodeOffset); - } - - /** - * Returns a location for a FlutterOutline object that makes a best effort - * to be compatible with the locations generated by the flutter kernel - * transformer to track creation locations. - */ - @Nullable - public static InspectorService.Location outlineToLocation(Editor editor, FlutterOutline outline) { - if (!(editor instanceof EditorEx editorEx)) return null; - return outlineToLocation(editor.getProject(), editorEx.getVirtualFile(), outline, editor.getDocument()); - } - } - - private static int nextGroupId = 0; - - public static class InteractiveScreenshot { - InteractiveScreenshot(Screenshot screenshot, ArrayList boxes, ArrayList elements) { - this.screenshot = screenshot; - this.boxes = boxes; - this.elements = elements; - } - - public final Screenshot screenshot; - public final ArrayList boxes; - public final ArrayList elements; - } - - @NotNull private final FlutterApp app; - @NotNull private final FlutterDebugProcess debugProcess; - @NotNull private final VmService vmService; - @NotNull private final Set clients; - @NotNull private final EvalOnDartLibrary inspectorLibrary; - @NotNull private final Set supportedServiceMethods; - - private final StreamSubscription addPubRootDirectoriesSubscription; - - /** - * Convenience ObjectGroup constructor for users who need to use DiagnosticsNode objects before the InspectorService is available. - */ - public static CompletableFuture createGroup( - @NotNull FlutterApp app, @NotNull FlutterDebugProcess debugProcess, - @NotNull VmService vmService, String groupName) { - return create(app, debugProcess, vmService).thenApplyAsync((service) -> service.createObjectGroup(groupName)); - } - - public static CompletableFuture create(@NotNull FlutterApp app, - @NotNull FlutterDebugProcess debugProcess, - @NotNull VmService vmService) { - assert app.getVMServiceManager() != null; - final Set inspectorLibraryNames = new HashSet<>(); - inspectorLibraryNames.add("package:flutter/src/widgets/widget_inspector.dart"); - final EvalOnDartLibrary inspectorLibrary = new EvalOnDartLibrary( - inspectorLibraryNames, - vmService, - app.getVMServiceManager() - ); - final CompletableFuture libraryFuture = - inspectorLibrary.libraryRef.thenComposeAsync((library) -> inspectorLibrary.getLibrary(library, null)); - return libraryFuture.thenComposeAsync((Library library) -> { - for (ClassRef classRef : library.getClasses()) { - if ("WidgetInspectorService".equals(classRef.getName())) { - return inspectorLibrary.getClass(classRef, null).thenApplyAsync((ClassObj classObj) -> { - final Set functionNames = new HashSet<>(); - for (FuncRef funcRef : classObj.getFunctions()) { - functionNames.add(funcRef.getName()); - } - return functionNames; - }); - } - } - throw new RuntimeException("WidgetInspectorService class not found"); - }).thenApplyAsync( - (supportedServiceMethods) -> new InspectorService( - app, debugProcess, vmService, inspectorLibrary, supportedServiceMethods)); - } - - private InspectorService(@NotNull FlutterApp app, - @NotNull FlutterDebugProcess debugProcess, - @NotNull VmService vmService, - @NotNull EvalOnDartLibrary inspectorLibrary, - @NotNull Set supportedServiceMethods) { - this.vmService = vmService; - this.app = app; - this.debugProcess = debugProcess; - this.inspectorLibrary = inspectorLibrary; - this.supportedServiceMethods = supportedServiceMethods; - - clients = new HashSet<>(); - - vmService.addVmServiceListener(new VmServiceListenerAdapter() { - @Override - public void received(String streamId, Event event) { - onVmServiceReceived(streamId, event); - } - - @Override - public void connectionClosed() { - // TODO(jacobr): dispose? - } - }); - - vmService.streamListen(VmService.EXTENSION_STREAM_ID, VmServiceConsumers.EMPTY_SUCCESS_CONSUMER); - - assert (app.getVMServiceManager() != null); - addPubRootDirectoriesSubscription = - app.getVMServiceManager().hasServiceExtension(ServiceExtensions.addPubRootDirectories, (Boolean available) -> { - if (!available) { - return; - } - final Workspace workspace = WorkspaceCache.getInstance(app.getProject()).get(); - final ArrayList rootDirectories = new ArrayList<>(); - if (workspace != null) { - for (VirtualFile root : rootsForProject(app.getProject())) { - final String relativePath = workspace.getRelativePath(root); - // TODO(jacobr): is it an error that the relative path can sometimes be null? - if (relativePath != null) { - rootDirectories.add(Workspace.BAZEL_URI_SCHEME + "/" + relativePath); - } - } - } - else { - for (PubRoot root : app.getPubRoots()) { - String path = root.getRoot().getCanonicalPath(); - if (SystemInfo.isWindows) { - // TODO(jacobr): remove after https://github.com/flutter/flutter-intellij/issues/2217. - // The problem is setPubRootDirectories is currently expecting - // valid URIs as opposed to windows paths. - path = "file:///" + path; - } - rootDirectories.add(path); - } - } - addPubRootDirectories(rootDirectories); - }); - } - - @NotNull - private static List rootsForProject(@NotNull Project project) { - final List result = new ArrayList<>(); - for (Module module : ModuleManager.getInstance(project).getModules()) { - Collections.addAll(result, ModuleRootManager.getInstance(module).getContentRoots()); - } - return result; - } - - /** - * Returns whether to use the VM service extension API or use eval to invoke - * the protocol directly. - *

- * Eval must be used when paused at a breakpoint as the VM Service extensions - * API calls won't execute until after the current frame is done rendering. - * TODO(jacobr): evaluate whether we should really be trying to execute while - * a frame is rendering at all as the Element tree may be in a broken state. - */ - private boolean useServiceExtensionApi() { - return !app.isFlutterIsolateSuspended(); - } - - public boolean isDetailsSummaryViewSupported() { - return hasServiceMethod("getSelectedSummaryWidget"); - } - - public boolean isHotUiScreenMirrorSupported() { - // Somewhat arbitrarily chosen new API that is required for full Hot UI - // support. - return hasServiceMethod("getBoundingBoxes"); - } - - /** - * Use this method to write code that is backwards compatible with versions - * of Flutter that are too old to contain specific service methods. - */ - private boolean hasServiceMethod(String methodName) { - return supportedServiceMethods.contains(methodName); - } - - @NotNull - public FlutterDebugProcess getDebugProcess() { - return debugProcess; - } - - public FlutterApp getApp() { - return debugProcess.getApp(); - } - - public ObjectGroup createObjectGroup(String debugName) { - return new ObjectGroup(this, debugName); - } - - @NotNull - private EvalOnDartLibrary getInspectorLibrary() { - return inspectorLibrary; - } - - @Override - public void dispose() { - Disposer.dispose(inspectorLibrary); - Disposer.dispose(addPubRootDirectoriesSubscription); - } - - public CompletableFuture forceRefresh() { - final List> futures = new ArrayList<>(); - - for (InspectorServiceClient client : clients) { - final CompletableFuture future = client.onForceRefresh(); - if (future != null && !future.isDone()) { - futures.add(future); - } - } - - if (futures.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); - } - - private void notifySelectionChanged(boolean uiAlreadyUpdated, boolean textEditorUpdated) { - ApplicationManager.getApplication().invokeLater(() -> { - for (InspectorServiceClient client : clients) { - client.onInspectorSelectionChanged(uiAlreadyUpdated, textEditorUpdated); - } - }); - } - - public void addClient(InspectorServiceClient client) { - clients.add(client); - } - - public void removeClient(InspectorServiceClient client) { - clients.remove(client); - } - - private void onVmServiceReceived(String streamId, Event event) { - switch (streamId) { - case VmService.DEBUG_STREAM_ID: { - if (event.getKind() == EventKind.Inspect) { - // Assume the inspector in Flutter DevTools or on the device widget - // inspector has already set the selection on the device so we don't - // have to. Having multiple clients set the selection risks race - // conditions where the selection ping-pongs back and forth. - - // Update the UI in IntelliJ. - notifySelectionChanged(false, false); - } - break; - } - case VmService.EXTENSION_STREAM_ID: { - if ("Flutter.Frame".equals(event.getExtensionKind())) { - ApplicationManager.getApplication().invokeLater(() -> { - for (InspectorServiceClient client : clients) { - client.onFlutterFrame(); - } - }); - } - break; - } - case "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; - try { - path = new URL(fileUri).getFile(); - } - catch (MalformedURLException e) { - return; - } - if (path == null) return; - - VirtualFile file = LocalFileSystem.getInstance().findFileByPath(path); - final int line = JsonUtils.getIntMember(json, "line"); - final int column = JsonUtils.getIntMember(json, "column"); - - ApplicationManager.getApplication().invokeLater(() -> { - if (file != null && line >= 0 && column >= 0) { - XSourcePositionImpl position = XSourcePositionImpl.create(file, line - 1, column - 1); - position.createNavigatable(app.getProject()).navigate(false); - } - }); - } - break; - } - default: - } - } - - CompletableFuture invokeServiceExtensionNoGroup(String methodName, List args) { - final JsonObject params = new JsonObject(); - for (int i = 0; i < args.size(); ++i) { - params.addProperty("arg" + i, args.get(i)); - } - return invokeServiceExtensionNoGroup(methodName, params); - } - - private CompletableFuture addPubRootDirectories(List rootDirectories) { - return invokeServiceExtensionNoGroup("addPubRootDirectories", rootDirectories).thenApplyAsync((ignored) -> null); - } - - CompletableFuture invokeServiceExtensionNoGroup(String methodName, JsonObject params) { - return invokeServiceExtensionHelper(methodName, params); - } - - private CompletableFuture invokeServiceExtensionHelper(String methodName, JsonObject params) { - // Workaround null values turning into the string "null" when using the VM Service extension protocol. - final ArrayList keysToRemove = new ArrayList<>(); - - for (String key : JsonUtils.getKeySet(params)) { - if (params.get(key).isJsonNull()) { - keysToRemove.add(key); - } - } - for (String key : keysToRemove) { - params.remove(key); - } - final CompletableFuture ret = new CompletableFuture<>(); - vmService.callServiceExtension( - getInspectorLibrary().getIsolateId(), ServiceExtensions.inspectorPrefix + methodName, params, - new ServiceExtensionConsumer() { - @Override - public void received(JsonObject object) { - if (object == null) { - ret.complete(null); - } - else { - ret.complete(object.get("result")); - } - } - - @Override - public void onError(RPCError error) { - ret.completeExceptionally(new RuntimeException("RPCError calling " + methodName + ": " + error.getMessage())); - } - } - ); - return ret; - } - - /** - * Class managing a group of inspector objects that can be freed by - * a single call to dispose(). - * After dispose is called, all pending requests made with the ObjectGroup - * will be skipped. This means that clients should not have to write any - * special logic to handle orphaned requests. - *

- * safeWhenComplete is the recommended way to await futures returned by the - * ObjectGroup as with that method the callback will be skipped if the - * ObjectGroup is disposed making it easy to get the correct behavior of - * skipping orphaned requests. Otherwise, code needs to handle getting back - * futures that return null values for requests from disposed ObjectGroup - * objects. - */ - @SuppressWarnings("CodeBlock2Expr") - public class ObjectGroup implements Disposable { - final InspectorService service; - /** - * Object group all objects in this arena are allocated with. - */ - final String groupName; - - volatile boolean disposed; - final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - - private ObjectGroup(InspectorService service, String debugName) { - this.service = service; - this.groupName = debugName + "_" + nextGroupId; - nextGroupId++; - } - - public InspectorService getInspectorService() { - return service; - } - - /** - * Once an ObjectGroup has been disposed, all methods returning - * DiagnosticsNode objects will return a placeholder dummy node and all methods - * returning lists or maps will return empty lists and all other methods will - * return null. Generally code should never call methods on a disposed object - * group but sometimes due to chained futures that can be difficult to avoid - * and it is simpler return an empty result that will be ignored anyway than to - * attempt carefully cancel futures. - */ - @Override - public void dispose() { - if (disposed) { - return; - } - lock.writeLock().lock(); - invokeVoidServiceMethod("disposeGroup", groupName); - disposed = true; - lock.writeLock().unlock(); - } - - private CompletableFuture nullIfDisposed(Supplier> supplier) { - lock.readLock().lock(); - if (disposed) { - lock.readLock().unlock(); - return CompletableFuture.completedFuture(null); - } - - try { - return supplier.get(); - } - finally { - lock.readLock().unlock(); - } - } - - private T nullValueIfDisposed(Supplier supplier) { - lock.readLock().lock(); - if (disposed) { - lock.readLock().unlock(); - return null; - } - - try { - return supplier.get(); - } - finally { - lock.readLock().unlock(); - } - } - - private void skipIfDisposed(Runnable runnable) { - lock.readLock().lock(); - if (disposed) { - return; - } - - try { - runnable.run(); - } - finally { - lock.readLock().unlock(); - } - } - - public CompletableFuture getPropertyLocation(InstanceRef instanceRef, String name) { - return nullIfDisposed(() -> getInstance(instanceRef) - .thenComposeAsync((Instance instance) -> nullValueIfDisposed(() -> getPropertyLocationHelper(instance.getClassRef(), name)))); - } - - public CompletableFuture getPropertyLocationHelper(ClassRef classRef, String name) { - return nullIfDisposed(() -> inspectorLibrary.getClass(classRef, this).thenComposeAsync((ClassObj clazz) -> { - return nullIfDisposed(() -> { - for (FuncRef f : clazz.getFunctions()) { - // TODO(pq): check for private properties that match name. - if (f.getName().equals(name)) { - return inspectorLibrary.getFunc(f, this).thenComposeAsync((Func func) -> nullIfDisposed(() -> { - final SourceLocation location = func.getLocation(); - return inspectorLibrary.getSourcePosition(debugProcess, location.getScript(), location.getTokenPos(), this); - })); - } - } - final ClassRef superClass = clazz.getSuperClass(); - return superClass == null ? CompletableFuture.completedFuture(null) : getPropertyLocationHelper(superClass, name); - }); - })); - } - - public CompletableFuture getRoot(FlutterTreeType type) { - // There is no excuse to call this method on a disposed group. - assert (!disposed); - return switch (type) { - case widget -> getRootWidget(); - case renderObject -> getRootRenderObject(); - }; - } - - /** - * Invokes a static method on the WidgetInspectorService class passing in the specified - * arguments. - *

- * Intent is we could refactor how the API is invoked by only changing this call. - */ - CompletableFuture invokeEval(String methodName) { - return nullIfDisposed(() -> invokeEval(methodName, groupName)); - } - - CompletableFuture invokeEval(String methodName, String arg1) { - return nullIfDisposed( - () -> getInspectorLibrary().eval("WidgetInspectorService.instance." + methodName + "(\"" + arg1 + "\")", null, this)); - } - - CompletableFuture invokeVmServiceExtension(String methodName) { - return invokeVmServiceExtension(methodName, groupName); - } - - CompletableFuture invokeVmServiceExtension(String methodName, String objectGroup) { - final JsonObject params = new JsonObject(); - params.addProperty("objectGroup", objectGroup); - return invokeVmServiceExtension(methodName, params); - } - - CompletableFuture invokeVmServiceExtension(String methodName, String arg, String objectGroup) { - final JsonObject params = new JsonObject(); - params.addProperty("arg", arg); - params.addProperty("objectGroup", objectGroup); - return invokeVmServiceExtension(methodName, params); - } - - // All calls to invokeVmServiceExtension bottom out to this call. - CompletableFuture invokeVmServiceExtension(String methodName, JsonObject paramsMap) { - return getInspectorLibrary().addRequest( - this, - methodName, - () -> invokeServiceExtensionHelper(methodName, paramsMap) - ); - } - - CompletableFuture invokeVmServiceExtension(String methodName, InspectorInstanceRef arg) { - if (arg == null || arg.getId() == null) { - return invokeVmServiceExtension(methodName, null, groupName); - } - return invokeVmServiceExtension(methodName, arg.getId(), groupName); - } - - private void addLocationToParams(Location location, JsonObject params) { - if (location == null) return; - params.addProperty("file", location.getPath()); - params.addProperty("line", location.getLine()); - params.addProperty("column", location.getColumn()); - } - - public CompletableFuture> getElementsAtLocation(Location location, int count) { - final JsonObject params = new JsonObject(); - addLocationToParams(location, params); - params.addProperty("count", count); - params.addProperty("groupName", groupName); - - return parseDiagnosticsNodesDaemon( - inspectorLibrary.invokeServiceMethod("ext.flutter.inspector.getElementsAtLocation", params).thenApplyAsync((o) -> { - if (o == null) return null; - return o.get("result"); - }), null); - } - - public CompletableFuture> getBoundingBoxes(DiagnosticsNode root, DiagnosticsNode target) { - final JsonObject params = new JsonObject(); - if (root == null || target == null || root.getValueRef() == null || target.getValueRef() == null) { - return CompletableFuture.completedFuture(new ArrayList<>()); - } - params.addProperty("rootId", root.getValueRef().getId()); - params.addProperty("targetId", target.getValueRef().getId()); - params.addProperty("groupName", groupName); - - return parseDiagnosticsNodesDaemon( - inspectorLibrary.invokeServiceMethod("ext.flutter.inspector.getBoundingBoxes", params).thenApplyAsync((o) -> { - if (o == null) return null; - return o.get("result"); - }), null); - } - - public CompletableFuture> hitTest(DiagnosticsNode root, - double dx, - double dy, - String file, - int startLine, - int endLine) { - final JsonObject params = new JsonObject(); - if (root == null || root.getValueRef() == null) { - return CompletableFuture.completedFuture(new ArrayList<>()); - } - params.addProperty("id", root.getValueRef().getId()); - params.addProperty("dx", dx); - params.addProperty("dy", dy); - if (file != null) { - params.addProperty("file", file); - } - - if (startLine >= 0 && endLine >= 0) { - params.addProperty("startLine", startLine); - params.addProperty("endLine", endLine); - } - - params.addProperty("groupName", groupName); - - return parseDiagnosticsNodesDaemon( - inspectorLibrary.invokeServiceMethod("ext.flutter.inspector.hitTest", params).thenApplyAsync((o) -> { - if (o == null) return null; - return o.get("result"); - }), null); - } - - public CompletableFuture setColorProperty(DiagnosticsNode target, Color color) { - // We implement this method directly here rather than landing it in - // package:flutter as the right long term solution is to optimize hot reloads of single property changes. - - // This method only supports Container and Text widgets and will intentionally fail for all other cases. - - if (target == null || target.getValueRef() == null || color == null) return CompletableFuture.completedFuture(false); - - final String command = - "final object = WidgetInspectorService.instance.toObject('" + - target.getValueRef().getId() + - "');" + - "if (object is! Element) return false;\n" + - "final Element element = object;\n" + - "final color = Color.fromARGB(" + - color.getAlpha() + - "," + - color.getRed() + - "," + - color.getGreen() + - "," + - color.getBlue() + - ");\n" + - "RenderObject render = element?.renderObject;\n" + - "\n" + - "if (render is RenderParagraph) {\n" + - " RenderParagraph paragraph = render;\n" + - " final InlineSpan inlineSpan = paragraph.text;\n" + - " if (inlineSpan is! TextSpan) return false;\n" + - " final TextSpan existing = inlineSpan;\n" + - " paragraph.text = TextSpan(text: existing.text,\n" + - " children: existing.children,\n" + - " style: existing.style.copyWith(color: color),\n" + - " recognizer: existing.recognizer,\n" + - " semanticsLabel: existing.semanticsLabel,\n" + - " );\n" + - " return true;\n" + - "} else {\n" + - " RenderDecoratedBox findFirstMatching(Element root) {\n" + - " RenderDecoratedBox match = null;\n" + - " void _matchHelper(Element e) {\n" + - " if (match != null || !identical(e, root) && _isLocalCreationLocation(e)) return;\n" + - " final r = e.renderObject;\n" + - " if (r is RenderDecoratedBox) {\n" + - " match = r;\n" + - " return;\n" + - " }\n" + - " e.visitChildElements(_matchHelper);\n" + - " }\n" + - " _matchHelper(root);\n" + - " return match;\n" + - " }\n" + - "\n" + - " final RenderDecoratedBox render = findFirstMatching(element);\n" + - " if (render != null) {\n" + - " final BoxDecoration existingDecoration = render.decoration;\n" + - " BoxDecoration decoration;\n" + - " if (existingDecoration is BoxDecoration) {\n" + - " decoration = existingDecoration.copyWith(color: color);\n" + - " } else if (existingDecoration == null) {\n" + - " decoration = BoxDecoration(color: color);\n" + - " }\n" + - " if (decoration != null) {\n" + - " render.decoration = decoration;\n" + - " return true;\n" + - " }\n" + - " }\n" + - "}\n" + - "return false;\n"; - - return evaluateCustomApiHelper(command, new HashMap<>()).thenApplyAsync((instanceRef) -> { - return instanceRef != null && "true".equals(instanceRef.getValueAsString()); - }); - } - - private CompletableFuture evaluateCustomApiHelper(String command, Map scope) { - // Avoid running command if we interrupted executing code as results will - // be weird. Repeatedly run the command until we hit idle. - // We cannot execute the command at a later point due to eval bugs where - // the VM crashes executing a closure created by eval asynchronously. - if (isDisposed()) return CompletableFuture.completedFuture(null); - - final ArrayList lines = new ArrayList<>(); - lines.add("((){"); - lines.add("if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle) return null;"); - - - final String[] commandLines = command.split("\n"); - Collections.addAll(lines, commandLines); - lines.add(")()"); - - // Strip out line breaks as that makes the VM evaluate expression api unhappy. - final String expression = Joiner.on("").join(lines); - return evalWithRetry(expression, scope); - } - - private CompletableFuture evalWithRetry(String expression, Map scope) { - if (isDisposed()) return CompletableFuture.completedFuture(null); - - return inspectorLibrary.eval(expression, scope, this).thenComposeAsync( - (instanceRef) -> { - if (instanceRef == null) { - // A null value indicates the request was cancelled. - return CompletableFuture.completedFuture(null); - } - if (instanceRef.isNull()) { - // An InstanceRef with an explicitly null return value indicates we should issue the request again. - return evalWithRetry(expression, scope); - } - return CompletableFuture.completedFuture(instanceRef); - } - ); - } - - public CompletableFuture getScreenshotAtLocation( - Location location, - int count, - int width, - int height, - double maxPixelRatio) { - final JsonObject params = new JsonObject(); - addLocationToParams(location, params); - params.addProperty("count", count); - params.addProperty("width", width); - params.addProperty("height", height); - params.addProperty("maxPixelRatio", maxPixelRatio); - params.addProperty("groupName", groupName); - return nullIfDisposed(() -> { - return inspectorLibrary.invokeServiceMethod("ext.flutter.inspector.screenshotAtLocation", params).thenApplyAsync( - (JsonObject response) -> { - if (response == null || response.get("result").isJsonNull()) { - // No screenshot available. - return null; - } - final JsonObject result = response.getAsJsonObject("result"); - Screenshot screenshot = null; - final JsonElement screenshotJson = result.get("screenshot"); - if (screenshotJson != null && !screenshotJson.isJsonNull()) { - screenshot = getScreenshotFromJson(screenshotJson.getAsJsonObject()); - } - return new InteractiveScreenshot( - screenshot, - parseDiagnosticsNodesHelper(result.get("boxes"), null), - parseDiagnosticsNodesHelper(result.get("elements"), null) - ); - }); - }); - } - - public CompletableFuture getScreenshot(InspectorInstanceRef ref, int width, int height, double maxPixelRatio) { - final JsonObject params = new JsonObject(); - params.addProperty("width", width); - params.addProperty("height", height); - params.addProperty("maxPixelRatio", maxPixelRatio); - params.addProperty("id", ref.getId()); - - return nullIfDisposed( - () -> inspectorLibrary.invokeServiceMethod("ext.flutter.inspector.screenshot", params).thenApplyAsync((JsonObject response) -> { - if (response == null || response.get("result").isJsonNull()) { - // No screenshot avaiable. - return null; - } - final JsonObject result = response.getAsJsonObject("result"); - - return getScreenshotFromJson(result); - })); - } - - @NotNull - private Screenshot getScreenshotFromJson(JsonObject result) { - final String imageString = result.getAsJsonPrimitive("image").getAsString(); - // create a buffered image - final Base64.Decoder decoder = Base64.getDecoder(); - final byte[] imageBytes = decoder.decode(imageString); - final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(imageBytes); - final BufferedImage image; - try { - image = ImageIO.read(byteArrayInputStream); - byteArrayInputStream.close(); - } - catch (IOException e) { - throw new RuntimeException("Error decoding image: " + e.getMessage()); - } - - final TransformedRect transformedRect = new TransformedRect(result.getAsJsonObject("transformedRect")); - return new Screenshot(image, transformedRect); - } - - CompletableFuture invokeEval(String methodName, InspectorInstanceRef arg) { - return nullIfDisposed(() -> { - if (arg == null || arg.getId() == null) { - return getInspectorLibrary().eval("WidgetInspectorService.instance." + methodName + "(null, \"" + groupName + "\")", null, this); - } - return getInspectorLibrary() - .eval("WidgetInspectorService.instance." + methodName + "(\"" + arg.getId() + "\", \"" + groupName + "\")", null, this); - }); - } - - /** - * Call a service method passing in an VM Service instance reference. - *

- * This call is useful when receiving an "inspect" event from the - * VM Service and future use cases such as inspecting a Widget from the - * IntelliJ watch window. - *

- * This method will always need to use the VM Service as the input - * parameter is an VM Service InstanceRef.. - */ - CompletableFuture invokeServiceMethodOnRefEval(String methodName, InstanceRef arg) { - return nullIfDisposed(() -> { - final HashMap scope = new HashMap<>(); - if (arg == null) { - return getInspectorLibrary().eval("WidgetInspectorService.instance." + methodName + "(null, \"" + groupName + "\")", scope, this); - } - scope.put("arg1", arg.getId()); - return getInspectorLibrary().eval("WidgetInspectorService.instance." + methodName + "(arg1, \"" + groupName + "\")", scope, this); - }); - } - - CompletableFuture parseDiagnosticsNodeVmService(CompletableFuture instanceRefFuture) { - return nullIfDisposed(() -> instanceRefFuture.thenComposeAsync(this::parseDiagnosticsNodeVmService)); - } - - /** - * Returns a CompletableFuture with a Map of property names to VM Service - * InstanceRef objects. This method is shorthand for individually evaluating - * each of the getters specified by property names. - *

- * It would be nice if the VM Service protocol provided a built in method - * to get InstanceRef objects for a list of properties but this is - * sufficient although slightly less efficient. The VM Service protocol - * does provide fast access to all fields as part of an Instance object - * but that is inadequate as for many Flutter data objects that we want - * to display visually we care about properties that are not necessarily - * fields. - *

- * The future will immediately complete to null if the inspectorInstanceRef is null. - */ - public CompletableFuture> getDartObjectProperties( - InspectorInstanceRef inspectorInstanceRef, final String[] propertyNames) { - return nullIfDisposed( - () -> toVmServiceInstanceRef(inspectorInstanceRef).thenComposeAsync((InstanceRef instanceRef) -> nullIfDisposed(() -> { - final StringBuilder sb = new StringBuilder(); - final List propertyAccessors = new ArrayList<>(); - final String objectName = "that"; - for (String propertyName : propertyNames) { - propertyAccessors.add(objectName + "." + propertyName); - } - sb.append("["); - sb.append(Joiner.on(',').join(propertyAccessors)); - sb.append("]"); - final Map scope = new HashMap<>(); - scope.put(objectName, instanceRef.getId()); - return getInstance(inspectorLibrary.eval(sb.toString(), scope, this)).thenApplyAsync( - (Instance instance) -> nullValueIfDisposed(() -> { - // We now have an instance object that is a Dart array of all the - // property values. Convert it back to a map from property name to - // property values. - - final Map properties = new HashMap<>(); - final ElementList values = instance.getElements(); - assert (values.size() == propertyNames.length); - for (int i = 0; i < propertyNames.length; ++i) { - properties.put(propertyNames[i], values.get(i)); - } - return properties; - })); - }))); - } - - public CompletableFuture toVmServiceInstanceRef(InspectorInstanceRef inspectorInstanceRef) { - return nullIfDisposed(() -> invokeEval("toObject", inspectorInstanceRef)); - } - - private CompletableFuture getInstance(InstanceRef instanceRef) { - return nullIfDisposed(() -> getInspectorLibrary().getInstance(instanceRef, this)); - } - - CompletableFuture getInstance(CompletableFuture instanceRefFuture) { - return nullIfDisposed(() -> instanceRefFuture.thenComposeAsync(this::getInstance)); - } - - CompletableFuture parseDiagnosticsNodeVmService(InstanceRef instanceRef) { - return nullIfDisposed(() -> instanceRefToJson(instanceRef).thenApplyAsync(this::parseDiagnosticsNodeHelper)); - } - - CompletableFuture parseDiagnosticsNodeDaemon(CompletableFuture json) { - return nullIfDisposed(() -> json.thenApplyAsync(this::parseDiagnosticsNodeHelper)); - } - - DiagnosticsNode parseDiagnosticsNodeHelper(JsonElement jsonElement) { - return nullValueIfDisposed(() -> { - if (jsonElement == null || jsonElement.isJsonNull()) { - return null; - } - return new DiagnosticsNode(jsonElement.getAsJsonObject(), this, false, null); - }); - } - - CompletableFuture instanceRefToJson(CompletableFuture instanceRefFuture) { - return nullIfDisposed(() -> instanceRefFuture.thenComposeAsync(this::instanceRefToJson)); - } - - /** - * Requires that the InstanceRef is really referring to a String that is valid JSON. - */ - CompletableFuture instanceRefToJson(InstanceRef instanceRef) { - if (instanceRef.getValueAsString() != null && !instanceRef.getValueAsStringIsTruncated()) { - // In some situations, the string may already be fully populated. - final JsonElement json = JsonUtils.parseString(instanceRef.getValueAsString()); - return CompletableFuture.completedFuture(json); - } - else { - // Otherwise, retrieve the full value of the string. - return nullIfDisposed(() -> getInspectorLibrary().getInstance(instanceRef, this).thenApplyAsync((Instance instance) -> { - return nullValueIfDisposed(() -> { - final String json = instance.getValueAsString(); - return JsonUtils.parseString(json); - }); - })); - } - } - - CompletableFuture> parseDiagnosticsNodesVmService(InstanceRef instanceRef, DiagnosticsNode parent) { - return nullIfDisposed(() -> instanceRefToJson(instanceRef).thenApplyAsync((JsonElement jsonElement) -> { - return nullValueIfDisposed(() -> { - final JsonArray jsonArray = jsonElement != null ? jsonElement.getAsJsonArray() : null; - return parseDiagnosticsNodesHelper(jsonArray, parent); - }); - })); - } - - ArrayList parseDiagnosticsNodesHelper(JsonElement jsonObject, DiagnosticsNode parent) { - return parseDiagnosticsNodesHelper(jsonObject != null && !jsonObject.isJsonNull() ? jsonObject.getAsJsonArray() : null, parent); - } - - ArrayList parseDiagnosticsNodesHelper(JsonArray jsonArray, DiagnosticsNode parent) { - return nullValueIfDisposed(() -> { - if (jsonArray == null) { - return null; - } - final ArrayList nodes = new ArrayList<>(); - for (JsonElement element : jsonArray) { - nodes.add(new DiagnosticsNode(element.getAsJsonObject(), this, false, parent)); - } - return nodes; - }); - } - - /** - * Converts an inspector ref to value suitable for use by generic intellij - * debugging tools. - *

- * Warning: FlutterVmServiceValue references do not make any lifetime guarantees - * so code keeping them around for a long period of time must be prepared to - * handle reference expiration gracefully. - */ - public CompletableFuture toDartVmServiceValue(InspectorInstanceRef inspectorInstanceRef) { - return invokeEval("toObject", inspectorInstanceRef).thenApplyAsync( - (InstanceRef instanceRef) -> nullValueIfDisposed(() -> { - //noinspection CodeBlock2Expr - return new DartVmServiceValue(debugProcess, inspectorLibrary.getIsolateId(), "inspectedObject", instanceRef, null, null, false); - })); - } - - /** - * Converts an inspector ref to value suitable for use by generic intellij - * debugging tools. - *

- * Warning: FlutterVmServiceValue references do not make any lifetime guarantees - * so code keeping them around for a long period of time must be prepared to - * handle reference expiration gracefully. - */ - public CompletableFuture toDartVmServiceValueForSourceLocation(InspectorInstanceRef inspectorInstanceRef) { - return invokeEval("toObjectForSourceLocation", inspectorInstanceRef).thenApplyAsync( - (InstanceRef instanceRef) -> nullValueIfDisposed(() -> { - //noinspection CodeBlock2Expr - return new DartVmServiceValue(debugProcess, inspectorLibrary.getIsolateId(), "inspectedObject", instanceRef, null, null, false); - })); - } - - CompletableFuture> parseDiagnosticsNodesVmService(CompletableFuture instanceRefFuture, - DiagnosticsNode parent) { - return nullIfDisposed( - () -> instanceRefFuture.thenComposeAsync((instanceRef) -> parseDiagnosticsNodesVmService(instanceRef, parent))); - } - - CompletableFuture> parseDiagnosticsNodesDaemon(CompletableFuture jsonFuture, - DiagnosticsNode parent) { - return nullIfDisposed(() -> jsonFuture.thenApplyAsync((json) -> parseDiagnosticsNodesHelper(json, parent))); - } - - CompletableFuture> getChildren(InspectorInstanceRef instanceRef, - boolean summaryTree, - DiagnosticsNode parent) { - if (isDetailsSummaryViewSupported()) { - return getListHelper(instanceRef, summaryTree ? "getChildrenSummaryTree" : "getChildrenDetailsSubtree", parent); - } - else { - return getListHelper(instanceRef, "getChildren", parent); - } - } - - CompletableFuture> getProperties(InspectorInstanceRef instanceRef) { - return getListHelper(instanceRef, "getProperties", null); - } - - private CompletableFuture> getListHelper( - InspectorInstanceRef instanceRef, String methodName, DiagnosticsNode parent) { - return nullIfDisposed(() -> { - if (useServiceExtensionApi()) { - return parseDiagnosticsNodesDaemon(invokeVmServiceExtension(methodName, instanceRef), parent); - } - else { - return parseDiagnosticsNodesVmService(invokeEval(methodName, instanceRef), parent); - } - }); - } - - public CompletableFuture invokeServiceMethodReturningNode(String methodName) { - return nullIfDisposed(() -> { - if (useServiceExtensionApi()) { - return parseDiagnosticsNodeDaemon(invokeVmServiceExtension(methodName)); - } - else { - return parseDiagnosticsNodeVmService(invokeEval(methodName)); - } - }); - } - - public CompletableFuture invokeServiceMethodReturningNode(String methodName, InspectorInstanceRef ref) { - return nullIfDisposed(() -> { - if (useServiceExtensionApi()) { - return parseDiagnosticsNodeDaemon(invokeVmServiceExtension(methodName, ref)); - } - else { - return parseDiagnosticsNodeVmService(invokeEval(methodName, ref)); - } - }); - } - - public CompletableFuture invokeVoidServiceMethod(String methodName, String arg1) { - return nullIfDisposed(() -> { - if (useServiceExtensionApi()) { - return invokeVmServiceExtension(methodName, arg1).thenApply((ignored) -> null); - } - else { - return invokeEval(methodName, arg1).thenApply((ignored) -> null); - } - }); - } - - public CompletableFuture invokeVoidServiceMethod(String methodName, InspectorInstanceRef ref) { - return nullIfDisposed(() -> { - if (useServiceExtensionApi()) { - return invokeVmServiceExtension(methodName, ref).thenApply((ignored) -> null); - } - else { - return invokeEval(methodName, ref).thenApply((ignored) -> null); - } - }); - } - - public CompletableFuture getRootWidget() { - return invokeServiceMethodReturningNode(isDetailsSummaryViewSupported() ? "getRootWidgetSummaryTree" : "getRootWidget"); - } - - public CompletableFuture getElementForScreenshot() { - return invokeServiceMethodReturningNode("getElementForScreenshot"); - } - - public CompletableFuture getSummaryTreeWithoutIds() { - return parseDiagnosticsNodeDaemon(invokeVmServiceExtension("getRootWidgetSummaryTree", new JsonObject())); - } - - public CompletableFuture getRootRenderObject() { - assert (!disposed); - return invokeServiceMethodReturningNode("getRootRenderObject"); - } - - public CompletableFuture> getParentChain(DiagnosticsNode target) { - return nullIfDisposed(() -> { - if (useServiceExtensionApi()) { - return parseDiagnosticsPathDaemon(invokeVmServiceExtension("getParentChain", target.getValueRef())); - } - else { - return parseDiagnosticsPathVmService(invokeEval("getParentChain", target.getValueRef())); - } - }); - } - - CompletableFuture> parseDiagnosticsPathVmService(CompletableFuture instanceRefFuture) { - return nullIfDisposed(() -> instanceRefFuture.thenComposeAsync(this::parseDiagnosticsPathVmService)); - } - - private CompletableFuture> parseDiagnosticsPathVmService(InstanceRef pathRef) { - return nullIfDisposed(() -> instanceRefToJson(pathRef).thenApplyAsync(this::parseDiagnosticsPathHelper)); - } - - CompletableFuture> parseDiagnosticsPathDaemon(CompletableFuture jsonFuture) { - return nullIfDisposed(() -> jsonFuture.thenApplyAsync(this::parseDiagnosticsPathHelper)); - } - - private ArrayList parseDiagnosticsPathHelper(JsonElement jsonElement) { - return nullValueIfDisposed(() -> { - final JsonArray jsonArray = jsonElement.getAsJsonArray(); - final ArrayList pathNodes = new ArrayList<>(); - for (JsonElement element : jsonArray) { - pathNodes.add(new DiagnosticsPathNode(element.getAsJsonObject(), this)); - } - return pathNodes; - }); - } - - public CompletableFuture getSelection(DiagnosticsNode previousSelection, FlutterTreeType treeType, boolean localOnly) { - // There is no reason to allow calling this method on a disposed group. - assert (!disposed); - return nullIfDisposed(() -> { - CompletableFuture result = null; - final InspectorInstanceRef previousSelectionRef = previousSelection != null ? previousSelection.getDartDiagnosticRef() : null; - - result = switch (treeType) { - case widget -> - invokeServiceMethodReturningNode(localOnly ? "getSelectedSummaryWidget" : "getSelectedWidget", previousSelectionRef); - case renderObject -> invokeServiceMethodReturningNode("getSelectedRenderObject", previousSelectionRef); - }; - return result.thenApplyAsync((DiagnosticsNode newSelection) -> nullValueIfDisposed(() -> { - if (newSelection != null && newSelection.getDartDiagnosticRef().equals(previousSelectionRef)) { - return previousSelection; - } - else { - return newSelection; - } - })); - }); - } - - public void setSelection(InspectorInstanceRef selection, boolean uiAlreadyUpdated, boolean textEditorUpdated) { - if (disposed) { - return; - } - if (selection == null || selection.getId() == null) { - return; - } - if (useServiceExtensionApi()) { - handleSetSelectionDaemon(invokeVmServiceExtension("setSelectionById", selection), uiAlreadyUpdated, textEditorUpdated); - } - else { - handleSetSelectionVmService(invokeEval("setSelectionById", selection), uiAlreadyUpdated, textEditorUpdated); - } - } - - public void setSelection(Location location, boolean uiAlreadyUpdated, boolean textEditorUpdated) { - if (disposed) { - return; - } - if (location == null) { - return; - } - if (useServiceExtensionApi()) { - final JsonObject params = new JsonObject(); - addLocationToParams(location, params); - handleSetSelectionDaemon(invokeVmServiceExtension("setSelectionByLocation", params), uiAlreadyUpdated, textEditorUpdated); - } - // skip if the vm service is expected to be used directly. - } - - /** - * Helper when we need to set selection given an VM Service InstanceRef - * instead of an InspectorInstanceRef. - */ - public void setSelection(InstanceRef selection, boolean uiAlreadyUpdated, boolean textEditorUpdated) { - // There is no excuse for calling setSelection using a disposed ObjectGroup. - assert (!disposed); - // This call requires the VM Service protocol as an VM Service InstanceRef is specified. - handleSetSelectionVmService(invokeServiceMethodOnRefEval("setSelection", selection), uiAlreadyUpdated, textEditorUpdated); - } - - private void handleSetSelectionVmService(CompletableFuture setSelectionResult, - boolean uiAlreadyUpdated, - boolean textEditorUpdated) { - // TODO(jacobr): we need to cancel if another inspect request comes in while we are trying this one. - skipIfDisposed(() -> setSelectionResult.thenAcceptAsync((InstanceRef instanceRef) -> skipIfDisposed(() -> { - handleSetSelectionHelper("true".equals(instanceRef.getValueAsString()), uiAlreadyUpdated, textEditorUpdated); - }))); - } - - private void handleSetSelectionHelper(boolean selectionChanged, boolean uiAlreadyUpdated, boolean textEditorUpdated) { - if (selectionChanged) { - notifySelectionChanged(uiAlreadyUpdated, textEditorUpdated); - } - } - - private void handleSetSelectionDaemon(CompletableFuture setSelectionResult, - boolean uiAlreadyUpdated, - boolean textEditorUpdated) { - skipIfDisposed(() -> - // TODO(jacobr): we need to cancel if another inspect request comes in while we are trying this one. - setSelectionResult.thenAcceptAsync( - (JsonElement json) -> skipIfDisposed( - () -> handleSetSelectionHelper(json.getAsBoolean(), uiAlreadyUpdated, textEditorUpdated))) - ); - } - - public CompletableFuture> getEnumPropertyValues(InspectorInstanceRef ref) { - return nullIfDisposed(() -> { - if (ref == null || ref.getId() == null) { - return CompletableFuture.completedFuture(null); - } - return getInstance(toVmServiceInstanceRef(ref)) - .thenComposeAsync( - (Instance instance) -> nullIfDisposed(() -> getInspectorLibrary().getClass(instance.getClassRef(), this).thenApplyAsync( - (ClassObj clazz) -> nullValueIfDisposed(() -> { - final Map properties = new LinkedHashMap<>(); - for (FieldRef field : clazz.getFields()) { - final String name = field.getName(); - if (name.startsWith("_")) { - // Needed to filter out _deleted_enum_sentinel synthetic property. - // If showing private enum values is useful we could special case - // just the _deleted_enum_sentinel property name. - continue; - } - if (name.equals("values")) { - // Need to filter out the synthetic "values" member. - // TODO(jacobr): detect that this properties return type is - // different and filter that way. - continue; - } - if (field.isConst() && field.isStatic()) { - properties.put(field.getName(), field.getDeclaredType()); - } - } - return properties; - }) - ))); - }); - } - - public CompletableFuture getDetailsSubtree(DiagnosticsNode node) { - if (node == null) { - return CompletableFuture.completedFuture(null); - } - return nullIfDisposed(() -> invokeServiceMethodReturningNode("getDetailsSubtree", node.getDartDiagnosticRef())); - } - - public XDebuggerEditorsProvider getEditorsProvider() { - return InspectorService.this.getDebugProcess().getEditorsProvider(); - } - - FlutterApp getApp() { - return InspectorService.this.getApp(); - } - - /** - * Await a Future invoking the callback on completion on the UI thread only if the - * rhis ObjectGroup is still alive when the Future completes. - */ - public void safeWhenComplete(CompletableFuture future, BiConsumer action) { - if (future == null) { - return; - } - future.whenCompleteAsync( - (T value, Throwable throwable) -> skipIfDisposed(() -> { - ApplicationManager.getApplication().invokeLater(() -> { - action.accept(value, throwable); - }); - }) - ); - } - - public boolean isDisposed() { - return disposed; - } - } - - public static String getFileUriPrefix() { - return SystemInfo.isWindows ? "file:///" : "file://"; - } - - // TODO(jacobr): remove this method as soon as the - // track-widget-creation kernel transformer is fixed to return paths instead - // of URIs. - public static String toSourceLocationUri(String path) { - return getFileUriPrefix() + path; - } - - public static String fromSourceLocationUri(String path, Project project) { - final Workspace workspace = WorkspaceCache.getInstance(project).get(); - if (workspace != null) { - path = workspace.convertPath(path); - } - - final String filePrefix = getFileUriPrefix(); - return path.startsWith(filePrefix) ? path.substring(filePrefix.length()) : path; - } - - public enum FlutterTreeType { - widget("Widget"), - renderObject("Render"); - // TODO(jacobr): add semantics, and layer trees. - - public final String displayName; - - FlutterTreeType(String displayName) { - this.displayName = displayName; - } - } - - public interface InspectorServiceClient { - void onInspectorSelectionChanged(boolean uiAlreadyUpdated, boolean textEditorUpdated); - - void onFlutterFrame(); - - CompletableFuture onForceRefresh(); - } -} diff --git a/flutter-idea/src/io/flutter/inspector/InspectorSourceLocation.java b/flutter-idea/src/io/flutter/inspector/InspectorSourceLocation.java deleted file mode 100644 index b549ce2c62..0000000000 --- a/flutter-idea/src/io/flutter/inspector/InspectorSourceLocation.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2018 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.inspector; - -import com.google.gson.JsonObject; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.xdebugger.XSourcePosition; -import com.intellij.xdebugger.impl.XSourcePositionImpl; -import io.flutter.utils.JsonUtils; - -public class InspectorSourceLocation { - private final JsonObject json; - private final InspectorSourceLocation parent; - private final Project project; - - public InspectorSourceLocation(JsonObject json, InspectorSourceLocation parent, Project project) { - this.json = json; - this.parent = parent; - this.project = project; - } - - public String getFile() { - return JsonUtils.getStringMember(json, "file"); - } - - public VirtualFile getVirtualFile() { - String fileName = getFile(); - if (fileName == null) { - return parent != null ? parent.getVirtualFile() : null; - } - - fileName = InspectorService.fromSourceLocationUri(fileName, project); - - final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName); - if (virtualFile != null && !virtualFile.exists()) { - return null; - } - return virtualFile; - } - - public int getLine() { - return JsonUtils.getIntMember(json, "line"); - } - - public String getName() { - return JsonUtils.getStringMember(json, "name"); - } - - public int getColumn() { - return JsonUtils.getIntMember(json, "column"); - } - - public XSourcePosition getXSourcePosition() { - final VirtualFile file = getVirtualFile(); - if (file == null) { - return null; - } - final int line = getLine(); - final int column = getColumn(); - if (line < 0 || column < 0) { - return null; - } - return XSourcePositionImpl.create(file, line - 1, column - 1); - } - - public InspectorService.Location getLocation() { - return new InspectorService.Location(getVirtualFile(), getLine(), getColumn(), getXSourcePosition().getOffset()); - } -} diff --git a/flutter-idea/src/io/flutter/inspector/Screenshot.java b/flutter-idea/src/io/flutter/inspector/Screenshot.java deleted file mode 100644 index c9f81323fd..0000000000 --- a/flutter-idea/src/io/flutter/inspector/Screenshot.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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.inspector; - -import java.awt.image.BufferedImage; - -public class Screenshot { - public final BufferedImage image; - public final TransformedRect transformedRect; - - Screenshot(BufferedImage image, TransformedRect transformedRect) { - this.image = image; - this.transformedRect = transformedRect; - } -} diff --git a/flutter-idea/src/io/flutter/inspector/TreeUtils.java b/flutter-idea/src/io/flutter/inspector/TreeUtils.java deleted file mode 100644 index 3d1257708d..0000000000 --- a/flutter-idea/src/io/flutter/inspector/TreeUtils.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2018 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.inspector; - -import javax.swing.tree.DefaultMutableTreeNode; - -public class TreeUtils { - public static DiagnosticsNode maybeGetDiagnostic(DefaultMutableTreeNode treeNode) { - if (treeNode == null) { - return null; - } - final Object userObject = treeNode.getUserObject(); - return (userObject instanceof DiagnosticsNode) ? (DiagnosticsNode)userObject : null; - } -} diff --git a/flutter-idea/src/io/flutter/inspector/DiagnosticLevel.java b/flutter-idea/src/io/flutter/logging/DiagnosticLevel.java similarity index 97% rename from flutter-idea/src/io/flutter/inspector/DiagnosticLevel.java rename to flutter-idea/src/io/flutter/logging/DiagnosticLevel.java index be3fff035a..8136848ce2 100644 --- a/flutter-idea/src/io/flutter/inspector/DiagnosticLevel.java +++ b/flutter-idea/src/io/flutter/logging/DiagnosticLevel.java @@ -1,9 +1,9 @@ /* - * Copyright 2017 The Chromium Authors. All rights reserved. + * 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.inspector; +package io.flutter.logging; /** * The various priority levels used to filter which diagnostics are shown and diff --git a/flutter-idea/src/io/flutter/inspector/DiagnosticsNode.java b/flutter-idea/src/io/flutter/logging/DiagnosticsNode.java similarity index 73% rename from flutter-idea/src/io/flutter/inspector/DiagnosticsNode.java rename to flutter-idea/src/io/flutter/logging/DiagnosticsNode.java index 1ff5dde275..44ff7d7eda 100644 --- a/flutter-idea/src/io/flutter/inspector/DiagnosticsNode.java +++ b/flutter-idea/src/io/flutter/logging/DiagnosticsNode.java @@ -1,9 +1,9 @@ /* - * Copyright 2017 The Chromium Authors. All rights reserved. + * 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.inspector; +package io.flutter.logging; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -56,9 +56,7 @@ public class DiagnosticsNode { private static final Logger LOG = Logger.getInstance(DiagnosticsNode.class); private static final CustomIconMaker iconMaker = new CustomIconMaker(); - private final FlutterApp app; - private InspectorSourceLocation location; private DiagnosticsNode parent; private CompletableFuture propertyDocFuture; @@ -66,24 +64,18 @@ public class DiagnosticsNode { private ArrayList cachedProperties; public DiagnosticsNode(JsonObject json, - InspectorService.ObjectGroup inspectorService, boolean isProperty, DiagnosticsNode parent) { this.json = json; - this.inspectorService = CompletableFuture.completedFuture(inspectorService); this.isProperty = isProperty; - this.app = inspectorService.getApp(); } public DiagnosticsNode(JsonObject json, - @NotNull CompletableFuture inspectorService, FlutterApp app, boolean isProperty, DiagnosticsNode parent) { this.json = json; - this.inspectorService = inspectorService; this.isProperty = isProperty; - this.app = app; } @Override @@ -104,18 +96,6 @@ public String toString() { return name + getSeparator() + ' ' + getDescription(); } - public boolean isDisposed() { - try { - final InspectorService.ObjectGroup service = inspectorService.getNow(null); - // If the service isn't created yet it can't have been disposed. - return service != null && service.isDisposed(); - } - catch (Exception e) { - // If the service can't be acquired then it is disposed. - return false; - } - } - /** * Set this node's parent. */ @@ -442,25 +422,10 @@ boolean hasException() { return json.has("exception"); } - public boolean hasCreationLocation() { - return location != null || json.has("creationLocation"); - } - public int getLocationId() { return JsonUtils.getIntMember(json, "locationId"); } - public InspectorSourceLocation getCreationLocation() { - if (location != null) { - return location; - } - if (!hasCreationLocation()) { - return null; - } - location = new InspectorSourceLocation(json.getAsJsonObject("creationLocation"), null, app.getProject()); - return location; - } - /** * String representation of the type of the property [value]. *

@@ -504,12 +469,6 @@ public boolean getIsDiagnosticableValue() { return getBooleanMember("isDiagnosticableValue", false); } - /** - * Service used to retrieve more detailed information about the value of the property and its children and properties. - */ - @NotNull - private final CompletableFuture inspectorService; - /** * JSON describing the diagnostic node. */ @@ -580,58 +539,6 @@ public boolean isEnumProperty() { return type != null && type.startsWith("EnumProperty<"); } - /** - * Returns a list of raw Dart property values of the Dart value of this - * property that are useful for custom display of the property value. - * For example, get the red, green, and blue components of color. - *

- * Unfortunately we cannot just use the list of fields from the Observatory - * Instance object for the Dart value because much of the relevant - * information to display good visualizations of Flutter values is stored - * in properties not in fields. - */ - public CompletableFuture> getValueProperties() { - final InspectorInstanceRef valueRef = getValueRef(); - if (valueProperties == null) { - if (getPropertyType() == null || valueRef == null || valueRef.getId() == null) { - valueProperties = CompletableFuture.completedFuture(null); - return valueProperties; - } - if (isEnumProperty()) { - // Populate all the enum property values. - valueProperties = inspectorService.thenComposeAsync((service) -> { - if (service == null) { - return null; - } - return service.getEnumPropertyValues(getValueRef()); - }); - return valueProperties; - } - - final String[] propertyNames; - // Add more cases here as visual displays for additional Dart objects - // are added. - switch (getPropertyType()) { - case "Color": - propertyNames = new String[]{"red", "green", "blue", "alpha"}; - break; - case "IconData": - propertyNames = new String[]{"codePoint"}; - break; - default: - valueProperties = CompletableFuture.completedFuture(null); - return valueProperties; - } - valueProperties = inspectorService.thenComposeAsync((service) -> { - if (service == null) { - return null; - } - return service.getDartObjectProperties(getValueRef(), propertyNames); - }); - } - return valueProperties; - } - public JsonObject getValuePropertiesJson() { return json.getAsJsonObject("valueProperties"); } @@ -675,20 +582,12 @@ public CompletableFuture> getChildren() { final JsonArray jsonArray = json.get("children").getAsJsonArray(); final ArrayList nodes = new ArrayList<>(); for (JsonElement element : jsonArray) { - final DiagnosticsNode child = new DiagnosticsNode(element.getAsJsonObject(), inspectorService, app, false, parent); + final DiagnosticsNode child = new DiagnosticsNode(element.getAsJsonObject(), false, parent); child.setParent(this); nodes.add(child); } children = CompletableFuture.completedFuture(nodes); } - else if (hasChildren()) { - children = inspectorService.thenComposeAsync((service) -> { - if (service == null) { - return null; - } - return service.getChildren(getDartDiagnosticRef(), isSummaryTree(), this); - }); - } else { // Known to have no children so we can provide the children immediately. children = CompletableFuture.completedFuture(new ArrayList<>()); @@ -722,73 +621,13 @@ public ArrayList getInlineProperties() { if (json.has("properties")) { final JsonArray jsonArray = json.get("properties").getAsJsonArray(); for (JsonElement element : jsonArray) { - cachedProperties.add(new DiagnosticsNode(element.getAsJsonObject(), inspectorService, app, true, parent)); + cachedProperties.add(new DiagnosticsNode(element.getAsJsonObject(), true, parent)); } } } return cachedProperties; } - public CompletableFuture> getProperties(InspectorService.ObjectGroup objectGroup) { - return objectGroup.getProperties(getDartDiagnosticRef()); - } - - @NotNull - public CompletableFuture getPropertyDoc() { - if (propertyDocFuture == null) { - propertyDocFuture = createPropertyDocFuture(); - } - return propertyDocFuture; - } - - private CompletableFuture createPropertyDocFuture() { - final DiagnosticsNode parent = getParent(); - if (parent != null) { - return inspectorService.thenComposeAsync((service) -> service.toDartVmServiceValueForSourceLocation(parent.getValueRef()) - .thenComposeAsync((DartVmServiceValue vmValue) -> { - if (vmValue == null) { - return CompletableFuture.completedFuture(null); - } - return inspectorService.getNow(null).getPropertyLocation(vmValue.getInstanceRef(), getName()) - .thenApplyAsync((XSourcePosition sourcePosition) -> { - if (sourcePosition != null) { - final VirtualFile file = sourcePosition.getFile(); - final int offset = sourcePosition.getOffset(); - - final Project project = getProject(file); - if (project != null) { - final List hovers = - DartAnalysisServerService.getInstance(project).analysis_getHover(file, offset); - if (!hovers.isEmpty()) { - return hovers.get(0).getDartdoc(); - } - } - } - return "Unable to find property source"; - }); - })).exceptionally(t -> { - LOG.info("ignoring exception from toObjectForSourceLocation: " + t.toString()); - return null; - }); - } - - return CompletableFuture.completedFuture("Unable to find property source"); - } - - @Nullable - private Project getProject(@NotNull VirtualFile file) { - return app != null ? app.getProject() : ProjectUtil.guessProjectForFile(file); - } - - private void setCreationLocation(InspectorSourceLocation location) { - this.location = location; - } - - @NotNull - public CompletableFuture getInspectorService() { - return inspectorService; - } - @Nullable public Icon getIcon() { return iconMaker.fromWidgetName(getDescription()); @@ -823,36 +662,4 @@ public boolean identicalDisplay(DiagnosticsNode node) { } return true; } - - /** - * Await a Future invoking the callback on completion on the UI thread only if the - * InspectorService group is still alive when the Future completes. - */ - public void safeWhenComplete(CompletableFuture future, BiConsumer action) { - try { - final InspectorService.ObjectGroup service = inspectorService.getNow(null); - if (service != null) { - service.safeWhenComplete(future, action); - return; - } - inspectorService.whenCompleteAsync((group, t) -> { - if (group == null || t != null) { - return; - } - group.safeWhenComplete(future, action); - }); - } - catch (Exception ignored) { - // Nothing to do if the service can't be acquired. - } - } - - public void setSelection(InspectorInstanceRef ref, boolean uiAlreadyUpdated) { - inspectorService.thenAcceptAsync((service) -> { - if (service == null) { - return; - } - service.setSelection(ref, uiAlreadyUpdated, false); - }); - } } diff --git a/flutter-idea/src/io/flutter/inspector/DiagnosticsTreeStyle.java b/flutter-idea/src/io/flutter/logging/DiagnosticsTreeStyle.java similarity index 96% rename from flutter-idea/src/io/flutter/inspector/DiagnosticsTreeStyle.java rename to flutter-idea/src/io/flutter/logging/DiagnosticsTreeStyle.java index 4e421ae5ab..711cbdea50 100644 --- a/flutter-idea/src/io/flutter/inspector/DiagnosticsTreeStyle.java +++ b/flutter-idea/src/io/flutter/logging/DiagnosticsTreeStyle.java @@ -1,9 +1,9 @@ /* - * Copyright 2017 The Chromium Authors. All rights reserved. + * 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.inspector; +package io.flutter.logging; /** * Styles for displaying a node in a [DiagnosticsNode] tree. diff --git a/flutter-idea/src/io/flutter/logging/FlutterConsoleLogManager.java b/flutter-idea/src/io/flutter/logging/FlutterConsoleLogManager.java index ebe038843d..15a0b949a3 100644 --- a/flutter-idea/src/io/flutter/logging/FlutterConsoleLogManager.java +++ b/flutter-idea/src/io/flutter/logging/FlutterConsoleLogManager.java @@ -22,7 +22,6 @@ import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; @@ -31,10 +30,6 @@ import com.intellij.util.concurrency.QueueProcessor; import io.flutter.FlutterUtils; import io.flutter.devtools.DevToolsUtils; -import io.flutter.inspector.DiagnosticLevel; -import io.flutter.inspector.DiagnosticsNode; -import io.flutter.inspector.DiagnosticsTreeStyle; -import io.flutter.inspector.InspectorService; import io.flutter.jxbrowser.JxBrowserManager; import io.flutter.run.daemon.FlutterApp; import io.flutter.settings.FlutterSettings; @@ -95,8 +90,6 @@ public static void initConsolePreferences() { private int frameErrorCount = 0; - private CompletableFuture objectGroup; - public FlutterConsoleLogManager(@NotNull ConsoleView console, @NotNull FlutterApp app) { this.console = console; this.app = app; @@ -128,40 +121,11 @@ public void notifyAppRestarted() { } } - /** - * This method is used to delay construction of the InspectorService ObjectGroup instance until its first used. - *

- * This ensures that the app's VMService field has been populated. - */ - @Nullable - private CompletableFuture getCreateInspectorGroup() { - if (objectGroup == null) { - if (app.getFlutterDebugProcess() == null || app.getVmService() == null) { - return null; - } - - // TODO(devoncarew): This creates a new InspectorService but may not dispose of it. - objectGroup = InspectorService.createGroup(app, app.getFlutterDebugProcess(), app.getVmService(), "console-group"); - objectGroup.whenCompleteAsync((group, error) -> { - if (group != null) { - Disposer.register(app, group.getInspectorService()); - } - }); - } - - return objectGroup; - } - public void handleFlutterErrorEvent(@NotNull Event event) { - final CompletableFuture objectGroup = getCreateInspectorGroup(); - if (objectGroup == null) { - return; - } - try { final ExtensionData extensionData = event.getExtensionData(); final JsonObject jsonObject = extensionData.getJson().getAsJsonObject(); - final DiagnosticsNode diagnosticsNode = new DiagnosticsNode(jsonObject, objectGroup, app, false, null); + final DiagnosticsNode diagnosticsNode = new DiagnosticsNode(jsonObject, app, false, null); if (FlutterSettings.getInstance().isShowStructuredErrors()) { queueLength.incrementAndGet(); @@ -256,8 +220,9 @@ private void processFlutterErrorEvent(@NotNull DiagnosticsNode diagnosticsNode) if (StringUtil.equals("ErrorSummary", property.getType())) { errorSummary = property.getDescription(); - } else if (StringUtil.equals("DevToolsDeepLinkProperty", property.getType()) && - FlutterUtils.embeddedBrowserAvailable(JxBrowserManager.getInstance().getStatus())) { + } + else if (StringUtil.equals("DevToolsDeepLinkProperty", property.getType()) && + FlutterUtils.embeddedBrowserAvailable(JxBrowserManager.getInstance().getStatus())) { showDeepLinkNotification(property, errorSummary); continue; } diff --git a/flutter-idea/src/io/flutter/logging/FlutterErrorHelper.java b/flutter-idea/src/io/flutter/logging/FlutterErrorHelper.java index 2755f6a2d8..5d42eaf242 100644 --- a/flutter-idea/src/io/flutter/logging/FlutterErrorHelper.java +++ b/flutter-idea/src/io/flutter/logging/FlutterErrorHelper.java @@ -7,8 +7,6 @@ import com.google.common.annotations.VisibleForTesting; import com.intellij.openapi.util.text.StringUtil; -import io.flutter.inspector.DiagnosticLevel; -import io.flutter.inspector.DiagnosticsNode; import java.util.regex.Pattern; diff --git a/flutter-idea/src/io/flutter/inspector/InspectorInstanceRef.java b/flutter-idea/src/io/flutter/logging/InspectorInstanceRef.java similarity index 92% rename from flutter-idea/src/io/flutter/inspector/InspectorInstanceRef.java rename to flutter-idea/src/io/flutter/logging/InspectorInstanceRef.java index fc268673f4..b46c836440 100644 --- a/flutter-idea/src/io/flutter/inspector/InspectorInstanceRef.java +++ b/flutter-idea/src/io/flutter/logging/InspectorInstanceRef.java @@ -1,9 +1,9 @@ /* - * Copyright 2017 The Chromium Authors. All rights reserved. + * 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.inspector; +package io.flutter.logging; import java.util.Objects; diff --git a/flutter-idea/src/io/flutter/inspector/TransformedRect.java b/flutter-idea/src/io/flutter/logging/TransformedRect.java similarity index 87% rename from flutter-idea/src/io/flutter/inspector/TransformedRect.java rename to flutter-idea/src/io/flutter/logging/TransformedRect.java index 3ffd5da454..d38edac88e 100644 --- a/flutter-idea/src/io/flutter/inspector/TransformedRect.java +++ b/flutter-idea/src/io/flutter/logging/TransformedRect.java @@ -1,9 +1,9 @@ /* - * Copyright 2019 The Chromium Authors. All rights reserved. + * 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.inspector; +package io.flutter.logging; import com.google.gson.JsonObject; diff --git a/flutter-idea/src/io/flutter/preview/PreviewView.java b/flutter-idea/src/io/flutter/preview/PreviewView.java new file mode 100644 index 0000000000..e69de29bb2 diff --git a/flutter-idea/src/io/flutter/preview/WidgetEditToolbar.java b/flutter-idea/src/io/flutter/preview/WidgetEditToolbar.java deleted file mode 100644 index f7472bbdd3..0000000000 --- a/flutter-idea/src/io/flutter/preview/WidgetEditToolbar.java +++ /dev/null @@ -1,338 +0,0 @@ -/* - * 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.preview; - -import com.google.common.util.concurrent.Uninterruptibles; -import com.intellij.ide.DataManager; -import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.TransactionGuard; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.TextEditor; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.vfs.VirtualFile; -import com.jetbrains.lang.dart.assists.AssistUtils; -import com.jetbrains.lang.dart.assists.DartSourceEditException; -import icons.FlutterIcons; -import io.flutter.FlutterMessages; -import io.flutter.dart.FlutterDartAnalysisServer; -import io.flutter.inspector.InspectorGroupManagerService; -import io.flutter.inspector.InspectorService; -import io.flutter.run.FlutterReloadManager; -import io.flutter.run.daemon.FlutterApp; -import io.flutter.utils.EventStream; -import org.dartlang.analysis.server.protocol.FlutterOutline; -import org.dartlang.analysis.server.protocol.SourceChange; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import java.awt.*; -import java.util.List; -import java.util.*; -import java.util.concurrent.TimeUnit; - -/** - * Toolbar containing all the widget refactor actions. - *

- * This toolbar is extracted out from the OutlineView so that it can be reused - * anywhere we want to expose Flutter widget refactors. For example, as a - * toolbar of a property editing popop exposed directly in a code editor. - */ -public class WidgetEditToolbar { - private class QuickAssistAction extends AnAction { - private final String id; - - QuickAssistAction(@NotNull String id, Icon icon, String assistMessage) { - super(assistMessage, null, icon); - this.id = id; - messageToActionMap.put(assistMessage, this); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final SourceChange change; - synchronized (actionToChangeMap) { - change = actionToChangeMap.get(this); - actionToChangeMap.clear(); - } - if (change != null) { - applyChangeAndShowException(change); - } - } - - @Override - public void update(AnActionEvent e) { - final boolean hasChange = actionToChangeMap.containsKey(this); - e.getPresentation().setEnabled(hasChange); - } - - @Override - public @NotNull ActionUpdateThread getActionUpdateThread() { - return ActionUpdateThread.BGT; - } - - boolean isEnabled() { - return actionToChangeMap.containsKey(this); - } - } - - private class ExtractMethodAction extends AnAction { - private final String id = "dart.assist.flutter.extractMethod"; - - ExtractMethodAction() { - super("Extract Method...", null, FlutterIcons.ExtractMethod); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final AnAction action = ActionManager.getInstance().getAction("ExtractMethod"); - if (action != null) { - final FlutterOutline outline = getWidgetOutline(); - if (outline != null) { - TransactionGuard.submitTransaction(project, () -> { - final Editor editor = getCurrentEditor(); - if (editor == null) { - // It is a race condition if we hit this. Gracefully assume - // the action has just been canceled. - return; - } - final OutlineOffsetConverter converter = new OutlineOffsetConverter(project, activeFile.getValue()); - final int offset = converter.getConvertedOutlineOffset(outline); - editor.getCaretModel().moveToOffset(offset); - - final JComponent editorComponent = editor.getComponent(); - final DataContext editorContext = DataManager.getInstance().getDataContext(editorComponent); - final AnActionEvent editorEvent = AnActionEvent.createFromDataContext(ActionPlaces.UNKNOWN, null, editorContext); - - action.actionPerformed(editorEvent); - }); - } - } - } - - @Override - public void update(AnActionEvent e) { - final boolean isEnabled = isEnabled(); - e.getPresentation().setEnabled(isEnabled); - } - - @Override - public @NotNull ActionUpdateThread getActionUpdateThread() { - return ActionUpdateThread.BGT; - } - - boolean isEnabled() { - return getWidgetOutline() != null && getCurrentEditor() != null; - } - } - - private class ExtractWidgetAction extends AnAction { - private final String id = "dart.assist.flutter.extractwidget"; - - ExtractWidgetAction() { - super("Extract Widget..."); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final AnAction action = ActionManager.getInstance().getAction("Flutter.ExtractWidget"); - if (action != null) { - TransactionGuard.submitTransaction(project, () -> { - final Editor editor = getCurrentEditor(); - if (editor == null) { - // It is a race condition if we hit this. Gracefully assume - // the action has just been canceled. - return; - } - final JComponent editorComponent = editor.getComponent(); - final DataContext editorContext = DataManager.getInstance().getDataContext(editorComponent); - final AnActionEvent editorEvent = AnActionEvent.createFromDataContext(ActionPlaces.UNKNOWN, null, editorContext); - - action.actionPerformed(editorEvent); - }); - } - } - - @Override - public void update(AnActionEvent e) { - final boolean isEnabled = isEnabled(); - e.getPresentation().setEnabled(isEnabled); - } - - @Override - public @NotNull ActionUpdateThread getActionUpdateThread() { - return ActionUpdateThread.BGT; - } - - boolean isEnabled() { - return getWidgetOutline() != null && getCurrentEditor() != null; - } - } - - final QuickAssistAction actionCenter; - final QuickAssistAction actionPadding; - final QuickAssistAction actionColumn; - final QuickAssistAction actionRow; - final QuickAssistAction actionContainer; - final QuickAssistAction actionMoveUp; - final QuickAssistAction actionMoveDown; - final QuickAssistAction actionRemove; - final ExtractMethodAction actionExtractMethod; - final ExtractWidgetAction actionExtractWidget; - private final FlutterDartAnalysisServer flutterAnalysisServer; - private final EventStream activeFile; - private final Map messageToActionMap = new HashMap<>(); - private final Map actionToChangeMap = new HashMap<>(); - private final Project project; - /** - * Wheter to trigger a hot reload after the widget refactor is executed. - */ - private final boolean hotReloadOnAction; - /** - * EventStream providing the outline(s) that are currently considered active. - */ - EventStream> activeOutlines; - private ActionToolbar toolbar; - - public WidgetEditToolbar( - boolean hotReloadOnAction, - EventStream> activeOutlines, - EventStream activeFile, - Project project, - FlutterDartAnalysisServer flutterAnalysisServer - ) { - this.hotReloadOnAction = hotReloadOnAction; - this.project = project; - this.flutterAnalysisServer = flutterAnalysisServer; - this.activeFile = activeFile; - actionCenter = new QuickAssistAction("dart.assist.flutter.wrap.center", FlutterIcons.Center, "Wrap with Center"); - actionPadding = new QuickAssistAction("dart.assist.flutter.wrap.padding", FlutterIcons.Padding, "Wrap with Padding"); - actionColumn = new QuickAssistAction("dart.assist.flutter.wrap.column", FlutterIcons.Column, "Wrap with Column"); - actionRow = new QuickAssistAction("dart.assist.flutter.wrap.row", FlutterIcons.Row, "Wrap with Row"); - actionContainer = new QuickAssistAction("dart.assist.flutter.wrap.container", FlutterIcons.Container, "Wrap with Container"); - actionMoveUp = new QuickAssistAction("dart.assist.flutter.move.up", FlutterIcons.Up, "Move widget up"); - actionMoveDown = new QuickAssistAction("dart.assist.flutter.move.down", FlutterIcons.Down, "Move widget down"); - actionRemove = new QuickAssistAction("dart.assist.flutter.removeWidget", FlutterIcons.RemoveWidget, "Remove this widget"); - actionExtractMethod = new ExtractMethodAction(); - actionExtractWidget = new ExtractWidgetAction(); - - this.activeOutlines = activeOutlines; - activeOutlines.listen(this::activeOutlineChanged); - } - - Editor getCurrentEditor() { - final VirtualFile file = activeFile.getValue(); - if (file == null) { - return null; - } - - final FileEditorManager fileEditorManager = FileEditorManager.getInstance(project); - if(fileEditorManager == null) { - return null; - } - final FileEditor fileEditor = fileEditorManager.getSelectedEditor(file); - if (fileEditor instanceof TextEditor textEditor) { - final Editor editor = textEditor.getEditor(); - if (!editor.isDisposed()) { - return editor; - } - } - return null; - } - - public ActionToolbar getToolbar() { - if (toolbar == null) { - final DefaultActionGroup toolbarGroup = new DefaultActionGroup(); - toolbarGroup.add(actionCenter); - toolbarGroup.add(actionPadding); - toolbarGroup.add(actionColumn); - toolbarGroup.add(actionRow); - toolbarGroup.add(actionContainer); - toolbarGroup.addSeparator(); - toolbarGroup.add(actionExtractMethod); - toolbarGroup.addSeparator(); - toolbarGroup.add(actionMoveUp); - toolbarGroup.add(actionMoveDown); - toolbarGroup.addSeparator(); - toolbarGroup.add(actionRemove); - - toolbar = ActionManager.getInstance().createActionToolbar("PreviewViewToolbar", toolbarGroup, true); - } - return toolbar; - } - - private void activeOutlineChanged(List outlines) { - synchronized (actionToChangeMap) { - actionToChangeMap.clear(); - } - - final VirtualFile selectionFile = activeFile.getValue(); - if (selectionFile != null && !outlines.isEmpty()) { - ApplicationManager.getApplication().executeOnPooledThread(() -> { - final OutlineOffsetConverter converter = new OutlineOffsetConverter(project, activeFile.getValue()); - final FlutterOutline firstOutline = outlines.get(0); - final FlutterOutline lastOutline = outlines.get(outlines.size() - 1); - final int offset = converter.getConvertedOutlineOffset(firstOutline); - final int length = converter.getConvertedOutlineEnd(lastOutline) - offset; - final List changes = flutterAnalysisServer.edit_getAssists(selectionFile, offset, length); - - // If the current file or outline are different, ignore the changes. - // We will eventually get new changes. - final List newOutlines = activeOutlines.getValue(); - if (!Objects.equals(activeFile.getValue(), selectionFile) || !outlines.equals(newOutlines)) { - return; - } - - // Associate changes with actions. - // Actions will be enabled / disabled in background. - for (SourceChange change : changes) { - final AnAction action = messageToActionMap.get(change.getMessage()); - if (action != null) { - actionToChangeMap.put(action, change); - } - } - - // Update actions immediately. - if (getToolbar() != null) { - ApplicationManager.getApplication().invokeLater(() -> getToolbar().updateActionsImmediately()); - } - }); - } - } - - FlutterOutline getWidgetOutline() { - final List outlines = activeOutlines.getValue(); - if (outlines.size() == 1) { - final FlutterOutline outline = outlines.get(0); - if (outline.getDartElement() == null) { - return outline; - } - } - return null; - } - - private void applyChangeAndShowException(SourceChange change) { - ApplicationManager.getApplication().runWriteAction(() -> { - try { - AssistUtils.applySourceChange(project, change, false); - if (hotReloadOnAction) { - final InspectorService inspectorService = InspectorGroupManagerService.getInstance(project).getInspectorService(); - - if (inspectorService != null) { - final ArrayList apps = new ArrayList<>(); - apps.add(inspectorService.getApp()); - FlutterReloadManager.getInstance(project).saveAllAndReloadAll(apps, "Refactor widget"); - } - } - } - catch (DartSourceEditException exception) { - FlutterMessages.showError("Error applying change", exception.getMessage(), project); - } - }); - } -} diff --git a/flutter-idea/src/io/flutter/run/FlutterDebugProcess.java b/flutter-idea/src/io/flutter/run/FlutterDebugProcess.java index 54afde0ea8..9e5d9dc8e2 100644 --- a/flutter-idea/src/io/flutter/run/FlutterDebugProcess.java +++ b/flutter-idea/src/io/flutter/run/FlutterDebugProcess.java @@ -23,7 +23,6 @@ import io.flutter.actions.ReloadFlutterApp; import io.flutter.actions.RestartAllFlutterApps; import io.flutter.actions.RestartFlutterApp; -import io.flutter.inspector.InspectorService; import io.flutter.run.common.RunMode; import io.flutter.run.daemon.FlutterApp; import io.flutter.view.FlutterViewMessages; @@ -35,7 +34,6 @@ import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.concurrent.CompletableFuture; /** * A debug process that handles hot reloads for Flutter. @@ -63,15 +61,6 @@ public FlutterApp getApp() { return app; } - CompletableFuture inspectorService; - - public CompletableFuture getInspectorService() { - if (inspectorService == null && getVmConnected() && app.getVmService() != null) { - inspectorService = InspectorService.create(app, this, app.getVmService()); - } - return inspectorService; - } - @Override protected void onVmConnected(@NotNull VmService vmService) { app.setFlutterDebugProcess(this); diff --git a/flutter-idea/src/io/flutter/view/FlutterView.java b/flutter-idea/src/io/flutter/view/FlutterView.java index 47ffbba1f1..fcea698bf7 100644 --- a/flutter-idea/src/io/flutter/view/FlutterView.java +++ b/flutter-idea/src/io/flutter/view/FlutterView.java @@ -27,17 +27,12 @@ import com.intellij.ui.content.ContentManager; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; -import com.intellij.xdebugger.XSourcePosition; import io.flutter.FlutterBundle; import io.flutter.FlutterUtils; import io.flutter.actions.RefreshToolWindowAction; import io.flutter.bazel.WorkspaceCache; import io.flutter.devtools.DevToolsIdeFeature; import io.flutter.devtools.DevToolsUrl; -import io.flutter.inspector.DiagnosticsNode; -import io.flutter.inspector.InspectorGroupManagerService; -import io.flutter.inspector.InspectorService; -import io.flutter.inspector.InspectorSourceLocation; import io.flutter.jxbrowser.FailureType; import io.flutter.jxbrowser.InstallationFailedReason; import io.flutter.jxbrowser.JxBrowserManager; @@ -55,7 +50,6 @@ import io.flutter.utils.JxBrowserUtils; import io.flutter.utils.LabelInput; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -103,42 +97,18 @@ public class FlutterView implements PersistentStateComponent, private final JxBrowserManager jxBrowserManager; public FlutterView(@NotNull Project project) { - this(project, JxBrowserManager.getInstance(), new JxBrowserUtils(), InspectorGroupManagerService.getInstance(project)); + this(project, JxBrowserManager.getInstance(), new JxBrowserUtils()); } @VisibleForTesting @NonInjectable - protected FlutterView(@NotNull Project project, @NotNull JxBrowserManager jxBrowserManager, JxBrowserUtils jxBrowserUtils, InspectorGroupManagerService inspectorGroupManagerService) { + protected FlutterView(@NotNull Project project, @NotNull JxBrowserManager jxBrowserManager, JxBrowserUtils jxBrowserUtils) { myProject = project; this.jxBrowserUtils = jxBrowserUtils; this.jxBrowserManager = jxBrowserManager; shouldAutoHorizontalScroll.listen(state::setShouldAutoScroll); highlightNodesShownInBothTrees.listen(state::setHighlightNodesShownInBothTrees); - - inspectorGroupManagerService.addListener(new InspectorGroupManagerService.Listener() { - @Override - public void onInspectorAvailable(InspectorService service) { } - - @Override - public void onSelectionChanged(DiagnosticsNode selection) { - if (selection != null) { - final InspectorSourceLocation location = selection.getCreationLocation(); - if (location != null) { - final XSourcePosition sourcePosition = location.getXSourcePosition(); - if (sourcePosition != null) { - sourcePosition.createNavigatable(project).navigate(true); - } - } - if (selection.isCreatedByLocalProject()) { - final XSourcePosition position = selection.getCreationLocation().getXSourcePosition(); - if (position != null) { - position.createNavigatable(project).navigate(false); - } - } - } - } - }, this); } @Override @@ -176,7 +146,7 @@ private void addBrowserInspectorViewContent(FlutterApp app, boolean isEmbedded, DevToolsIdeFeature ideFeature, DevToolsInstance devToolsInstance) { - assert(SwingUtilities.isEventDispatchThread()); + assert (SwingUtilities.isEventDispatchThread()); final ContentManager contentManager = toolWindow.getContentManager(); @@ -226,7 +196,8 @@ private void addBrowserInspectorViewContent(FlutterApp app, } toolWindow.setTitleActions(List.of(new RefreshToolWindowAction(TOOL_WINDOW_ID))); - } else { + } + else { BrowserLauncher.getInstance().browse( new DevToolsUrl.Builder() .setDevToolsHost(devToolsInstance.host) @@ -256,25 +227,10 @@ private Optional embeddedBrowserOptional() { */ public void debugActive(@NotNull FlutterViewMessages.FlutterDebugEvent event) { final FlutterApp app = event.app; - if (app.getFlutterDebugProcess() == null || app.getFlutterDebugProcess().getInspectorService() == null) { + if (app.getFlutterDebugProcess() == null) { return; } - - if (app.getMode().isProfiling() || app.getLaunchMode().isProfiling()) { - ApplicationManager.getApplication().invokeLater(() -> debugActiveHelper(app, null)); - } - else { - AsyncUtils.whenCompleteUiThread( - app.getFlutterDebugProcess().getInspectorService(), - (InspectorService inspectorService, Throwable throwable) -> { - if (throwable != null) { - FlutterUtils.warn(LOG, throwable); - return; - } - - debugActiveHelper(app, inspectorService); - }); - } + ApplicationManager.getApplication().invokeLater(() -> debugActiveHelper(app)); } protected void handleJxBrowserInstalled(FlutterApp app, ToolWindow toolWindow, DevToolsIdeFeature ideFeature) { @@ -294,7 +250,7 @@ private void presentDevTools(FlutterApp app, ToolWindow toolWindow, boolean isEm @VisibleForTesting protected void verifyEventDispatchThread() { - assert(SwingUtilities.isEventDispatchThread()); + assert (SwingUtilities.isEventDispatchThread()); } @VisibleForTesting @@ -320,10 +276,10 @@ protected void openInspectorWithDevTools(FlutterApp app, ToolWindow toolWindow, } private void openInspectorWithDevTools(FlutterApp app, - ToolWindow toolWindow, - boolean isEmbedded, - DevToolsIdeFeature ideFeature, - boolean forceDevToolsRestart) { + ToolWindow toolWindow, + boolean isEmbedded, + DevToolsIdeFeature ideFeature, + boolean forceDevToolsRestart) { AsyncUtils.whenCompleteUiThread( forceDevToolsRestart ? DevToolsService.getInstance(myProject).getDevToolsInstanceWithForcedRestart() @@ -389,23 +345,25 @@ protected void waitForJxBrowserInstallation(FlutterApp app, ToolWindow toolWindo } protected void handleUpdatedJxBrowserStatusOnEventThread( - FlutterApp app, - ToolWindow toolWindow, - JxBrowserStatus jxBrowserStatus, - DevToolsIdeFeature ideFeature) { + FlutterApp app, + ToolWindow toolWindow, + JxBrowserStatus jxBrowserStatus, + DevToolsIdeFeature ideFeature) { AsyncUtils.invokeLater(() -> handleUpdatedJxBrowserStatus(app, toolWindow, jxBrowserStatus, ideFeature)); } protected void handleUpdatedJxBrowserStatus( - FlutterApp app, - ToolWindow toolWindow, - JxBrowserStatus jxBrowserStatus, - DevToolsIdeFeature ideFeature) { + FlutterApp app, + ToolWindow toolWindow, + JxBrowserStatus jxBrowserStatus, + DevToolsIdeFeature ideFeature) { if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLED)) { handleJxBrowserInstalled(app, toolWindow, ideFeature); - } else if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_FAILED)) { + } + else if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_FAILED)) { handleJxBrowserInstallationFailed(app, toolWindow, ideFeature); - } else { + } + else { // newStatus can be null if installation is interrupted or stopped for another reason. presentOpenDevToolsOptionWithMessage(app, toolWindow, INSTALLATION_WAIT_FAILED, ideFeature); } @@ -422,7 +380,8 @@ protected void handleJxBrowserInstallationFailed(FlutterApp app, ToolWindow tool // If the license isn't available, allow the user to open the equivalent page in a non-embedded browser window. inputs.add(new LabelInput("The JxBrowser license could not be found.")); inputs.add(openDevToolsLabel); - } else if (latestFailureReason != null && latestFailureReason.failureType.equals(FailureType.SYSTEM_INCOMPATIBLE)) { + } + else if (latestFailureReason != null && latestFailureReason.failureType.equals(FailureType.SYSTEM_INCOMPATIBLE)) { // If we know the system is incompatible, skip retry link and offer to open in browser. inputs.add(new LabelInput(latestFailureReason.detail)); inputs.add(openDevToolsLabel); @@ -455,7 +414,8 @@ protected void presentClickableLabel(ToolWindow toolWindow, List lab 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); @@ -494,7 +454,7 @@ private void replacePanelLabel(ToolWindow toolWindow, JComponent label) { }); } - private void debugActiveHelper(FlutterApp app, @Nullable InspectorService inspectorService) { + private void debugActiveHelper(FlutterApp app) { final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); if (!(toolWindowManager instanceof ToolWindowManagerEx)) { return; @@ -538,7 +498,8 @@ private void debugActiveHelper(FlutterApp app, @Nullable InspectorService inspec private void displayEmbeddedBrowser(FlutterApp app, ToolWindow toolWindow, DevToolsIdeFeature ideFeature) { if (FlutterSettings.getInstance().isEnableJcefBrowser()) { presentDevTools(app, toolWindow, true, ideFeature); - } else { + } + else { displayEmbeddedBrowserIfJxBrowser(app, toolWindow, ideFeature); } } @@ -555,8 +516,9 @@ else if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_IN_PROGRESS)) { handleJxBrowserInstallationInProgress(app, toolWindow, ideFeature); } else if (jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_FAILED)) { - handleJxBrowserInstallationFailed(app, toolWindow, ideFeature); - } else if (jxBrowserStatus.equals(JxBrowserStatus.NOT_INSTALLED) || jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_SKIPPED)) { + handleJxBrowserInstallationFailed(app, toolWindow, ideFeature); + } + else if (jxBrowserStatus.equals(JxBrowserStatus.NOT_INSTALLED) || jxBrowserStatus.equals(JxBrowserStatus.INSTALLATION_SKIPPED)) { manager.setUp(myProject.getName()); handleJxBrowserInstallationInProgress(app, toolWindow, ideFeature); } diff --git a/flutter-idea/src/io/flutter/vmService/VMServiceManager.java b/flutter-idea/src/io/flutter/vmService/VMServiceManager.java index 20e22956e5..9fd7faa346 100644 --- a/flutter-idea/src/io/flutter/vmService/VMServiceManager.java +++ b/flutter-idea/src/io/flutter/vmService/VMServiceManager.java @@ -12,7 +12,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; -import io.flutter.inspector.EvalOnDartLibrary; import io.flutter.run.daemon.FlutterApp; import io.flutter.utils.EventStream; import io.flutter.utils.StreamSubscription; @@ -145,30 +144,6 @@ public HeapMonitor getHeapMonitor() { } public void addRegisteredExtensionRPCs(Isolate isolate, boolean attach) { - // If attach was called, there is a risk we may never receive a - // Flutter.Frame or Flutter.FirstFrame event so we need to query the - // framework to determine if a frame has already been rendered. - // This check would be safe to do outside of attach mode but is not needed. - if (attach && isolate.getExtensionRPCs() != null && !firstFrameEventReceived) { - final Set bindingLibraryNames = new HashSet<>(); - bindingLibraryNames.add("package:flutter/src/widgets/binding.dart"); - - final EvalOnDartLibrary flutterLibrary = new EvalOnDartLibrary( - bindingLibraryNames, - vmService, - this - ); - flutterLibrary.eval("WidgetsBinding.instance.debugDidSendFirstFrameEvent", null, null).whenCompleteAsync((v, e) -> { - // If there is an error we assume the first frame has been received. - final boolean didSendFirstFrameEvent = e == null || - v == null || - Objects.equals(v.getValueAsString(), "true"); - if (didSendFirstFrameEvent) { - onFrameEventReceived(); - } - Disposer.dispose(flutterLibrary); - }); - } if (isolate.getExtensionRPCs() != null) { for (String extension : isolate.getExtensionRPCs()) { addServiceExtension(extension); diff --git a/flutter-idea/testSrc/unit/io/flutter/view/FlutterViewTest.java b/flutter-idea/testSrc/unit/io/flutter/view/FlutterViewTest.java index f35e5d2ca8..aab3a6d127 100644 --- a/flutter-idea/testSrc/unit/io/flutter/view/FlutterViewTest.java +++ b/flutter-idea/testSrc/unit/io/flutter/view/FlutterViewTest.java @@ -9,8 +9,6 @@ import com.intellij.openapi.wm.ToolWindow; import io.flutter.ObservatoryConnector; import io.flutter.devtools.DevToolsIdeFeature; -import io.flutter.inspector.InspectorGroupManagerService; -import io.flutter.inspector.InspectorService; import io.flutter.jxbrowser.FailureType; import io.flutter.jxbrowser.InstallationFailedReason; import io.flutter.jxbrowser.JxBrowserManager; @@ -31,12 +29,10 @@ public class FlutterViewTest { Project mockProject = mock(Project.class); @Mock FlutterApp mockApp; - @Mock InspectorService mockInspectorService; @Mock ToolWindow mockToolWindow; @Mock ObservatoryConnector mockObservatoryConnector; JxBrowserUtils mockUtils = mock(JxBrowserUtils.class); JxBrowserManager mockJxBrowserManager = mock(JxBrowserManager.class); - InspectorGroupManagerService mockInspectorGroupManagerService = mock(InspectorGroupManagerService.class); @Test public void testHandleJxBrowserInstalled() { @@ -57,7 +53,7 @@ public void testHandleJxBrowserInstallationFailed() { when(mockJxBrowserManager.getLatestFailureReason()).thenReturn(new InstallationFailedReason(FailureType.FILE_DOWNLOAD_FAILED)); // If JxBrowser failed to install, we should show a failure message that allows the user to manually retry. - final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockJxBrowserUtils, mockInspectorGroupManagerService); + final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockJxBrowserUtils); final FlutterView spy = spy(flutterView); doNothing().when(spy).presentClickableLabel( eq(mockToolWindow), @@ -77,7 +73,7 @@ public void testHandleJxBrowserInstallationInProgressWithSuccessfulInstall() { // If the JxBrowser installation is initially in progress, we should show a message about the installation. // If the installation quickly finishes (on the first re-check), then we should call the function to handle successful installation. - final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils, mockInspectorGroupManagerService); + final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils); final FlutterView spy = spy(flutterView); doNothing().when(spy).presentOpenDevToolsOptionWithMessage(any(), any(), any(), any()); @@ -95,7 +91,7 @@ public void testHandleJxBrowserInstallationInProgressWaiting() { // If the JxBrowser installation is in progress and is not finished on the first re-check, we should start a thread to wait for the // installation to finish. - final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils, mockInspectorGroupManagerService); + final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils); final FlutterView spy = spy(flutterView); doNothing().when(spy).presentOpenDevToolsOptionWithMessage(any(), any(), any(), any()); @@ -113,7 +109,7 @@ public void testWaitForJxBrowserInstallationWithoutTimeout() throws TimeoutExcep when(mockJxBrowserManager.waitForInstallation(INSTALLATION_WAIT_LIMIT_SECONDS)).thenReturn(JxBrowserStatus.INSTALLATION_FAILED); // If waiting for JxBrowser installation completes without timing out, then we should return to event thread. - final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils, mockInspectorGroupManagerService); + final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils); final FlutterView spy = spy(flutterView); doNothing().when(spy).handleUpdatedJxBrowserStatusOnEventThread(any(), any(), any(), any()); @@ -130,7 +126,7 @@ public void testWaitForJxBrowserInstallationWithTimeout() throws TimeoutExceptio when(mockJxBrowserManager.waitForInstallation(INSTALLATION_WAIT_LIMIT_SECONDS)).thenThrow(new TimeoutException()); // If the JxBrowser installation doesn't complete on time, we should show a timed out message. - final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils, mockInspectorGroupManagerService); + final FlutterView flutterView = new FlutterView(mockProject, mockJxBrowserManager, mockUtils); final FlutterView spy = spy(flutterView); doNothing().when(spy).presentOpenDevToolsOptionWithMessage(any(), any(), any(), any()); diff --git a/gradle.properties b/gradle.properties index e2f623f424..632ea9bf2a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,14 @@ name = "flutter-intellij -buildSpec=2024.1 +buildSpec=2023.3 flutterPluginVersion=1 ideaProduct=android-studio -ideaVersion=2024.1.2.10 -baseVersion=241.18034.62 -dartPluginVersion=241.17502 +ideaVersion=2023.3.1.16 +baseVersion=233.14808.21 +dartPluginVersion=233.15271 androidPluginVersion= -sinceBuild=241 -untilBuild=241.* -testing=true +sinceBuild=233 +untilBuild=233.* +testing=false kotlin.stdlib.default.dependency=false org.gradle.parallel=true org.gradle.jvmargs=-Xms1024m -Xmx4048m