diff --git a/.gitignore b/.gitignore index 58befc88b2..6153b2c2d8 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ pubspec.lock .idea/ .vscode/ material-design-icons/ +flutter-gui-tests/guitest.log +flutter-gui-tests/.gradle/ diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 96cc43efa6..6caf4dfcdd 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,10 @@ + + + + diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b312839bf..0000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b367b696f6..8cda265111 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,6 @@ script: ./tool/travis.sh env: - DART_BOT=true - CHECK_BOT=true - - IDEA_VERSION=3.3 + - IDEA_VERSION=3.3.2 - IDEA_VERSION=3.4 - - IDEA_VERSION=2018.3 - - IDEA_VERSION=2019.1 + - IDEA_VERSION=3.5 diff --git a/CHANGELOG.md b/CHANGELOG.md index 519fb2b9ce..4e9ceaa7ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,85 @@ +## 35.0 +- Sample panel layout improvements (#3396) +- Remove unneeded logging (#3394) +- Java analysis lints cleanup (#3395) +- Update subscriptions after analysis server restart (#3393) +- Read sample index from flutter_tool call (#3379) +- Update README (#3387) +- Fix unit tests +- Update build for canary 11 (#3380) +- Integration test update (#3374) +- Make the inspector easier to test (#3373) +- Adjust build to make plugin for testing (#3366) +- Address reported Java lints (#3356) +- Adjust build for AS canary 10 +- Address an array index out of bounds (#3355) +- Address an NPE (#3354) +- Upgrade the service protocol library (#3353) +- Address a number format exception (#3352) +- Update how we manipulate the service protocol url (#3351) +- Remove some uses of reflection (#3350) +- Some initial work for FlutterWeb apps (#3342) +- Fix an NPE when sample content generation is disabled (#3336) +- Add inspector dependency to test (#3316) +- Make Dart constructor calls pop out in light mode (#3327) + +## 34.0 +- Update build for Android Studio 3.3.2 and IntelliJ 2019.1 (#3321) +- Fix issue preventing plugin from working in AS Canary 8 (#3321) +- Provides a better display if the variable has a `toStringDeep()` method defined. (#3291) +- Don't show a background square in the inspector summary tree. (#3326) +- Make FlutterModuleUtils consistently robust to disposed projects. (#3323) +- Fix NPE issue sometimes hit evaluating expressions. (#3324) +- Fix widget names. (#3322) +- Make Perf and Inspector views only display when a Flutter app is being debugged. (#3320) +- Support the inspector for flutter_web libraries. (#3310) +- Detect when integrations tests are running (#3308) +- Add in support for reloading and restarting all running apps (#3268) +- Log tree path selection fixes (#3302) +- Throttle logger updates (#3280) +- New method in FlutterUtils: declaresFlutterWeb, this method checks for dependencies: fluttler_web in a pubspec file. (#3275) +- Update a comment in FlutterSaveActionsManager (#3277) +- Remove the second parameter (the Project) from SdkFields constructor, it isn't used anymore. (#3261) +- Add a comment to a recent change (#3267) +- Fix a file handle leak (#3264) +- Port inferPubRootDirectoryIfNeeded from devtools (#3242) +- Add support for matching customized Widget tests. (#3249) +- Hide DevTools debugger when launching from IntelliJ. (#3252) +- Migrate to GearPlain (#3248) +- Minor cleanup (#3247) +- Inline sample index reading (#3245) +- Make a newer daemon protocol field optional (#3230) +- Link to the plugins readme file from the building instructions. (#3222) + +## 33.3 +- Fix an issue with an IllegalArgumentException when running Flutter apps + +## 33.2 +- Support IntelliJ 2018.3.3 + +## 33.1 +- add menu and toolbar button to open Flutter DevTools +- fix Gradle sync issue for Android Studio 3.3.1 +- fix highlighting of the Go link in sample banner + +## 33.0 +- update build for Android Studio 3.3.1 +- reorder console filters so links work +- more intelligently enable support for detaching from Flutter apps on exit +- change the icon used for paint baselines +- prevent bazel test run configurations from generating in a non-bazel workspace +- support 2019.1 eap +- mention 'Dart' in the plugin description +- correct the bazel output for debugging bazel tests +- simplify the bazel parameters we pass to Bazel Run configurations +- pin flutter error events in the log +- propagate node selections to inspector +- link support for log data entries +- fix category cell rendering +- add sample creation banner +- add sample apps to Android Studio New Project Wizard +- update log entry data badge + ## 32.0 - address an NPE in FlutterWidgetPerfManager.java - added overlay renderered for GC, snapshot and memory reset events diff --git a/docs/building.md b/docs/building.md index a96e037d6c..9963cd5a55 100644 --- a/docs/building.md +++ b/docs/building.md @@ -27,4 +27,4 @@ $ gsutil cp gs://flutter_infra/flutter/intellij/ ## The plugin tool Building is done by the `plugin` tool. -See tool/plugin/README.md for details. +See [tool/plugin/README.md](../tool/plugin/README.md) for details. diff --git a/docs/testing.md b/docs/testing.md index 8138793f3a..41f0c8ebbc 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -25,7 +25,8 @@ Validate basic project creation. * Project contents are created. * Verify that a run configuration (`main.dart`) is enabled in the run/debug selector. * Navigation works. - * Open `lib/main.dart` and navigate to `ThemeData`. + * Open `lib/main.dart` and navigate to `Scaffold`, from line 37 or so. + * Verify that the new editor includes a sample creation banner. * There are no analysis errors or warnings. * Pub operations work. * Open `pubspec.yaml` and click the "Packages get" and "Packages upgrade" links. diff --git a/flutter-gui-tests/README.md b/flutter-gui-tests/README.md new file mode 100644 index 0000000000..a1d4e70081 --- /dev/null +++ b/flutter-gui-tests/README.md @@ -0,0 +1,36 @@ +# Flutter Plugin Integration Testing + +On the advice of Jetbrains engineers, we have switched the integration testing module to be +a Gradle-built plugin. This allows the tests to run from anywhere; previously they only ran +when copied into the GUI testing framework module. Thanks to karashevich@jetbrains for +creating the plugin. + +## Usage + +1. Prepare the flutter-intellij plugin. Run `bin/plugin build` in a terminal. If you do not +want to wait for all the distros to be build, consult product-matrix.json to find which version +sets isTestTarget to true. Then you can use it as the value of the -o option to the build command. +For example: `bin/plugin build -o3.5` + +2. Check that the buildPlugin task works normally. Open the Gradle +tool view: View -> Tool Windows -> Gradle. Expamd `Tasks`, then expand `intellij`. +Select `buildPliugin`, right-click and choose `Run ...`. This may take a while initially +as it may have to download some files. + +3. For now, we use the built-in terminal to run tests. Open the terminal emulator in IntelliJ, then: +```bash +cd flutter-gui-tests +./gradlew -Dtest.single=TestSuite clean test +``` + +## Editing + +Currently, the tests need to be edited in a minimal flutter-intellij project. Open the flutter-intellij +project in IntelliJ 2019.1 for stand-alone editing, without Dart or IntelliJ sources (see CONTRIBUTING.md). + +If you want to test recent changes be sure to repeat Step 1 in Usage so you are testing the latest build. + +## Notes + +If the buildPlugin task fails, check for a new version of the Gradle plugin with id org.jetbrains.intellij. +This is likely to be needed if a new version of IntelliJ is downloaded automatically. \ No newline at end of file diff --git a/flutter-gui-tests/build.gradle b/flutter-gui-tests/build.gradle new file mode 100644 index 0000000000..8fe593688e --- /dev/null +++ b/flutter-gui-tests/build.gradle @@ -0,0 +1,98 @@ +plugins { + id 'org.jetbrains.intellij' version '0.4.5' + id 'org.jetbrains.kotlin.jvm' version '1.3.21' +} + +group 'io.flutter.tests.gui' +version '1.0-SNAPSHOT' + +repositories { + mavenLocal() + mavenCentral() + jcenter() + // this repo contains the test target plugin produced by the plugin tool, io.flutter:SNAPSHOT + flatDir dirs: ['../releases/release_master/test_target'] +} + +dependencies { + compile "org.jetbrains.kotlin:kotlin-reflect" + testCompile group: 'junit', name: 'junit', version: '4.12' + testCompile fileTree(include: ['*.jar'], dir: '../releases/release_master/2019.1') + testImplementation fileTree(include: ['*.jar'], dir: '../releases/release_master/2019.1') +} + +sourceSets { + main { + java.srcDir 'src' + kotlin.srcDir 'src' + resources.srcDir 'res' + } + test { + java.srcDir 'testSrc' + kotlin.srcDir 'testSrc' + resources.srcDir 'testData' + } +} + +intellij { + plugins 'com.intellij.testGuiFramework:0.9.44.1@nightly', 'Dart:191.5849.16', 'io.flutter:SNAPSHOT' + if (System.getProperty("idea.gui.test.alternativeIdePath") != null) { + alternativeIdePath System.getProperty("idea.gui.test.alternativeIdePath") + } + updateSinceUntilBuild false +// version 'LATEST-TRUNK-SNAPSHOT' +} +kotlin { + experimental { + coroutines "enable" + } +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +task testsJar(type: Jar, dependsOn: classes) { + classifier = 'tests' + from sourceSets.test.output + exclude 'testData/*' +} + +prepareSandbox { + from(testsJar) { + into "$pluginName/lib" + } +} + +prepareTestingSandbox { + from(testsJar) { + into "$pluginName/lib" + } +} + +runIde { + if (System.getProperty("idea.gui.tests.gradle.runner").equals("true")) { + systemProperties System.properties.findAll { (it.key as String).startsWith("idea") || + (it.key as String).startsWith("jb") }.findAll { + (it.key as String) != "idea.home.path" && + (it.key as String) != "jb.vmOptionsFile" + } + /* Need to split the space-delimited value in the exec.args */ + print systemProperties + args System.getProperty("exec.args", "").split(",") + print args + } +} + +test { + def sysProps = System.properties.findAll { (it.key as String).startsWith("idea") || + (it.key as String).startsWith("jb") && + (it.key as String) != "idea.home.path" && + (it.key as String) != "jb.vmOptionsFile" + } + sysProps.put("idea.gui.tests.gradle.runner", true) //Use Gradle Launcher to run GUI tests + systemProperties sysProps +} \ No newline at end of file diff --git a/flutter-gui-tests/gradle/wrapper/gradle-wrapper.jar b/flutter-gui-tests/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..13372aef5e Binary files /dev/null and b/flutter-gui-tests/gradle/wrapper/gradle-wrapper.jar differ diff --git a/flutter-gui-tests/gradle/wrapper/gradle-wrapper.properties b/flutter-gui-tests/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..2819f022f1 --- /dev/null +++ b/flutter-gui-tests/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/flutter-gui-tests/gradlew b/flutter-gui-tests/gradlew new file mode 100755 index 0000000000..9d82f78915 --- /dev/null +++ b/flutter-gui-tests/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/flutter-gui-tests/res/META-INF/plugin.xml b/flutter-gui-tests/res/META-INF/plugin.xml new file mode 100644 index 0000000000..9894f517ac --- /dev/null +++ b/flutter-gui-tests/res/META-INF/plugin.xml @@ -0,0 +1,15 @@ + + 1.0-SNAPSHOT + Created the plugin. Added UI Tests for the Flutter plugin. + io.flutter.tests.gui.flutter-gui-tests + Flutter UI Tests + + Plugin should be used only for internal purposes.<br> + <em>Do not distribute this plugin with Flutter</em> + + com.intellij.testGuiFramework + io.flutter + flutter.io + + + diff --git a/flutter-gui-tests/settings.gradle b/flutter-gui-tests/settings.gradle new file mode 100644 index 0000000000..b5036d916b --- /dev/null +++ b/flutter-gui-tests/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'flutter-gui-tests' + diff --git a/flutter-gui-tests/testData/flutter_projects/simple_app/src.zip b/flutter-gui-tests/testData/flutter_projects/simple_app/src.zip new file mode 100644 index 0000000000..d9b1d339a0 Binary files /dev/null and b/flutter-gui-tests/testData/flutter_projects/simple_app/src.zip differ diff --git a/flutter-gui-tests/testData/flutter_projects/simple_plugin/src.zip b/flutter-gui-tests/testData/flutter_projects/simple_plugin/src.zip new file mode 100644 index 0000000000..550f6d921e Binary files /dev/null and b/flutter-gui-tests/testData/flutter_projects/simple_plugin/src.zip differ diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt new file mode 100644 index 0000000000..9d6dec5e32 --- /dev/null +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/InspectorTest.kt @@ -0,0 +1,168 @@ +/* + * 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.tests.gui + +import com.intellij.openapi.util.SystemInfo.isMac +import com.intellij.testGuiFramework.fixtures.ActionButtonFixture +import com.intellij.testGuiFramework.fixtures.ExecutionToolWindowFixture +import com.intellij.testGuiFramework.fixtures.IdeFrameFixture +import com.intellij.testGuiFramework.framework.RunWithIde +import com.intellij.testGuiFramework.framework.Timeouts +import com.intellij.testGuiFramework.impl.GuiTestCase +import com.intellij.testGuiFramework.launcher.ide.CommunityIde +import com.intellij.testGuiFramework.util.step +import io.flutter.tests.gui.fixtures.flutterInspectorFixture +import org.fest.swing.fixture.JTreeRowFixture +import org.fest.swing.timing.Condition +import org.fest.swing.timing.Pause.pause +import org.junit.Test +import java.awt.event.KeyEvent +import kotlin.test.expect + +@RunWithIde(CommunityIde::class) +class InspectorTest : GuiTestCase() { + + @Test + fun widgetTree() { + ProjectCreator.importProject() + ideFrame { + launchFlutterApp() + val inspector = flutterInspectorFixture(this) + inspector.populate() + val widgetTree = inspector.widgetsFixture() + val inspectorTree = widgetTree.inspectorTreeFixture() + val detailsTree = widgetTree.inspectorTreeFixture(isDetails = true) + expect(true) { detailsTree.selection() == null } + + step("Details selection synced with main tree") { + inspectorTree.selectRow(2, expand = true) + expect("[[root], MyApp, MaterialApp, MyHomePage]") { inspectorTree.selectionSync().toString() } + expect("[MyHomePage]") { detailsTree.selectionSync().toString() } + inspectorTree.selectRow(10, expand = true) + expect("[[root], MyApp, MaterialApp, MyHomePage, Scaffold, FloatingActionButton]") { + inspectorTree.selectionSync().toString() + } + val string = detailsTree.selectionSync().toString() + expect(true) { + string.startsWith("[MyHomePage,") && string.endsWith("FloatingActionButton]") + } + } + + // This is disabled due to an issue in the test framework. The #selectRow call causes + // the widget tree to change its selection, which is absolutely not what we want. + // step("Details selection leaves main tree unchanged") { + // val string = detailsTree.selectionSync().toString() + // detailsTree.selectRow(1, expand = false) + // pause(object : Condition("Details tree changes") { + // override fun test(): Boolean { + // return string != detailsTree.selectionSync().toString() + // } + // }, Timeouts.seconds05) + // expect("[[root], MyApp, MaterialApp, MyHomePage, Scaffold, FloatingActionButton]") { + // inspectorTree.selectionSync().toString() + // } + // } + + runner().stop() + } + } + + @Test + fun hotReload() { + ProjectCreator.importProject() + ideFrame { + launchFlutterApp() + val inspector = flutterInspectorFixture(this) + inspector.populate() + val widgets = inspector.widgetsFixture() + val widgetsTree = widgets.inspectorTreeFixture(isDetails = false) + widgetsTree.selectRow(0) + val detailsTree = widgets.inspectorTreeFixture(isDetails = true) + val initialDetails = detailsTree.selectionSync().toString() + + editor { + // Wait until current file has appeared in current editor and set focus to editor. + moveTo(0) + val editorCode = getCurrentFileContents(false)!! + val original = "pushed the button this" + val index = editorCode.indexOf(original) + original.length + moveTo(index) + val key = if (isMac) KeyEvent.VK_BACK_SPACE else KeyEvent.VK_DELETE + for (n in 1..4) typeKey(key) + typeText("that") + //typeKey(KeyEvent.VK_ESCAPE) // Dismiss completion popup -- not needed with "that" but is needed with "so" + } + + step("Trigger Hot Reload and wait for it to finish") { + val reload = findHotReloadButton() + reload.click() + editor.clickCenter() // Need to cycle the event loop to get button enabled on Mac. + pause(object : Condition("Hot Reload finishes") { + override fun test(): Boolean { + return reload.isEnabled + } + }, Timeouts.seconds05) + step("Work around #3370") { + // https://github.com/flutter/flutter-intellij/issues/3370 + inspector.renderTreeFixture().show() // The refresh button is broken so force tree update by switching views. + inspector.populate() + widgets.show() // And back to the one we want + } + widgetsTree.selectRow(6) // Text widget + pause(object : Condition("Details tree changes") { + override fun test(): Boolean { + return initialDetails != detailsTree.selectionSync().toString() + } + }, Timeouts.seconds05) + val row: JTreeRowFixture = detailsTree.treeFixture().node(1) + val expected = "\"You have pushed the button that many times:\"" + expect(expected) { row.value() } + } + + runner().stop() + } + } + + fun IdeFrameFixture.launchFlutterApp() { + step("Launch Flutter app") { + findRunApplicationButton().click() + val runner = runner() + pause(object : Condition("Start app") { + override fun test(): Boolean { + return runner.isExecutionInProgress + } + }, Timeouts.seconds30) + } + } + + private fun findHotReloadButton(): ActionButtonFixture { + return findActionButtonByClassName("ReloadFlutterAppRetarget") + } + + private fun IdeFrameFixture.runner(): ExecutionToolWindowFixture.ContentFixture { + return runToolWindow.findContent("main.dart") + } + + private fun findActionButtonByActionId(actionId: String): ActionButtonFixture { + // This seems to be broken, but finding by simple class name works. + var button: ActionButtonFixture? = null + ideFrame { + button = ActionButtonFixture.fixtureByActionId(target().parent, robot(), actionId) + } + return button!! + } + + private fun findActionButtonByClassName(className: String): ActionButtonFixture { + // This works when the button is enabled but fails if it is disabled (implementation detail). + var button: ActionButtonFixture? = null + ideFrame { + button = ActionButtonFixture.fixtureByActionClassName(target(), robot(), className) + } + return button!! + } + +} diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt new file mode 100644 index 0000000000..b623c13626 --- /dev/null +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/ProjectCreator.kt @@ -0,0 +1,159 @@ +/* + * 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.tests.gui + +import com.intellij.ide.fileTemplates.impl.UrlUtil +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.io.StreamUtil +import com.intellij.platform.templates.github.ZipUtil +import com.intellij.testGuiFramework.fixtures.IdeFrameFixture +import com.intellij.testGuiFramework.framework.Timeouts +import com.intellij.testGuiFramework.impl.* +import com.intellij.testGuiFramework.util.step +import com.intellij.testGuiFramework.utils.TestUtilsClass +import com.intellij.testGuiFramework.utils.TestUtilsClassCompanion +import com.intellij.util.UriUtil +import com.intellij.util.io.URLUtil +import io.flutter.tests.gui.fixtures.FlutterMessagesToolWindowFixture +import org.fest.swing.exception.WaitTimedOutError +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileOutputStream +import java.net.URL + +val GuiTestCase.ProjectCreator by ProjectCreator + +// Inspired by CommunityProjectCreator +class ProjectCreator(guiTestCase: GuiTestCase) : TestUtilsClass(guiTestCase) { + + companion object : TestUtilsClassCompanion({ ProjectCreator(it) }) + + private val defaultProjectName = "untitled" + private val sampleProjectName = "simple_app" + private val log = Logger.getInstance(this.javaClass) + var flutterMessagesFixture: FlutterMessagesToolWindowFixture.FlutterContentFixture? = null + + fun importProject(projectName: String = sampleProjectName): File { + return step("Import project $projectName") { + val projectDirFile = extractProject(projectName) + val projectPath: File = guiTestCase.guiTestRule.importProject(projectDirFile) + with(guiTestCase) { + waitForFirstIndexing() + step("Get packages") { + openPubspecInProject() + ideFrame { + editor { + val note = notificationPanel() + if (note?.getLabelText() == "Flutter commands") { + note.clickLink("Packages get") + flutterMessagesFixture = flutterMessagesToolWindowFixture().getFlutterContent(projectName) + flutterMessagesFixture!!.findMessageContainingText("Process finished") + } + // TODO(messick) Close pubspec.yaml once editor tab fixtures are working. + //closeTab("pubspec.yaml") + } + } + } + openMainInProject() + } + projectPath + } + } + + private fun extractProject(projectName: String): File { + val projectDirUrl = this.javaClass.classLoader.getResource("flutter_projects/$projectName") + val children = UrlUtil.getChildrenRelativePaths(projectDirUrl) + val tempDir = java.nio.file.Files.createTempDirectory("test").toFile() + val projectDir = File(tempDir, projectName) + for (child in children) { + val url = childUrl(projectDirUrl, child) + val inputStream = URLUtil.openResourceStream(url) + val outputFile = File(projectDir, child) + File(outputFile.parent).mkdirs() + val outputStream = BufferedOutputStream(FileOutputStream(outputFile)) + try { + StreamUtil.copyStreamContent(inputStream, outputStream) + } + finally { + inputStream.close() + outputStream.close() + } + } + val srcZip = File(projectDir, "src.zip") + if (srcZip.exists() && srcZip.isFile) { + run { + ZipUtil.unzip(null, projectDir, srcZip, null, null, true) + srcZip.delete() + } + } + return projectDir + } + + private fun childUrl(parent: URL, child: String): URL { + return URL(UriUtil.trimTrailingSlashes(parent.toExternalForm()) + "/" + child) + } + + fun createProject(projectName: String = defaultProjectName, needToOpenMain: Boolean = true) { + with(guiTestCase) { + step("Create project $projectName") { + welcomeFrame { + this.actionLink(name = "Create New Project").click() + GuiTestUtilKt.waitProgressDialogUntilGone( + robot = robot(), progressTitle = "Loading Templates", timeoutToAppear = Timeouts.seconds02) + dialog("New Project") { + jList("Flutter").clickItem("Flutter") + button("Next").click() + typeText(projectName) + button("Finish").click() + GuiTestUtilKt.waitProgressDialogUntilGone( + robot = robot(), progressTitle = "Creating Flutter Project", timeoutToAppear = Timeouts.seconds03) + } + } + waitForFirstIndexing() + } + if (needToOpenMain) openMainInProject(wait = true) + } + } + + private fun GuiTestCase.waitForFirstIndexing() { + ideFrame { + val secondsToWait = 10 + try { + waitForStartingIndexing(secondsToWait) + } + catch (timedOutError: WaitTimedOutError) { + log.warn("Wait for indexing exceeded $secondsToWait seconds") + } + waitForBackgroundTasksToFinish() + } + } + + private fun GuiTestCase.openMainInProject(wait: Boolean = false) { + ideFrame { + projectView { + step("Open lib/main.dart") { + path(project.name, "lib", "main.dart").doubleClick() + if (wait) waitForBackgroundTasksToFinish() + } + } + } + } + + private fun GuiTestCase.openPubspecInProject() { + ideFrame { + projectView { + step("Open pubspec.yaml") { + path(project.name, "pubspec.yaml").doubleClick() + } + } + } + } + + fun IdeFrameFixture.flutterMessagesToolWindowFixture(): FlutterMessagesToolWindowFixture { + return FlutterMessagesToolWindowFixture(project, robot()) + } +} diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt new file mode 100644 index 0000000000..e97f8659a1 --- /dev/null +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/SmokeTest.kt @@ -0,0 +1,49 @@ +/* + * 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.tests.gui + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.testGuiFramework.framework.RunWithIde +import com.intellij.testGuiFramework.impl.GuiTestCase +import com.intellij.testGuiFramework.launcher.ide.CommunityIde +import com.intellij.testGuiFramework.util.step +import org.junit.Assert +import org.junit.Test + +// Inspired by CommandLineProjectGuiTest +@RunWithIde(CommunityIde::class) +class SmokeTest : GuiTestCase() { + private val LOG = Logger.getInstance(this.javaClass) + + @Test + fun createBasicProject() { + ProjectCreator.createProject(projectName = "guitest") + checkProject() + } + + @Test + fun importSimpleProject() { + ProjectCreator.importProject() + checkProject() + } + + private fun checkProject() { + ideFrame { + editor { + // Wait until current file has appeared in current editor and set focus to editor. + moveTo(1) + } + step("Verify open file has some content") { + val editorCode = editor.getCurrentFileContents(false) + Assert.assertTrue(editorCode!!.isNotEmpty()) + } + step("Close project") { + closeProjectAndWaitWelcomeFrame() + } + } + } +} diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt new file mode 100644 index 0000000000..9b117b6555 --- /dev/null +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/TestSuite.kt @@ -0,0 +1,19 @@ +/* + * 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.tests.gui + +import com.intellij.testGuiFramework.framework.GuiTestSuite +import com.intellij.testGuiFramework.framework.GuiTestSuiteRunner +import org.junit.runner.RunWith +import org.junit.runners.Suite + +//* gradle -Dtest.single=TestSuite clean test -Didea.gui.test.alternativeIdePath="" +// The log file is at flutter-gui-tests/build/idea-sandbox/system/log/idea.log +// The test report is at flutter-gui-tests/build/reports/tests/test/index.html +@RunWith(GuiTestSuiteRunner::class) +@Suite.SuiteClasses(SmokeTest::class, InspectorTest::class) +class TestSuite : GuiTestSuite() diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt new file mode 100644 index 0000000000..1b2156c25d --- /dev/null +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterInspectorFixture.kt @@ -0,0 +1,182 @@ +/* + * 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.tests.gui.fixtures + +import com.intellij.execution.ui.layout.impl.JBRunnerTabs +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Ref +import com.intellij.testGuiFramework.fixtures.IdeFrameFixture +import com.intellij.testGuiFramework.fixtures.JComponentFixture +import com.intellij.testGuiFramework.fixtures.ToolWindowFixture +import com.intellij.testGuiFramework.framework.Timeouts +import com.intellij.testGuiFramework.impl.GuiRobotHolder.robot +import com.intellij.testGuiFramework.matcher.ClassNameMatcher +import com.intellij.testGuiFramework.util.step +import com.intellij.ui.tabs.TabInfo +import com.intellij.ui.tabs.impl.TabLabel +import io.flutter.inspector.InspectorService +import io.flutter.inspector.InspectorTree +import io.flutter.view.InspectorPanel +import junit.framework.Assert.assertNotNull +import org.fest.swing.core.ComponentFinder +import org.fest.swing.core.Robot +import org.fest.swing.fixture.JTreeFixture +import org.fest.swing.timing.Condition +import org.fest.swing.timing.Pause +import org.fest.swing.timing.Pause.pause +import java.awt.Component +import javax.swing.JPanel +import javax.swing.JTree +import javax.swing.tree.TreePath + +fun IdeFrameFixture.flutterInspectorFixture(ideFrame: IdeFrameFixture): FlutterInspectorFixture { + return FlutterInspectorFixture(project, robot(), ideFrame) +} + +// A fixture for the Inspector top-level view. +class FlutterInspectorFixture(project: Project, robot: Robot, private val ideFrame: IdeFrameFixture) + : ToolWindowFixture("Flutter Inspector", project, robot) { + + fun populate() { + step("Populate inspector tree") { + activate() + selectedContent + Pause.pause(object : Condition("Initialize inspector") { + override fun test(): Boolean { + return contents[0].displayName != null + } + }, Timeouts.seconds30) + } + } + + fun widgetsFixture(): InspectorPanelFixture { + showTab(0) + return inspectorPanel(InspectorService.FlutterTreeType.widget) + } + + fun renderTreeFixture(): InspectorPanelFixture { + showTab(1) + return inspectorPanel(InspectorService.FlutterTreeType.renderObject) + } + + private fun showTab(index: Int) { + val tabs: JBRunnerTabs = contents[0].component.components[0] as JBRunnerTabs + val info: TabInfo = tabs.getTabAt(index) + val label = tabs.getTabLabel(info) + TabLabelFixture(robot, label).click() + } + + private fun finder(): ComponentFinder { + return ideFrame.robot().finder() + } + + private fun classMatcher(name: String, base: Class): ClassNameMatcher { + return ClassNameMatcher.forClass(name, base) + } + + private fun inspectorPanel(type: InspectorService.FlutterTreeType): InspectorPanelFixture { + val inspectorPanelRef = Ref() + + pause(object : Condition("Inspector with type '$type' shows up") { + override fun test(): Boolean { + val inspectorPanel = findInspectorPanel(type) + inspectorPanelRef.set(inspectorPanel) + return inspectorPanel != null + } + }, Timeouts.seconds10) + + val notificationPanel = inspectorPanelRef.get() + assertNotNull(notificationPanel) + return InspectorPanelFixture(notificationPanel, type) + } + + private fun findInspectorPanel(type: InspectorService.FlutterTreeType): InspectorPanel? { + val panels = finder().findAll(contents[0].component, classMatcher("io.flutter.view.InspectorPanel", JPanel::class.java)) + return panels.firstOrNull { it is InspectorPanel && it.treeType == type && !it.isDetailsSubtree } as InspectorPanel + } + + // The InspectorPanel is a little tricky to work with. In order for the tree to have content its view must be made + // visible (either Widgets or Render Tree). However, we don't want to return a reference to an empty tree, because + // of timing issues. We use the fact that the tree has content as a signal to move on to the next step. + inner class InspectorPanelFixture(val inspectorPanel: InspectorPanel, val type: InspectorService.FlutterTreeType) { + + fun show() { + showTab(tabIndex()) + } + + private fun tabIndex(): Int { + return if (type == InspectorService.FlutterTreeType.widget) 0 else 1 + } + + fun inspectorTreeFixture(isDetails: Boolean = false): InspectorTreeFixture { + val inspectorTreeRef = Ref() + + pause(object : Condition("Tree shows up") { + override fun test(): Boolean { + val inspectorTree = findInspectorTree(isDetails) + inspectorTreeRef.set(inspectorTree) + return inspectorTree != null + } + }, Timeouts.seconds10) + + val inspectorTree = inspectorTreeRef.get() + assertNotNull(inspectorTree) + return InspectorTreeFixture(inspectorTree) + } + + fun findInspectorTree(isDetails: Boolean): InspectorTree? { + val trees = finder().findAll(inspectorPanel, classMatcher("io.flutter.inspector.InspectorTree", JTree::class.java)) + val tree = trees.firstOrNull { it is InspectorTree && isDetails == it.detailsSubtree } + if (tree != null) return tree as InspectorTree else return null + } + } + + // This fixture is used to access the tree for both Widgets and Render Objects. + inner class InspectorTreeFixture(private val inspectorTree: InspectorTree) { + + fun treeFixture(): JTreeFixture { + return JTreeFixture(ideFrame.robot(), inspectorTree) + } + + fun selectRow(number: Int, expand: Boolean = true) { + waitForContent() + treeFixture().clickRow(number) // This should not collapse the tree, but it does. + if (expand) { + treeFixture().expandRow(number) // TODO(messick) Remove when selection preserves tree expansion. + } + } + + fun selection(): TreePath? { + return inspectorTree.selectionPath + } + + fun selectionSync(): TreePath { + waitForContent() + waitForCondition("Selection is set") { inspectorTree.selectionPath != null } + return selection()!! + } + + private fun waitForContent() { + waitForCondition("Tree has content") { inspectorTree.rowCount > 1 } + } + + private fun waitForCondition(description: String, condition: () -> Boolean): Boolean { + var result: Boolean = false + pause(object : Condition(description) { + override fun test(): Boolean { + result = condition.invoke() + return result + } + }, Timeouts.seconds05) + return result + } + } +} + +// A clickable fixture for the three tabs in the inspector: Widgets, Render Tree, and Performance. +class TabLabelFixture(robot: Robot, target: TabLabel) + : JComponentFixture(TabLabelFixture::class.java, robot, target) diff --git a/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt new file mode 100644 index 0000000000..96d9b7a588 --- /dev/null +++ b/flutter-gui-tests/testSrc/io/flutter/tests/gui/fixtures/FlutterMessagesToolWindowFixture.kt @@ -0,0 +1,72 @@ +/* + * 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.tests.gui.fixtures + +import com.intellij.execution.impl.ConsoleViewImpl +import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.ToolWindowId +import com.intellij.testGuiFramework.fixtures.MessagesToolWindowFixture +import com.intellij.testGuiFramework.fixtures.ToolWindowFixture +import com.intellij.testGuiFramework.framework.GuiTestUtil +import com.intellij.testGuiFramework.framework.Timeouts +import com.intellij.ui.content.Content +import org.fest.swing.core.GenericTypeMatcher +import org.fest.swing.core.Robot +import org.fest.swing.timing.Timeout +import org.junit.Assert + +class FlutterMessagesToolWindowFixture(project: Project, robot: Robot) : ToolWindowFixture(ToolWindowId.MESSAGES_WINDOW, project, robot) { + + fun getFlutterContent(appName: String): FlutterContentFixture { + val content = getContent("[$appName] Flutter") + Assert.assertNotNull(content) + return FlutterContentFixture(content!!) + } + + inner class FlutterContentFixture(val myContent: Content) { + + fun findMessageContainingText(text: String, timeout: Timeout = Timeouts.seconds30): FlutterMessageFixture { + val element = doFindMessage(text, timeout) + return createFixture(element) + } + + private fun robot(): Robot { + return (this@FlutterMessagesToolWindowFixture).myRobot + } + + private fun createFixture(found: ConsoleViewImpl): FlutterMessageFixture { + return FlutterMessageFixture(robot(), found) + } + + private fun doFindMessage(matcher: String, timeout: Timeout): ConsoleViewImpl { + return GuiTestUtil.waitUntilFound(robot(), myContent.component, + object : GenericTypeMatcher(ConsoleViewImpl::class.java) { + override fun isMatching(panel: ConsoleViewImpl): Boolean { + if (panel.javaClass.name.startsWith(ConsoleViewImpl::class.java.name) && panel.isShowing) { + val doc = panel.editor.document + return (doc.text.contains(matcher)) + } + return false + } + }, timeout) + } + + } +} + +// Modeled after MessagesToolWindowFixture.MessageFixture but used with consoles rather than error trees. +class FlutterMessageFixture(val robot: Robot, val target: ConsoleViewImpl) { + + fun getText(): String { + return target.editor.document.text + } + + fun findHyperlink(p0: String): MessagesToolWindowFixture.HyperlinkFixture { + TODO("not implemented") + } + +} diff --git a/flutter-intellij.iml b/flutter-intellij.iml index 01a0f327ab..e7e67e6df9 100644 --- a/flutter-intellij.iml +++ b/flutter-intellij.iml @@ -33,7 +33,7 @@ - + @@ -42,7 +42,7 @@ - + diff --git a/flutter-studio/README.md b/flutter-studio/README.md index eaeea4dfb9..34e903401e 100644 --- a/flutter-studio/README.md +++ b/flutter-studio/README.md @@ -40,35 +40,54 @@ the Android Studio dev team. Currently that is 2018.2. To run the tests create a Junit Run Configuration for class `io.flutter.tests.gui.NewProjectTest`. Set its working directory -to the `bin` directory of the Android Studio sources. For -example: `/Volumes/android/studio-master-dev/tools/idea/bin` +to the root directory of the Android Studio sources. For +example: `/Volumes/android/studio-master-dev/tools/idea` Set it to use the classpath of module `flutter-studio`. It needs to run with Java 8 or later. The VM options are a bit complex. Here's mine (formatted with newlines in place of spaces): ```bash --ea --Xbootclasspath/p:../out/classes/production/boot --Xms512m --Xmx1024m --Didea.is.internal=true --Didea.platform.prefix=AndroidStudio --Dandroid.extra_templates.path=../../../sdk/templates --Dapple.laf.useScreenMenuBar=true --Dcom.apple.mrj.application.apple.menu.about.name=AndroidStudio --Dsun.awt.disablegrab=true --Dawt.useSystemAAFontSettings=lcd --Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine --Dmrj.version=mac --Dcom.apple.macos.useScreenMenuBar=true --Dapple.laf.useScreenMenuBar=true --Dflutter.home=/path/to/flutter +-Xms512m +-Xmx4096m +-ea +-XX:ReservedCodeCacheSize=240m +-XX:+UseConcMarkSweepGC +-XX:SoftRefLRUPolicyMSPerMB=50 +-XX:MaxJavaStackTraceDepth=10000 +-Didea.is.internal=true +-Didea.platform.prefix=AndroidStudio +-Dandroid.extra_templates.path=../../../sdk/templates +-Dmrj.version=mac +-Dcom.apple.macos.useScreenMenuBar=true +-Dapple.laf.useScreenMenuBar=true +-Dcom.apple.mrj.application.apple.menu.about.name=AndroidStudio +-Dsun.awt.disablegrab=true +-Dawt.useSystemAAFontSettings=lcd +-Dsun.io.useCanonCaches=false +-Djava.net.preferIPv4Stack=true +-Didea.jre.check=true +-Didea.debug.mode=true +-Dflutter.home=/Users/messick/src/flutter/flutter +-Dsun.java2d.renderer=sun.java2d.marlin.MarlinRenderingEngine +-Dplugin.path=/Volumes/android/studio-master-dev/prebuilts/tools/common/kotlin-plugin/Kotlin +-Didea.config.path=/tmp/idea-test/config +-Didea.system.path=/tmp/idea-test/system +-Didea.plugins.path=/tmp/idea-test/plugins +-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 ``` -Don't forget to adjust the path to your Flutter SDK in the last one. - If you're not using a Mac then delete these: - mrj.version - com.apple.macos.useScreenMenuBar - apple.laf.useScreenMenuBar +The last line causes the test to be forked in a remote JVM +that's waiting for a debug connection. Make a 'Remote' run +config and launch it after launching the Flutter test run +config to get it running. + +Got past React native issue by defining custom JDK that includes +Javascript and CSS lib dir contents from IntelliJ plugins. +Also disabled JS plugins, not sure that helped. +The custom JDK causes the Javascript plugin to be loaded +twice, so there's a hack in the test runner to compensate. diff --git a/flutter-studio/flutter-studio.iml b/flutter-studio/flutter-studio.iml index 2d89dfbbd2..f7e726c0e9 100644 --- a/flutter-studio/flutter-studio.iml +++ b/flutter-studio/flutter-studio.iml @@ -6,7 +6,7 @@ - + @@ -28,17 +28,13 @@ - - - - @@ -46,10 +42,11 @@ - - + + + \ No newline at end of file diff --git a/flutter-studio/src/io/flutter/FlutterStudioStartupActivity.java b/flutter-studio/src/io/flutter/FlutterStudioStartupActivity.java index fb5cd00131..f06363c112 100644 --- a/flutter-studio/src/io/flutter/FlutterStudioStartupActivity.java +++ b/flutter-studio/src/io/flutter/FlutterStudioStartupActivity.java @@ -26,7 +26,6 @@ public void runActivity(@NotNull Project project) { // The IntelliJ version of this action spawns a new process for Android Studio. // Since we're already running Android Studio we want to simply open the project in the current process. replaceAction("flutter.androidstudio.open", new OpenAndroidModule()); - replaceAction("ShowProjectStructureSettings", new FlutterShowStructureSettingsAction()); // Unset this flag for all projects, mainly to ease the upgrade from 3.0.1 to 3.1. // TODO(messick) Delete once 3.0.x has 0 7DA's. FlutterProjectCreator.disableUserConfig(project); diff --git a/flutter-studio/src/io/flutter/module/FlutterDescriptionProvider.java b/flutter-studio/src/io/flutter/module/FlutterDescriptionProvider.java index 8cad306d7f..fa9c701a57 100644 --- a/flutter-studio/src/io/flutter/module/FlutterDescriptionProvider.java +++ b/flutter-studio/src/io/flutter/module/FlutterDescriptionProvider.java @@ -20,6 +20,7 @@ import io.flutter.project.FlutterProjectModel; import io.flutter.project.FlutterProjectStep; import io.flutter.utils.FlutterModuleUtils; +import javax.swing.Icon; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -113,8 +114,8 @@ private FlutterApplicationGalleryEntry(OptionalValueProperty sh @Nullable @Override - public Image getIcon() { - return IconUtil.toImage(FlutterIcons.AndroidStudioNewPackage); + public Icon getIcon() { + return FlutterIcons.AndroidStudioNewPackage; } @NotNull @@ -209,8 +210,8 @@ private FlutterPluginGalleryEntry(OptionalValueProperty sha @Nullable @Override - public Image getIcon() { - return IconUtil.toImage(FlutterIcons.AndroidStudioNewPlugin); + public Icon getIcon() { + return FlutterIcons.AndroidStudioNewPlugin; } @NotNull @@ -257,8 +258,8 @@ private FlutterModuleGalleryEntry(OptionalValueProperty sha @Nullable @Override - public Image getIcon() { - return IconUtil.toImage(FlutterIcons.AndroidStudioNewModule); + public Icon getIcon() { + return FlutterIcons.AndroidStudioNewModule; } @NotNull @@ -305,8 +306,8 @@ private ImportFlutterModuleGalleryEntry(OptionalValueProperty lastItem = multiData.get(HEAP_CAPACITY).mData.get(lastEntry - 1); - long yValue = lastItem.value; - multiData.get(GC_DATA).mData.add(new SeriesData(timestamp, yValue)); + int entriesLength = multiData.get(HEAP_CAPACITY).mData.size(); + if (entriesLength > 0) { + SeriesData lastItem = multiData.get(HEAP_CAPACITY).mData.get(entriesLength - 1); + long yValue = lastItem.value; + multiData.get(GC_DATA).mData.add(new SeriesData(timestamp, yValue)); + } } private void updateModel(HeapState heapState) { @@ -142,21 +144,7 @@ private void updateModel(HeapState heapState) { multiData.get(EXTERNAL_MEMORY_USED).mData.add(new SeriesData<>(sampleTime, (long)sample.getExternal())); - String rssString = heapState.getRSSSummary(); - rssString = rssString.substring(0, rssString.indexOf(" RSS")); - int rssLength = rssString.length(); - int rssSize = Integer.valueOf(rssString.substring(0, rssLength - 2)); - String rssUnit = rssString.substring(rssLength - 2); - if (rssUnit.equals("KB")) { - rssSize *= 1000; - } - else if (rssUnit.equals("MB")) { - rssSize *= 1000000; - } - else if (rssUnit.equals("GB")) { - rssSize *= 1000000000; - } - multiData.get(RSS_SIZE).mData.add(new SeriesData<>(sampleTime, (long)rssSize)); + multiData.get(RSS_SIZE).mData.add(new SeriesData<>(sampleTime, (long)heapState.getRssBytes())); } } }; diff --git a/flutter-studio/src/io/flutter/project/FlutterProjectModel.java b/flutter-studio/src/io/flutter/project/FlutterProjectModel.java index fe24cad595..41e24b8ac5 100644 --- a/flutter-studio/src/io/flutter/project/FlutterProjectModel.java +++ b/flutter-studio/src/io/flutter/project/FlutterProjectModel.java @@ -48,7 +48,7 @@ public class FlutterProjectModel extends WizardModel { public FlutterProjectModel(@NotNull FlutterProjectType type) { myProjectType.set(new OptionalValueProperty<>(type)); - myCompanyDomain.addListener(sender -> { + myCompanyDomain.addListener(() -> { String domain = myCompanyDomain.get(); if (domain.isEmpty()) { domain = null; // Keys with null values are deleted. @@ -59,10 +59,10 @@ public FlutterProjectModel(@NotNull FlutterProjectType type) { myProjectName.addConstraint(String::trim); myKotlin.set(getInitialKotlinSupport()); - myKotlin.addListener(sender -> setInitialKotlinSupport(myKotlin.get())); + myKotlin.addListener(() -> setInitialKotlinSupport(myKotlin.get())); mySwift.set(getInitialSwiftSupport()); - mySwift.addListener(sender -> setInitialSwiftSupport(mySwift.get())); + mySwift.addListener(() -> setInitialSwiftSupport(mySwift.get())); } public void setSample(@Nullable FlutterSample sample) { diff --git a/flutter-studio/src/io/flutter/project/FlutterProjectSystem.java b/flutter-studio/src/io/flutter/project/FlutterProjectSystem.java index af2ac79b3f..0feb010b4e 100644 --- a/flutter-studio/src/io/flutter/project/FlutterProjectSystem.java +++ b/flutter-studio/src/io/flutter/project/FlutterProjectSystem.java @@ -65,7 +65,7 @@ public AndroidModuleSystem getModuleSystem(@NotNull Module module) { return gradleProjectSystem.getModuleSystem(module); } - @Override + @SuppressWarnings("override") public boolean upgradeProjectToSupportInstantRun() { return false; // Already done. } diff --git a/flutter-studio/src/io/flutter/project/FlutterSettingsStep.java b/flutter-studio/src/io/flutter/project/FlutterSettingsStep.java index 19b0ed371a..e31c0fe2e8 100644 --- a/flutter-studio/src/io/flutter/project/FlutterSettingsStep.java +++ b/flutter-studio/src/io/flutter/project/FlutterSettingsStep.java @@ -65,7 +65,7 @@ public String get() { BoolProperty isPackageSynced = new BoolValueProperty(true); myBindings.bind(packageNameText, computedPackageName, isPackageSynced); myBindings.bind(model.packageName(), packageNameText); - myListeners.receive(packageNameText, value -> isPackageSynced.set(value.equals(computedPackageName.get()))); + myListeners.listen(packageNameText, value -> isPackageSynced.set(value.equals(computedPackageName.get()))); myBindings.bindTwoWay(new TextProperty(myCompanyDomain), model.companyDomain()); diff --git a/flutter-studio/testData/flutter_projects/simple_app/src.zip b/flutter-studio/testData/flutter_projects/simple_app/src.zip new file mode 100644 index 0000000000..d9b1d339a0 Binary files /dev/null and b/flutter-studio/testData/flutter_projects/simple_app/src.zip differ diff --git a/flutter-studio/testData/flutter_projects/simple_plugin/src.zip b/flutter-studio/testData/flutter_projects/simple_plugin/src.zip new file mode 100644 index 0000000000..550f6d921e Binary files /dev/null and b/flutter-studio/testData/flutter_projects/simple_plugin/src.zip differ diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/FlutterGuiTestRule.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/FlutterGuiTestRule.java index aeca799f06..69a19d1f21 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/FlutterGuiTestRule.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/FlutterGuiTestRule.java @@ -5,30 +5,45 @@ */ package com.android.tools.idea.tests.gui.framework; -import com.android.testutils.TestUtils; -import com.android.tools.idea.gradle.util.LocalProperties; -import com.android.tools.idea.sdk.IdeSdks; +import static com.android.testutils.TestUtils.getWorkspaceRoot; +import static com.android.testutils.TestUtils.runningFromBazel; +import static com.android.tools.idea.tests.gui.framework.GuiTests.setUpDefaultProjectCreationLocationPath; +import static com.google.common.truth.TruthJUnit.assume; +import static com.intellij.openapi.util.io.FileUtil.sanitizeFileName; + import com.android.tools.idea.tests.gui.framework.fixture.FlutterFrameFixture; import com.android.tools.idea.tests.gui.framework.fixture.FlutterWelcomeFrameFixture; import com.android.tools.idea.tests.gui.framework.fixture.IdeFrameFixture; -import com.android.tools.idea.tests.gui.framework.fixture.IdeaFrameFixture; -import com.android.tools.idea.projectsystem.TestProjectSystem; import com.android.tools.idea.tests.gui.framework.matcher.Matchers; +import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.io.FileUtilRt; -import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.impl.IdeFrameImpl; +import io.flutter.tests.util.ProjectWrangler; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.KeyboardFocusManager; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.KeyEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Hashtable; +import java.util.List; +import java.util.concurrent.TimeUnit; +import javax.swing.JPopupMenu; +import javax.swing.SwingUtilities; import org.fest.swing.core.Robot; import org.fest.swing.exception.WaitTimedOutError; -import org.jdom.Document; -import org.jdom.Element; -import org.jdom.input.SAXBuilder; -import org.jdom.xpath.XPath; +import org.fest.swing.timing.Wait; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.AssumptionViolatedException; @@ -39,29 +54,11 @@ import org.junit.runners.model.MultipleFailureException; import org.junit.runners.model.Statement; -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.beans.PropertyChangeListener; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Hashtable; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import static com.android.testutils.TestUtils.*; -import static com.android.tools.idea.tests.gui.framework.GuiTests.setUpDefaultProjectCreationLocationPath; -import static com.google.common.truth.TruthJUnit.assume; -import static com.intellij.openapi.util.io.FileUtil.sanitizeFileName; -import static org.fest.reflect.core.Reflection.*; - /** * A GUI test rule is used to drive GUI tests. It provides access to top-level * UI elements, such as dialogs, IDE frame, and welcome screen (when no projects * are open). - * + *

* For example: * FlutterGuiTestRule myGuiTest = new FlutterGuiTestRule(); * WizardUtils.createNewApplication(myGuiTest); @@ -69,29 +66,27 @@ * EditorFixture editor = ideFrame.getEditor(); * editor.waitUntilErrorAnalysisFinishes(); * ... - * + *

* {@link TestRule}s can do everything that could be done previously with * methods annotated with {@link org.junit.Before}, * {@link org.junit.After}, {@link org.junit.BeforeClass}, or * {@link org.junit.AfterClass}, but they are more powerful, and more easily * shared between projects and classes. */ -@SuppressWarnings("Duplicates") // Adapted from com.android.tools.idea.tests.gui.framework.GuiTestRule +@SuppressWarnings({"Duplicates", "unused", "deprecation"}) +// Adapted from com.android.tools.idea.tests.gui.framework.GuiTestRule public class FlutterGuiTestRule implements TestRule { /** * Hack to solve focus issue when running with no window manager */ private static final boolean HAS_EXTERNAL_WINDOW_MANAGER = Toolkit.getDefaultToolkit().isFrameStateSupported(Frame.MAXIMIZED_BOTH); - - private FlutterFrameFixture myIdeFrameFixture; - @Nullable private String myTestDirectory; - + /* By nesting a pair of timeouts (one around just the test, one around the entire rule chain), we ensure that Rule code executing + * before/after the test gets a chance to run, while preventing the whole rule chain from running forever. + */ + private static final int DEFAULT_TEST_TIMEOUT_MINUTES = 3; private final RobotTestRule myRobotTestRule = new RobotTestRule(); private final LeakCheck myLeakCheck = new LeakCheck(); - - private Timeout myTimeout = new Timeout(5, TimeUnit.MINUTES); - private final PropertyChangeListener myGlobalFocusListener = e -> { Object oldValue = e.getOldValue(); if ("permanentFocusOwner".equals(e.getPropertyName()) && oldValue instanceof Component && e.getNewValue() == null) { @@ -104,6 +99,10 @@ public class FlutterGuiTestRule implements TestRule { } } }; + private FlutterFrameFixture myIdeFrameFixture; + @Nullable private String myTestDirectory; + private Timeout myInnerTimeout = new DebugFriendlyTimeout(DEFAULT_TEST_TIMEOUT_MINUTES, TimeUnit.MINUTES); + private Timeout myOuterTimeout = new DebugFriendlyTimeout(DEFAULT_TEST_TIMEOUT_MINUTES + 2, TimeUnit.MINUTES); private IdeFrameFixture myOldIdeFrameFixture; public FlutterGuiTestRule withLeakCheck() { @@ -117,13 +116,15 @@ public Statement apply(final Statement base, final Description description) { // TODO(messick) Update this. RuleChain chain = RuleChain.emptyRuleChain() .around(new LogStartAndStop()) + .around(myRobotTestRule) + .around(myOuterTimeout) // Rules should be inside this timeout when possible + .around(new IdeControl(myRobotTestRule::getRobot)) .around(new BlockReloading()) .around(new BazelUndeclaredOutputs()) - .around(myRobotTestRule) .around(myLeakCheck) .around(new IdeHandling()) .around(new ScreenshotOnFailure()) - .around(myTimeout); + .around(myInnerTimeout); // Perf logging currently writes data to the Bazel-specific TEST_UNDECLARED_OUTPUTS_DIR. Skipp logging if running outside of Bazel. if (runningFromBazel()) { @@ -133,43 +134,6 @@ public Statement apply(final Statement base, final Description description) { return chain.apply(base, description); } - private class IdeHandling implements TestRule { - @NotNull - @Override - public Statement apply(final Statement base, final Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - if (!runningFromBazel()) { - // when state can be bad from previous tests, check and skip in that case - assume().that(GuiTests.fatalErrorsFromIde()).named("IDE errors").isEmpty(); - assumeOnlyWelcomeFrameShowing(); - } - setUp(description.getMethodName()); - List errors = new ArrayList<>(); - try { - base.evaluate(); - } catch (MultipleFailureException e) { - errors.addAll(e.getFailures()); - } catch (Throwable e) { - errors.add(e); - } finally { - try { - boolean hasTestPassed = errors.isEmpty(); - errors.addAll(tearDown()); // shouldn't throw, but called inside a try-finally for defense in depth - if (hasTestPassed && !errors.isEmpty()) { // If we get a problem during tearDown, take a snapshot. - new ScreenshotOnFailure().failed(errors.get(0), description); - } - } finally { - //noinspection ThrowFromFinallyBlock; assertEmpty is intended to throw here - MultipleFailureException.assertEmpty(errors); - } - } - } - }; - } - } - private void assumeOnlyWelcomeFrameShowing() { try { FlutterWelcomeFrameFixture.find(robot()); @@ -194,16 +158,6 @@ private void setUp(@NotNull String methodName) { } } - private static ImmutableList thrownFromRunning(Runnable r) { - try { - r.run(); - return ImmutableList.of(); - } - catch (Throwable e) { - return ImmutableList.of(e); - } - } - protected void tearDownProject() { if (!robot().finder().findAll(Matchers.byType(IdeFrameImpl.class).andIsShowing()).isEmpty()) { ideFrame().closeProject(); @@ -220,7 +174,12 @@ private ImmutableList tearDown() { if (!HAS_EXTERNAL_WINDOW_MANAGER) { KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener(myGlobalFocusListener); } - errors.addAll(GuiTests.fatalErrorsFromIde()); + errors.addAll( + FluentIterable + .from(GuiTests.fatalErrorsFromIde()) + // A hack to allow tests to pass despite duplicate JavaScript plugins. + // TODO(messick) Fix the JDK so this isn't required. + .filter(error -> !error.getCause().getMessage().contains("Duplicate plugin id:JavaScript")).toList()); fixMemLeaks(); return errors.build(); } @@ -259,150 +218,44 @@ private List checkForPopupMenus() { return errors; } - // Note: this works with a cooperating window manager that returns focus properly. It does not work on bare Xvfb. - private static Dialog getActiveModalDialog() { - Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); - if (activeWindow instanceof Dialog) { - Dialog dialog = (Dialog)activeWindow; - if (dialog.getModalityType() == Dialog.ModalityType.APPLICATION_MODAL) { - return dialog; - } - } - return null; - } - - private void fixMemLeaks() { - myIdeFrameFixture = null; - - // Work-around for https://youtrack.jetbrains.com/issue/IDEA-153492 - Class keyboardManagerType = type("javax.swing.KeyboardManager").load(); - Object manager = method("getCurrentManager").withReturnType(Object.class).in(keyboardManagerType).invoke(); - field("componentKeyStrokeMap").ofType(Hashtable.class).in(manager).get().clear(); - field("containerMap").ofType(Hashtable.class).in(manager).get().clear(); + public FlutterFrameFixture importSimpleApplication() throws IOException { + return importProject("simple_app"); } - public FlutterFrameFixture importSimpleLocalApplication() throws IOException { - return importProjectAndWaitForProjectSyncToFinish("SimpleLocalApplication"); + public FlutterFrameFixture openSimpleApplication() throws IOException { + return openProject("simple_app"); } - /** - * @deprecated use importSimpleLocalApplication that doesn't use remote repositories. - */ - @Deprecated() - public FlutterFrameFixture importSimpleApplication() throws IOException { - return importProjectAndWaitForProjectSyncToFinish("SimpleApplication"); + public FlutterFrameFixture importProject(@NotNull String name) throws IOException { + return importProjectAndWaitForProjectSyncToFinish(name); } - public FlutterFrameFixture importMultiModule() throws IOException { - return importProjectAndWaitForProjectSyncToFinish("MultiModule"); + public FlutterFrameFixture openProject(@NotNull String name) throws IOException { + return openProjectAndWaitForProjectSyncToFinish(name); } public FlutterFrameFixture importProjectAndWaitForProjectSyncToFinish(@NotNull String projectDirName) throws IOException { - return importProjectAndWaitForProjectSyncToFinish(projectDirName, null); - } - - public FlutterFrameFixture importProjectAndWaitForProjectSyncToFinish(@NotNull String projectDirName, @Nullable String buildFilePath) throws IOException { - importProject(projectDirName, buildFilePath); - //testSystem().waitForProjectSyncToFinish(baseIdeFrame()); + importOrOpenProject(projectDirName, true).waitForProjectSyncToFinish(); return ideFrame(); } - public FlutterFrameFixture importProject(@NotNull String projectDirName) throws IOException { - return importProject(projectDirName, null); - } - - public FlutterFrameFixture importProject(@NotNull String projectDirName, @Nullable String buildFilePath) throws IOException { - File testProjectDir = setUpProject(projectDirName); - //testSystem().importProject(testProjectDir, robot(), buildFilePath); + public FlutterFrameFixture openProjectAndWaitForProjectSyncToFinish(@NotNull String projectDirName) throws IOException { + importOrOpenProject(projectDirName, false).waitForProjectSyncToFinish(); return ideFrame(); } - /** - * Sets up a project before using it in a UI test: - *

    - *
  • Makes a copy of the project in testData/guiTests/newProjects (deletes any existing copy of the project first.) This copy is - * the one the test will use.
  • - *
  • Creates a Gradle wrapper for the test project.
  • - *
  • Updates the version of the Android Gradle plug-in used by the project, if applicable
  • - *
  • Creates a local.properties file pointing to the Android SDK path specified by the system property (or environment variable) - * 'ANDROID_HOME'
  • - *
  • Copies over missing files to the .idea directory (if the project will be opened, instead of imported.)
  • - *
  • Deletes .idea directory, .iml files and build directories, if the project will be imported.
  • - *

    - *

- * - * @param projectDirName the name of the project's root directory. Tests are located in testData/guiTests. - * @throws IOException if an unexpected I/O error occurs. - */ - private File setUpProject(@NotNull String projectDirName) throws IOException { - File projectPath = copyProjectBeforeOpening(projectDirName); - - updateLocalProperties(projectPath); - cleanUpProjectForImport(projectPath); - return projectPath; - } - - public File copyProjectBeforeOpening(@NotNull String projectDirName) throws IOException { - File masterProjectPath = getMasterProjectDirPath(projectDirName); - - File projectPath = getTestProjectDirPath(projectDirName); - if (projectPath.isDirectory()) { - FileUtilRt.delete(projectPath); - } - FileUtil.copyDir(masterProjectPath, projectPath); - return projectPath; - } - - protected void updateLocalProperties(File projectPath) throws IOException { - LocalProperties localProperties = new LocalProperties(projectPath); - localProperties.setAndroidSdkPath(IdeSdks.getInstance().getAndroidSdkPath()); - localProperties.save(); - } - - @NotNull - protected File getMasterProjectDirPath(@NotNull String projectDirName) { - return new File(GuiTests.getTestProjectsRootDirPath(), projectDirName); - } + public FlutterFrameFixture importOrOpenProject(@NotNull String projectDirName, boolean isImport) throws IOException { + ProjectWrangler wrangler = new ProjectWrangler(myTestDirectory); + VirtualFile toSelect = VfsUtil.findFileByIoFile(wrangler.setUpProject(projectDirName, isImport), true); + ApplicationManager.getApplication().invokeAndWait(() -> wrangler.openProject(toSelect)); - @NotNull - protected File getTestProjectDirPath(@NotNull String projectDirName) { - return new File(GuiTests.getProjectCreationDirPath(myTestDirectory), projectDirName); - } - - public void cleanUpProjectForImport(@NotNull File projectPath) { - File dotIdeaFolderPath = new File(projectPath, Project.DIRECTORY_STORE_FOLDER); - if (dotIdeaFolderPath.isDirectory()) { - File modulesXmlFilePath = new File(dotIdeaFolderPath, "modules.xml"); - if (modulesXmlFilePath.isFile()) { - SAXBuilder saxBuilder = new SAXBuilder(); - try { - Document document = saxBuilder.build(modulesXmlFilePath); - XPath xpath = XPath.newInstance("//*[@fileurl]"); - //noinspection unchecked - List modules = xpath.selectNodes(document); - int urlPrefixSize = "file://$PROJECT_DIR$/".length(); - for (Element module : modules) { - String fileUrl = module.getAttributeValue("fileurl"); - if (!StringUtil.isEmpty(fileUrl)) { - String relativePath = FileUtil.toSystemDependentName(fileUrl.substring(urlPrefixSize)); - File imlFilePath = new File(projectPath, relativePath); - if (imlFilePath.isFile()) { - FileUtilRt.delete(imlFilePath); - } - // It is likely that each module has a "build" folder. Delete it as well. - File buildFilePath = new File(imlFilePath.getParentFile(), "build"); - if (buildFilePath.isDirectory()) { - FileUtilRt.delete(buildFilePath); - } - } - } - } - catch (Throwable ignored) { - // if something goes wrong, just ignore. Most likely it won't affect project import in any way. - } - } - FileUtilRt.delete(dotIdeaFolderPath); - } + Wait.seconds(5).expecting("Project to be open").until(() -> ProjectManager.getInstance().getOpenProjects().length != 0); + // TODO(messick) Find a way to start the IDE without the tip-of-the-day showing -- this is flaky, fails if dialog has focus. + ideFrame().dismissTipDialog(); + // After the project is opened there will be an indexing and an analysis phase, and these can happen in any order. + // Waiting for indexing to finish, makes sure analysis will start next or all analysis was done already. + GuiTests.waitForProjectIndexingToFinish(ProjectManager.getInstance().getOpenProjects()[0]); + return ideFrame(); } public void waitForBackgroundTasks() { @@ -453,7 +306,71 @@ public IdeFrameFixture baseIdeFrame() { } public FlutterGuiTestRule withTimeout(long timeout, @NotNull TimeUnit timeUnits) { - myTimeout = new Timeout(timeout, timeUnits); + myInnerTimeout = new Timeout(timeout, timeUnits); + myOuterTimeout = new Timeout(timeUnits.toSeconds(timeout) + 120, TimeUnit.SECONDS); return this; } + + private static ImmutableList thrownFromRunning(Runnable r) { + try { + r.run(); + return ImmutableList.of(); + } + catch (Throwable e) { + return ImmutableList.of(e); + } + } + + // Note: this works with a cooperating window manager that returns focus properly. It does not work on bare Xvfb. + private static Dialog getActiveModalDialog() { + Window activeWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); + if (activeWindow instanceof Dialog) { + Dialog dialog = (Dialog)activeWindow; + if (dialog.getModalityType() == Dialog.ModalityType.APPLICATION_MODAL) { + return dialog; + } + } + return null; + } + + private class IdeHandling implements TestRule { + @NotNull + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (!runningFromBazel()) { + // when state can be bad from previous tests, check and skip in that case + assume().that(GuiTests.fatalErrorsFromIde()).named("IDE errors").isEmpty(); + assumeOnlyWelcomeFrameShowing(); + } + setUp(description.getMethodName()); + List errors = new ArrayList<>(); + try { + base.evaluate(); + } + catch (MultipleFailureException e) { + errors.addAll(e.getFailures()); + } + catch (Throwable e) { + errors.add(e); + } + finally { + try { + boolean hasTestPassed = errors.isEmpty(); + errors.addAll(tearDown()); // shouldn't throw, but called inside a try-finally for defense in depth + if (hasTestPassed && !errors.isEmpty()) { // If we get a problem during tearDown, take a snapshot. + new ScreenshotOnFailure().failed(errors.get(0), description); + } + } + finally { + //noinspection ThrowFromFinallyBlock; assertEmpty is intended to throw here + MultipleFailureException.assertEmpty(errors); + } + } + } + }; + } + } } diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterFrameFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterFrameFixture.java index dd679153d6..83c9862e77 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterFrameFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterFrameFixture.java @@ -11,8 +11,10 @@ import com.android.tools.idea.tests.gui.framework.matcher.Matchers; import com.intellij.openapi.wm.impl.IdeFrameImpl; import org.fest.swing.core.Robot; +import org.fest.swing.fixture.DialogFixture; import org.jetbrains.annotations.NotNull; +@SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass", "UnusedReturnValue"}) public class FlutterFrameFixture extends IdeaFrameFixture { private FlutterFrameFixture(@NotNull Robot robot, @NotNull IdeFrameImpl target) { super(robot, target); @@ -33,6 +35,14 @@ public NewFlutterModuleWizardFixture findNewModuleWizard() { return NewFlutterModuleWizardFixture.find(this); } + public FlutterFrameFixture dismissTipDialog() { + DialogFixture tipDialog = findDialog("Tip of the Day"); + if (tipDialog != null) { + tipDialog.close(); + } + return this; + } + public void waitForProjectSyncToFinish() { GuiTests.waitForBackgroundTasks(robot()); } diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterWelcomeFrameFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterWelcomeFrameFixture.java index 7c6670ead0..71b45fd60d 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterWelcomeFrameFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/FlutterWelcomeFrameFixture.java @@ -14,6 +14,7 @@ import org.jetbrains.annotations.NotNull; // Adapted from com.android.tools.idea.tests.gui.framework.fixture.WelcomeFrameFixture +@SuppressWarnings("SameParameterValue") public class FlutterWelcomeFrameFixture extends ComponentFixture { private static final String NEW_PROJECT_WELCOME_ID = "flutter.NewProject.welcome"; // See META-INF/studio-contribs.xml diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeaFrameFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeaFrameFixture.java index 83ba47b3f2..6de2a9d14e 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeaFrameFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/IdeaFrameFixture.java @@ -5,6 +5,19 @@ */ package com.android.tools.idea.tests.gui.framework.fixture; +import static com.android.tools.idea.gradle.util.BuildMode.ASSEMBLE; +import static com.android.tools.idea.gradle.util.BuildMode.SOURCE_GEN; +import static com.android.tools.idea.gradle.util.GradleUtil.getGradleBuildFile; +import static com.android.tools.idea.ui.GuiTestingService.EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY; +import static java.awt.event.InputEvent.CTRL_MASK; +import static java.awt.event.InputEvent.META_MASK; +import static org.fest.swing.edt.GuiActionRunner.execute; +import static org.jetbrains.plugins.gradle.settings.DistributionType.LOCAL; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import com.android.ide.common.repository.GradleVersion; import com.android.tools.idea.gradle.dsl.api.GradleBuildModel; import com.android.tools.idea.gradle.plugin.AndroidPluginVersionUpdater; @@ -26,9 +39,7 @@ import com.android.tools.idea.tests.gui.framework.fixture.gradle.GradleBuildModelFixture; import com.android.tools.idea.tests.gui.framework.fixture.gradle.GradleProjectEventListener; import com.android.tools.idea.tests.gui.framework.fixture.gradle.GradleToolWindowFixture; -import com.android.tools.idea.projectsystem.TestProjectSystem; import com.android.tools.idea.tests.gui.framework.matcher.Matchers; -import com.android.tools.idea.ui.GuiTestingService; import com.google.common.collect.Lists; import com.intellij.ide.actions.ShowSettingsUtilImpl; import com.intellij.openapi.Disposable; @@ -46,6 +57,21 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.util.ThreeState; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.KeyboardFocusManager; +import java.awt.Point; +import java.awt.event.KeyEvent; +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; import org.fest.swing.core.GenericTypeMatcher; import org.fest.swing.core.Robot; import org.fest.swing.edt.GuiQuery; @@ -60,39 +86,13 @@ import org.jetbrains.plugins.gradle.settings.GradleProjectSettings; import org.jetbrains.plugins.gradle.settings.GradleSettings; -import javax.swing.*; -import java.awt.*; -import java.awt.event.KeyEvent; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static com.android.tools.idea.gradle.util.BuildMode.ASSEMBLE; -import static com.android.tools.idea.gradle.util.BuildMode.SOURCE_GEN; -import static com.android.tools.idea.gradle.util.GradleUtil.getGradleBuildFile; -import static com.android.tools.idea.ui.GuiTestingService.EXECUTE_BEFORE_PROJECT_BUILD_IN_GUI_TEST_KEY; -import static java.awt.event.InputEvent.CTRL_MASK; -import static java.awt.event.InputEvent.META_MASK; -import static junit.framework.Assert.assertNotNull; -import static junit.framework.Assert.fail; -import static org.fest.swing.edt.GuiActionRunner.execute; -import static org.jetbrains.plugins.gradle.settings.DistributionType.LOCAL; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -@SuppressWarnings("Duplicates") // Adapted from IdeFrameFixture in uitest-framework module, due to private constructor. +@SuppressWarnings({"Duplicates", "unused", "UnusedReturnValue", "RedundantSuppression", "SameParameterValue", "deprecation"}) +// Adapted from IdeFrameFixture in uitest-framework module, due to private constructor. public class IdeaFrameFixture extends ComponentFixture { @NotNull private final GradleProjectEventListener myGradleProjectEventListener; @NotNull private final Modules myModules; @NotNull private final IdeFrameFixture myIdeFrameFixture; // Replaces 'this' when creating component fixtures. - private TestProjectSystem myTestProjectSystem; private EditorFixture myEditor; private boolean myIsClosed; @@ -111,11 +111,6 @@ public class IdeaFrameFixture extends ComponentFixture resultRef.get() != null); + .until(() -> resultRef.get() != null); return resultRef.get(); } @@ -353,7 +348,7 @@ public FileFixture findExistingFileByRelativePath(@NotNull String relativePath) * Returns the virtual file corresponding to the given path. The path must be relative to the project root directory * (the top-level directory containing all source files associated with the project). * - * @param relativePath a file path relative to the project root directory + * @param relativePath a file path relative to the project root directory * @param requireExists if true, this method asserts that the given path corresponds to an existing file * @return the virtual file corresponding to the given path, or null if requireExists is false and the file does not exist */ @@ -373,10 +368,6 @@ public VirtualFile findFileByRelativePath(@NotNull String relativePath, boolean return file; } - public void setTestProjectSystem(TestProjectSystem testProjectSystem) { - myTestProjectSystem = testProjectSystem; - } - @NotNull public IdeaFrameFixture requestProjectSync() { return requestProjectSync(null); @@ -608,13 +599,13 @@ public IdeaFrameFixture setGradleJvmArgs(@NotNull String jvmArgs) { } @NotNull - public IdeaFrameFixture updateGradleWrapperVersion(@NotNull String version) throws IOException { + public IdeaFrameFixture updateGradleWrapperVersion(@NotNull String version) { GradleWrapper.find(getProject()).updateDistributionUrlAndDisplayFailure(version); return this; } @NotNull - public IdeaFrameFixture updateAndroidGradlePluginVersion(@NotNull String version) throws IOException { + public IdeaFrameFixture updateAndroidGradlePluginVersion(@NotNull String version) { ApplicationManager.getApplication().invokeAndWait( () -> { AndroidPluginVersionUpdater versionUpdater = AndroidPluginVersionUpdater.getInstance(getProject()); @@ -631,19 +622,13 @@ public GradleBuildModelFixture parseBuildFileForModule(@NotNull String moduleNam Ref buildModelRef = new Ref<>(); new ReadAction() { @Override - protected void run(@NotNull Result result) throws Throwable { + protected void run(@NotNull Result result) { buildModelRef.set(GradleBuildModel.parseBuildFile(buildFile, getProject())); } }.execute(); return new GradleBuildModelFixture(buildModelRef.get()); } - private static class NoOpDisposable implements Disposable { - @Override - public void dispose() { - } - } - public void selectApp(@NotNull String appName) { ActionButtonFixture runButton = findRunApplicationButton(); Container actionToolbarContainer = GuiQuery.getNonNull(() -> runButton.target().getParent()); @@ -697,4 +682,15 @@ public IdeaFrameFixture setIdeFrameSize(@NotNull Dimension size) { target().setSize(size); return this; } + + @NotNull + public static IdeaFrameFixture find(@NotNull final Robot robot) { + return new IdeaFrameFixture(robot, GuiTests.waitUntilShowing(robot, Matchers.byType(IdeFrameImpl.class))); + } + + private static class NoOpDisposable implements Disposable { + @Override + public void dispose() { + } + } } diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MacMenuFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MacMenuFixture.java index 73cf7356d6..46a94324dc 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MacMenuFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MacMenuFixture.java @@ -5,6 +5,9 @@ */ package com.android.tools.idea.tests.gui.framework.fixture; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + import com.android.tools.idea.tests.gui.framework.GuiTests; import com.android.tools.idea.tests.gui.framework.matcher.Matchers; import com.google.common.base.Joiner; @@ -12,18 +15,15 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Ref; import com.intellij.openapi.wm.impl.IdeFrameImpl; +import java.awt.Container; +import java.util.Arrays; +import java.util.List; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; import org.fest.swing.core.Robot; import org.fest.swing.timing.Wait; import org.jetbrains.annotations.NotNull; -import javax.swing.*; -import java.awt.*; -import java.util.Arrays; -import java.util.List; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - // Use the MenuItemFixture from fest to control Mac menus. public class MacMenuFixture extends MenuFixture { diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuItemFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuItemFixture.java index 34be81f830..506984c5d2 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuItemFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/MenuItemFixture.java @@ -5,12 +5,12 @@ */ package com.android.tools.idea.tests.gui.framework.fixture; +import javax.swing.JMenuItem; import org.fest.swing.core.Robot; import org.fest.swing.fixture.JMenuItemFixture; import org.jetbrains.annotations.NotNull; -import javax.swing.*; - +@SuppressWarnings("UnusedReturnValue") public class MenuItemFixture extends JMenuItemFixture { public MenuItemFixture(@NotNull Robot robot, @NotNull JMenuItem target) { super(robot, target); diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterProjectStepFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterProjectStepFixture.java index c81dbc720e..6c0ad7b694 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterProjectStepFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterProjectStepFixture.java @@ -5,26 +5,28 @@ */ package com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard; +import static com.google.common.truth.Truth.assertThat; +import static org.fest.swing.edt.GuiActionRunner.execute; + import com.android.tools.idea.tests.gui.framework.fixture.wizard.AbstractWizardFixture; import com.android.tools.idea.tests.gui.framework.fixture.wizard.AbstractWizardStepFixture; import com.intellij.openapi.ui.TextFieldWithBrowseButton; import com.intellij.ui.components.JBLabel; import io.flutter.project.FlutterProjectStep; +import java.awt.Component; +import java.io.File; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JRootPane; +import javax.swing.text.JTextComponent; import org.fest.swing.edt.GuiQuery; import org.fest.swing.exception.ComponentLookupException; import org.fest.swing.fixture.JComboBoxFixture; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.*; -import javax.swing.text.JTextComponent; -import java.awt.*; -import java.io.File; - -import static com.google.common.truth.Truth.assertThat; -import static org.fest.swing.edt.GuiActionRunner.execute; - // TODO(messick): Browse button for SDK; "Install SDK" button +@SuppressWarnings({"UnusedReturnValue", "unused"}) public class FlutterProjectStepFixture extends AbstractWizardStepFixture { protected FlutterProjectStepFixture(@NotNull W wizard, @NotNull JRootPane target) { super(FlutterProjectStepFixture.class, wizard, target); @@ -84,7 +86,7 @@ public File getLocationInFileSystem() { final TextFieldWithBrowseButton locationField = getLocationField(); return execute(new GuiQuery() { @Override - protected File executeInEDT() throws Throwable { + protected File executeInEDT() { String location = locationField.getText(); assertThat(location).isNotEmpty(); return new File(location); diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterSettingsStepFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterSettingsStepFixture.java index dcbcb63415..62dd84cc7b 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterSettingsStepFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/FlutterSettingsStepFixture.java @@ -5,20 +5,22 @@ */ package com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard; +import static com.google.common.truth.Truth.assertThat; +import static org.fest.swing.edt.GuiActionRunner.execute; + import com.android.tools.adtui.LabelWithEditButton; import com.android.tools.idea.tests.gui.framework.fixture.wizard.AbstractWizardFixture; import com.android.tools.idea.tests.gui.framework.fixture.wizard.AbstractWizardStepFixture; import io.flutter.FlutterBundle; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JRootPane; +import javax.swing.text.JTextComponent; import org.fest.swing.edt.GuiQuery; import org.fest.swing.fixture.JCheckBoxFixture; import org.jetbrains.annotations.NotNull; -import javax.swing.*; -import javax.swing.text.JTextComponent; - -import static com.google.common.truth.Truth.assertThat; -import static org.fest.swing.edt.GuiActionRunner.execute; - +@SuppressWarnings({"UnusedReturnValue", "unused"}) public class FlutterSettingsStepFixture extends AbstractWizardStepFixture { protected FlutterSettingsStepFixture(@NotNull W wizard, @NotNull JRootPane target) { @@ -56,7 +58,7 @@ public String getPackageName() { final LabelWithEditButton locationField = robot().finder().findByType(target(), LabelWithEditButton.class); return execute(new GuiQuery() { @Override - protected String executeInEDT() throws Throwable { + protected String executeInEDT() { String location = locationField.getText(); assertThat(location).isNotEmpty(); return location; diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterModuleWizardFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterModuleWizardFixture.java index f512518387..00aaaedb48 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterModuleWizardFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterModuleWizardFixture.java @@ -5,21 +5,21 @@ */ package com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard; +import static com.android.tools.idea.tests.gui.framework.GuiTests.findAndClickButton; + import com.android.tools.adtui.ASGallery; import com.android.tools.idea.tests.gui.framework.GuiTests; import com.android.tools.idea.tests.gui.framework.fixture.IdeaFrameFixture; import com.android.tools.idea.tests.gui.framework.fixture.wizard.AbstractWizardFixture; import com.android.tools.idea.tests.gui.framework.matcher.Matchers; import io.flutter.module.FlutterProjectType; +import javax.swing.JDialog; +import javax.swing.JRootPane; import org.fest.swing.core.Robot; import org.fest.swing.fixture.JListFixture; import org.fest.swing.timing.Wait; import org.jetbrains.annotations.NotNull; -import javax.swing.*; - -import static com.android.tools.idea.tests.gui.framework.GuiTests.findAndClickButton; - public class NewFlutterModuleWizardFixture extends AbstractWizardFixture { private NewFlutterModuleWizardFixture(@NotNull Robot robot, @NotNull JDialog target) { diff --git a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterProjectWizardFixture.java b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterProjectWizardFixture.java index bac4921000..093793b2f3 100644 --- a/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterProjectWizardFixture.java +++ b/flutter-studio/testSrc/com/android/tools/idea/tests/gui/framework/fixture/newProjectWizard/NewFlutterProjectWizardFixture.java @@ -5,6 +5,8 @@ */ package com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard; +import static com.google.common.collect.Lists.newArrayList; + import com.android.tools.adtui.ASGallery; import com.android.tools.idea.tests.gui.framework.GuiTests; import com.android.tools.idea.tests.gui.framework.fixture.wizard.AbstractWizardFixture; @@ -13,17 +15,16 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import io.flutter.module.FlutterProjectType; +import java.util.List; +import javax.swing.JDialog; +import javax.swing.JRootPane; import org.fest.swing.core.Robot; import org.fest.swing.fixture.JListFixture; import org.fest.swing.timing.Wait; import org.jetbrains.annotations.NotNull; -import javax.swing.*; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; - // Adapted from com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.NewProjectWizardFixture +@SuppressWarnings("UnusedReturnValue") public class NewFlutterProjectWizardFixture extends AbstractWizardFixture { private NewFlutterProjectWizardFixture(@NotNull Robot robot, @NotNull JDialog target) { diff --git a/flutter-studio/testSrc/io/flutter/GradleDependencyFetcherTest.java b/flutter-studio/testSrc/io/flutter/GradleDependencyFetcherTest.java index e17f9a9b4a..4b59c57396 100644 --- a/flutter-studio/testSrc/io/flutter/GradleDependencyFetcherTest.java +++ b/flutter-studio/testSrc/io/flutter/GradleDependencyFetcherTest.java @@ -5,6 +5,11 @@ */ package io.flutter; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import com.intellij.mock.MockApplication; import com.intellij.mock.MockProject; import com.intellij.openapi.Disposable; @@ -13,13 +18,10 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import io.flutter.android.GradleDependencyFetcher; -import org.junit.BeforeClass; -import org.junit.Test; - import java.util.List; import java.util.Map; - -import static org.junit.Assert.*; +import org.junit.BeforeClass; +import org.junit.Test; public class GradleDependencyFetcherTest { diff --git a/flutter-studio/testSrc/io/flutter/tests/gui/NewModuleTest.java b/flutter-studio/testSrc/io/flutter/tests/gui/NewModuleTest.java index 8b3bc879cb..2956d12ee0 100644 --- a/flutter-studio/testSrc/io/flutter/tests/gui/NewModuleTest.java +++ b/flutter-studio/testSrc/io/flutter/tests/gui/NewModuleTest.java @@ -5,58 +5,37 @@ */ package io.flutter.tests.gui; +import static com.google.common.truth.Truth.assertThat; + import com.android.tools.idea.tests.gui.framework.FlutterGuiTestRule; -import com.android.tools.idea.tests.gui.framework.GuiTestSuiteRunner; import com.android.tools.idea.tests.gui.framework.fixture.EditorFixture; import com.android.tools.idea.tests.gui.framework.fixture.FlutterFrameFixture; import com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.FlutterProjectStepFixture; import com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.FlutterSettingsStepFixture; import com.android.tools.idea.tests.gui.framework.fixture.newProjectWizard.NewFlutterModuleWizardFixture; -import com.intellij.openapi.application.PathManager; import io.flutter.module.FlutterProjectType; -import io.flutter.tests.util.WizardUtils; +import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.RunnerBuilder; - -import java.io.IOException; - -import static com.google.common.truth.Truth.assertThat; @RunWith(NewModuleTest.GuiTestRemoteRunner.class) public class NewModuleTest { - /** - * This custom runner sets a custom path to the GUI tests. - * This needs to be done by the test runner because the test framework - * initializes the path before the test class is loaded. - */ - public static class GuiTestRemoteRunner extends com.intellij.testGuiFramework.framework.GuiTestRemoteRunner { - - public GuiTestRemoteRunner(Class suiteClass) { - super(suiteClass); - System.setProperty("gui.tests.root.dir.path", "somewhere"); - } - - } @Rule public final FlutterGuiTestRule myGuiTest = new FlutterGuiTestRule(); @Test - public void createNewAppModule() { - PathManager.getHomePath(); - WizardUtils.createNewApplication(myGuiTest); - FlutterFrameFixture ideFrame = myGuiTest.ideFrame(); + public void createNewAppModule() throws IOException { + FlutterFrameFixture ideFrame = myGuiTest.importSimpleApplication(); EditorFixture editor = ideFrame.getEditor(); editor.waitUntilErrorAnalysisFinishes(); NewFlutterModuleWizardFixture wizardFixture = ideFrame.openFromMenu(NewFlutterModuleWizardFixture::find, "File", "New", "New Module..."); - wizardFixture.chooseModuleType("Flutter Application").clickNext(); + wizardFixture.chooseModuleType("Flutter Package").clickNext(); NewFlutterModuleWizardFixture wizard = ideFrame.findNewModuleWizard(); - FlutterProjectStepFixture projectStep = wizard.getFlutterProjectStep(FlutterProjectType.APP); + FlutterProjectStepFixture projectStep = wizard.getFlutterProjectStep(FlutterProjectType.PACKAGE); assertThat(projectStep.isConfiguredForModules()).isTrue(); // Check error messages. @@ -73,22 +52,32 @@ public void createNewAppModule() { assertThat(projectStep.getErrorMessage()).contains("less than"); projectStep.enterProjectName("module"); - String path = projectStep.getSdkPath(); - projectStep.enterSdkPath(""); - // This does not work. The message comes back as " ". It does work in manual testing. - //assertThat(projectStep.getErrorMessage()).endsWith(("not given.")); - projectStep.enterSdkPath("x"); - assertThat(projectStep.getErrorMessage()).endsWith(("not exist.")); - projectStep.enterSdkPath("/tmp"); - assertThat(projectStep.getErrorMessage()).endsWith(("location.")); - projectStep.enterSdkPath(path); - wizard.clickNext(); - - FlutterSettingsStepFixture settingsStep = wizard.getFlutterSettingsStep(); - settingsStep.enterCompanyDomain("flutter.io"); + // TODO(messick) Fix SDK path tests + //String path = projectStep.getSdkPath(); + //projectStep.enterSdkPath(""); + //// This does not work. The message comes back as " ". It does work in manual testing. + ////assertThat(projectStep.getErrorMessage()).endsWith(("not given.")); + //projectStep.enterSdkPath("x"); + //assertThat(projectStep.getErrorMessage()).endsWith(("not exist.")); + //projectStep.enterSdkPath("/tmp"); + //assertThat(projectStep.getErrorMessage()).endsWith(("location.")); + //projectStep.enterSdkPath(path); wizard.clickFinish(); myGuiTest.waitForBackgroundTasks(); myGuiTest.ideFrame().waitForProjectSyncToFinish(); } + + /** + * This custom runner sets a custom path to the GUI tests. + * This needs to be done by the test runner because the test framework + * initializes the path before the test class is loaded. + */ + public static class GuiTestRemoteRunner extends com.intellij.testGuiFramework.framework.GuiTestRemoteRunner { + + public GuiTestRemoteRunner(Class suiteClass) { + super(suiteClass); + System.setProperty("gui.tests.root.dir.path", "somewhere"); + } + } } diff --git a/flutter-studio/testSrc/io/flutter/tests/gui/NewProjectTest.java b/flutter-studio/testSrc/io/flutter/tests/gui/NewProjectTest.java index edd5dc12f3..a9f30b92e8 100644 --- a/flutter-studio/testSrc/io/flutter/tests/gui/NewProjectTest.java +++ b/flutter-studio/testSrc/io/flutter/tests/gui/NewProjectTest.java @@ -5,6 +5,9 @@ */ package io.flutter.tests.gui; +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; + import com.android.tools.idea.tests.gui.framework.FlutterGuiTestRule; import com.android.tools.idea.tests.gui.framework.GuiTestSuiteRunner; import com.android.tools.idea.tests.gui.framework.fixture.EditorFixture; @@ -18,18 +21,15 @@ import org.junit.Test; import org.junit.runner.RunWith; -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertEquals; - /** * As long as the wizard is working properly the error checks * in FlutterProjectCreator will never be triggered. That leaves * quite a few lines untested. It currently has 79% coverage. - * + *

* The "Install SDK" button of FlutterProjectStep is not tested. * It has 86% coverage currently, and most of the untested code * is part of the installer implementation. - * + *

* If flakey tests are found try adjusting these settings: * Settings festSettings = myGuiTest.robot().settings(); * festSettings.delayBetweenEvents(50); // 30 diff --git a/flutter-studio/testSrc/io/flutter/tests/util/ProjectWrangler.java b/flutter-studio/testSrc/io/flutter/tests/util/ProjectWrangler.java new file mode 100644 index 0000000000..e7d3319da2 --- /dev/null +++ b/flutter-studio/testSrc/io/flutter/tests/util/ProjectWrangler.java @@ -0,0 +1,191 @@ +/* + * 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.tests.util; + +import static com.android.tools.idea.util.ToolWindows.activateProjectView; +import static com.intellij.ide.impl.ProjectUtil.focusProjectWindow; +import static com.intellij.openapi.fileChooser.impl.FileChooserUtil.setLastOpenedFile; +import static com.intellij.openapi.ui.Messages.showErrorDialog; +import static com.intellij.openapi.util.io.FileUtil.toCanonicalPath; +import static com.intellij.openapi.util.io.FileUtil.toSystemDependentName; +import static com.intellij.util.ExceptionUtil.rethrowUnchecked; + +import com.android.tools.idea.tests.gui.framework.GuiTests; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; +import com.intellij.openapi.application.TransactionGuard; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.FileUtilRt; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.platform.PlatformProjectOpenProcessor; +import com.intellij.platform.templates.github.ZipUtil; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.EnumSet; +import java.util.List; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; +import org.jdom.xpath.XPath; +import org.jetbrains.annotations.NotNull; + +/** + * ProjectWrangler encapsulates the project operations required by GUI tests. + * The testData directory must be identified as a test resource in the Project + * Structure dialog. + *

+ * Sample projects are found in $MODULE_NAME/testData/PROJECT_DIR + * During a test build everything in testData (but NOT testData itself) is + * copied to the working directory under out/test. When a sample project is + * opened or imported it is first copied to a temp directory, then opened. + *

+ * Opening a project uses it as-is. Importing a project first deletes IntelliJ + * meta-data, like .idea and *.iml. + */ +@SuppressWarnings("deprecation") +public class ProjectWrangler { + + // Name of the module that defines GUI tests + public static final String MODULE_NAME = "flutter-studio"; + + // Name of the directory under testData where test projects are hosted during testing + public static final String PROJECT_DIR = "flutter_projects"; + + private static final String SRC_ZIP_NAME = "src.zip"; + + @NotNull final private String myTestDirectory; + + public ProjectWrangler(@NotNull String dirName) { + myTestDirectory = dirName; + } + + public void openProject(@NotNull VirtualFile selectedFile) { + VirtualFile projectFolder = findProjectFolder(selectedFile); + try { + doOpenProject(projectFolder); + } + catch (Throwable e) { + if (ApplicationManager.getApplication().isUnitTestMode()) { + rethrowUnchecked(e); + } + showErrorDialog(e.getMessage(), "Open Project"); + getLogger().error(e); + } + } + + @NotNull + private Logger getLogger() { + return Logger.getInstance(getClass()); + } + + public File setUpProject(@NotNull String projectDirName, boolean isImport) throws IOException { + File projectPath = copyProjectBeforeOpening(projectDirName); + if (isImport) { + cleanUpProjectForImport(projectPath); + } + return projectPath; + } + + public File copyProjectBeforeOpening(@NotNull String projectDirName) throws IOException { + File masterProjectPath = getMasterProjectDirPath(projectDirName); + + File projectPath = getTestProjectDirPath(projectDirName); + if (projectPath.isDirectory()) { + FileUtilRt.delete(projectPath); + } + // If masterProjectPath contains a src.zip file, unzip the file to projectPath. + // Otherwise, copy the whole directory to projectPath. + File srcZip = new File(masterProjectPath, SRC_ZIP_NAME); + if (srcZip.exists() && srcZip.isFile()) { + ZipUtil.unzip(null, projectPath, srcZip, null, null, true); + } + else { + FileUtil.copyDir(masterProjectPath, projectPath); + } + return projectPath; + } + + @NotNull + private File getTestProjectDirPath(@NotNull String projectDirName) { + assert (myTestDirectory != null); + return new File(GuiTests.getProjectCreationDirPath(myTestDirectory), projectDirName); + } + + public void cleanUpProjectForImport(@NotNull File projectPath) { + File dotIdeaFolderPath = new File(projectPath, Project.DIRECTORY_STORE_FOLDER); + if (dotIdeaFolderPath.isDirectory()) { + File modulesXmlFilePath = new File(dotIdeaFolderPath, "modules.xml"); + if (modulesXmlFilePath.isFile()) { + SAXBuilder saxBuilder = new SAXBuilder(); + try { + Document document = saxBuilder.build(modulesXmlFilePath); + XPath xpath = XPath.newInstance("//*[@fileurl]"); + //noinspection unchecked + List modules = xpath.selectNodes(document); + int urlPrefixSize = "file://$PROJECT_DIR$/".length(); + for (Element module : modules) { + String fileUrl = module.getAttributeValue("fileurl"); + if (!StringUtil.isEmpty(fileUrl)) { + String relativePath = toSystemDependentName(fileUrl.substring(urlPrefixSize)); + File imlFilePath = new File(projectPath, relativePath); + if (imlFilePath.isFile()) { + FileUtilRt.delete(imlFilePath); + } + // It is likely that each module has a "build" folder. Delete it as well. + File buildFilePath = new File(imlFilePath.getParentFile(), "build"); + if (buildFilePath.isDirectory()) { + FileUtilRt.delete(buildFilePath); + } + } + } + } + catch (Throwable ignored) { + // if something goes wrong, just ignore. Most likely it won't affect project import in any way. + } + } + FileUtilRt.delete(dotIdeaFolderPath); + } + } + + @NotNull + private static File getMasterProjectDirPath(@NotNull String projectDirName) { + return new File(ProjectWrangler.getTestProjectsRootDirPath(), projectDirName); + } + + @NotNull + private static File getTestProjectsRootDirPath() { + // It is important that the testData directory be marked as a test resource so its content is copied to out/test dir + String testDataPath = PathManager.getHomePathFor(ProjectWrangler.class); + // "out/test" is defined by IntelliJ but we may want to change the module or root dir of the test projects. + testDataPath = Paths.get(testDataPath, "out", "test", MODULE_NAME).toString(); + testDataPath = toCanonicalPath(toSystemDependentName(testDataPath)); + return new File(testDataPath, PROJECT_DIR); + } + + @NotNull + private static VirtualFile findProjectFolder(@NotNull VirtualFile selectedFile) { + return selectedFile.isDirectory() ? selectedFile : selectedFile.getParent(); + } + + private static void afterProjectOpened(@NotNull VirtualFile projectFolder, @NotNull Project project) { + TransactionGuard.getInstance().submitTransactionLater(project, () -> { + setLastOpenedFile(project, projectFolder); + focusProjectWindow(project, false); + activateProjectView(project); + }); + } + + private static void doOpenProject(VirtualFile baseDir) { + // Open the project window. + EnumSet options = EnumSet.noneOf(PlatformProjectOpenProcessor.Option.class); + Project project = PlatformProjectOpenProcessor.doOpenProject(baseDir, null, -1, null, options); + afterProjectOpened(baseDir, project); + } +} diff --git a/flutter-studio/testSrc/io/flutter/tests/util/WizardUtils.java b/flutter-studio/testSrc/io/flutter/tests/util/WizardUtils.java index ba8dae7690..f273746d84 100644 --- a/flutter-studio/testSrc/io/flutter/tests/util/WizardUtils.java +++ b/flutter-studio/testSrc/io/flutter/tests/util/WizardUtils.java @@ -29,7 +29,7 @@ public static void createNewPlugin(@NotNull FlutterGuiTestRule guiTest) { public static void createNewProject(@NotNull FlutterGuiTestRule guiTest, @NotNull FlutterProjectType type, String name, String description, String domain, Boolean isKotlin, Boolean isSwift) { - String sdkPath = FlutterSdkUtil.locateSdkFromPath(); + String sdkPath = FlutterSdkUtil.locateSdkFromPath(); if (sdkPath == null) { // Fail fast if the Flutter SDK is not found. System.out.println("Ensure the 'flutter' tool is on your PATH. 'which flutter' is used to find the SDK"); diff --git a/gen/icons/FlutterIcons.java b/gen/icons/FlutterIcons.java index 2f093d776a..73f6e98b57 100644 --- a/gen/icons/FlutterIcons.java +++ b/gen/icons/FlutterIcons.java @@ -25,6 +25,7 @@ private static Icon load(String path) { public static final Icon OpenObservatory = load("/icons/observatory.png"); public static final Icon OpenObservatoryGroup = load("/icons/observatory_overflow.png"); + public static final Icon Dart_16 = load("/icons/dart_16.svg"); public static final Icon OpenTimeline = load("/icons/timeline.png"); diff --git a/lib/dart-plugin/182.5215/Dart.jar b/lib/dart-plugin/182.5215/Dart.jar new file mode 100755 index 0000000000..9a9155a881 Binary files /dev/null and b/lib/dart-plugin/182.5215/Dart.jar differ diff --git a/lib/dart-plugin/182.5215/resources_en.jar b/lib/dart-plugin/182.5215/resources_en.jar new file mode 100644 index 0000000000..67af2fceaa Binary files /dev/null and b/lib/dart-plugin/182.5215/resources_en.jar differ diff --git a/product-matrix.json b/product-matrix.json index 2b9623875f..a7b90449f3 100644 --- a/product-matrix.json +++ b/product-matrix.json @@ -1,44 +1,47 @@ { "list": [ { - "comments": "AS 3.3 and IntelliJ 2018.2.5", - "name": "IntelliJ", - "version": "3.3", + "comments": "AS 3.3.2", + "name": "Android Studio 3.3.2", + "version": "3.3.2", "ideaProduct": "android-studio", - "ideaVersion": "182.5199772", - "dartPluginVersion": "182.5124", - "sinceBuild": "182.5107.7", + "ideaVersion": "182.5314842", + "dartPluginVersion": "182.5215", + "sinceBuild": "182.5107.16", "untilBuild": "182.*" }, { - "comments": "AS 3.4 beta", - "name": "Android Studio", + "comments": "IntelliJ 2018.3, AS 3.4 beta", + "name": "Android Studio 3.4", "version": "3.4", + "ijVersion": "2018.3", "ideaProduct": "android-studio", - "ideaVersion": "183.5217543", - "dartPluginVersion": "183.4886.3", - "sinceBuild": "183.0", - "untilBuild": "183.4886.37.34.*" + "ideaVersion": "183.5370308", + "dartPluginVersion": "183.5901", + "sinceBuild": "183.5153", + "untilBuild": "183.*" }, { - "comments": "IntelliJ 2018.3, AS 3.5 canary", - "name": "Android Studio", - "version": "2018.3", + "comments": "IntelliJ 2019.1, AS 3.5 canary", + "name": "Android Studio 3.5", + "version": "3.5", + "ijVersion": "2019.1", "ideaProduct": "android-studio", - "ideaVersion": "183.5215047", - "dartPluginVersion": "183.4886.3", - "sinceBuild": "183.4886.37.35", - "untilBuild": "183.*" + "ideaVersion": "191.5416148", + "dartPluginVersion": "191.6183.87", + "sinceBuild": "191.5416148", + "untilBuild": "191.*" }, { - "comments": "IntelliJ 2019.1 EAP", - "name": "IntelliJ 2019.1 EAP", - "version": "2019.1", - "ideaProduct": "ideaIC", - "ideaVersion": "191.4738.6", + "comments": "IntelliJ 2019.2 EAP", + "name": "IntelliJ 2019.2 EAP", + "version": "2019.2", + "isTestTarget": "true", + "ideaProduct": "ideaIU", + "ideaVersion": "2019.1", "dartPluginVersion": "191.4899", "sinceBuild": "191.0", - "untilBuild": "191.*" + "untilBuild": "192.SNAPSHOT" } ] } diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 27c82cbca8..bba9f40de2 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -12,8 +12,8 @@ flutter.io Custom Languages - - + SNAPSHOT + Dart @@ -29,6 +29,94 @@ 35.0 +

    +
  • Sample panel layout improvements (#3396)
  • +
  • Remove unneeded logging (#3394)
  • +
  • Java analysis lints cleanup (#3395)
  • +
  • Update subscriptions after analysis server restart (#3393)
  • +
  • Read sample index from flutter_tool call (#3379)
  • +
  • Update README (#3387)
  • +
  • Fix unit tests
  • +
  • Update build for canary 11 (#3380)
  • +
  • Integration test update (#3374)
  • +
  • Make the inspector easier to test (#3373)
  • +
  • Adjust build to make plugin for testing (#3366)
  • +
  • Address reported Java lints (#3356)
  • +
  • Adjust build for AS canary 10
  • +
  • Address an array index out of bounds (#3355)
  • +
  • Address an NPE (#3354)
  • +
  • Upgrade the service protocol library (#3353)
  • +
  • Address a number format exception (#3352)
  • +
  • Update how we manipulate the service protocol url (#3351)
  • +
  • Remove some uses of reflection (#3350)
  • +
  • Some initial work for FlutterWeb apps (#3342)
  • +
  • Fix an NPE when sample content generation is disabled (#3336)
  • +
  • Add inspector dependency to test (#3316)
  • +
  • Make Dart constructor calls pop out in light mode (#3327)
  • +
+

34.0

+
    +
  • Update build for Android Studio 3.3.2 and IntelliJ 2019.1 (#3321)
  • +
  • Fix issue preventing plugin from working in AS Canary 8 (#3321)
  • +
  • Provides a better display if the variable has a toStringDeep() method defined. (#3291)
  • +
  • Don't show a background square in the inspector summary tree. (#3326)
  • +
  • Make FlutterModuleUtils consistently robust to disposed projects. (#3323)
  • +
  • Fix NPE issue sometimes hit evaluating expressions. (#3324)
  • +
  • Fix widget names. (#3322)
  • +
  • Make Perf and Inspector views only display when a Flutter app is being debugged. (#3320)
  • +
  • Support the inspector for flutter_web libraries. (#3310)
  • +
  • Detect when integrations tests are running (#3308)
  • +
  • Add in support for reloading and restarting all running apps (#3268)
  • +
  • Log tree path selection fixes (#3302)
  • +
  • Throttle logger updates (#3280)
  • +
  • New method in FlutterUtils: declaresFlutterWeb, this method checks for dependencies: fluttler_web in a pubspec file. (#3275)
  • +
  • Update a comment in FlutterSaveActionsManager (#3277)
  • +
  • Remove the second parameter (the Project) from SdkFields constructor, it isn't used anymore. (#3261)
  • +
  • Add a comment to a recent change (#3267)
  • +
  • Fix a file handle leak (#3264)
  • +
  • Port inferPubRootDirectoryIfNeeded from devtools (#3242)
  • +
  • Add support for matching customized Widget tests. (#3249)
  • +
  • Hide DevTools debugger when launching from IntelliJ. (#3252)
  • +
  • Migrate to GearPlain (#3248)
  • +
  • Minor cleanup (#3247)
  • +
  • Inline sample index reading (#3245)
  • +
  • Make a newer daemon protocol field optional (#3230)
  • +
  • Link to the plugins readme file from the building instructions. (#3222)
  • +
+

33.3

+
    +
  • Fix an issue with an IllegalArgumentException when running Flutter apps
  • +
+

33.2

+
    +
  • Support IntelliJ 2018.3.3
  • +
+

33.1

+
    +
  • add menu and toolbar button to open Flutter DevTools
  • +
  • fix Gradle sync issue for Android Studio 3.3.1
  • +
  • fix highlighting of the Go link in sample banner
  • +
+

33.0

+
    +
  • update build for Android Studio 3.3.1
  • +
  • reorder console filters so links work
  • +
  • more intelligently enable support for detaching from Flutter apps on exit
  • +
  • change the icon used for paint baselines
  • +
  • prevent bazel test run configurations from generating in a non-bazel workspace
  • +
  • support 2019.1 eap
  • +
  • mention 'Dart' in the plugin description
  • +
  • correct the bazel output for debugging bazel tests
  • +
  • simplify the bazel parameters we pass to Bazel Run configurations
  • +
  • pin flutter error events in the log
  • +
  • propagate node selections to inspector
  • +
  • link support for log data entries
  • +
  • fix category cell rendering
  • +
  • add sample creation banner
  • +
  • add sample apps to Android Studio New Project Wizard
  • +
  • update log entry data badge
  • +

32.0

  • address an NPE in FlutterWidgetPerfManager.java
  • @@ -646,7 +734,7 @@ - 
 + @@ -697,6 +785,18 @@ + + + + + + + + + + + @@ -820,6 +924,10 @@ serviceImplementation="io.flutter.perf.FlutterWidgetPerfManager" overrides="false"/> + + @@ -827,6 +935,7 @@ + diff --git a/resources/META-INF/pluginIcon.svg b/resources/META-INF/pluginIcon.svg new file mode 100644 index 0000000000..fc73075f0a --- /dev/null +++ b/resources/META-INF/pluginIcon.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/META-INF/plugin_template.xml b/resources/META-INF/plugin_template.xml index dfdbb69e0b..7000ec86e1 100644 --- a/resources/META-INF/plugin_template.xml +++ b/resources/META-INF/plugin_template.xml @@ -97,7 +97,7 @@ - 
 + @@ -148,6 +148,18 @@ + + + + + + + + + + + @@ -271,6 +287,10 @@ serviceImplementation="io.flutter.perf.FlutterWidgetPerfManager" overrides="false"/> + + @@ -278,6 +298,7 @@ + diff --git a/resources/META-INF/studio-contribs.xml b/resources/META-INF/studio-contribs.xml index 1ea8741567..f1361e6d82 100644 --- a/resources/META-INF/studio-contribs.xml +++ b/resources/META-INF/studio-contribs.xml @@ -50,6 +50,11 @@ icon="AllIcons.Debugger.AttachToProcess"> + + diff --git a/resources/META-INF/studio-contribs_template.xml b/resources/META-INF/studio-contribs_template.xml index f58f1dea36..f9442adcc9 100644 --- a/resources/META-INF/studio-contribs_template.xml +++ b/resources/META-INF/studio-contribs_template.xml @@ -48,6 +48,11 @@ icon="AllIcons.Debugger.AttachToProcess"> + + diff --git a/resources/colorSchemes/FlutterCodeColorSchemeDefault.xml b/resources/colorSchemes/FlutterCodeColorSchemeDefault.xml new file mode 100644 index 0000000000..6a63e4d67c --- /dev/null +++ b/resources/colorSchemes/FlutterCodeColorSchemeDefault.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/resources/icons/dart_16.svg b/resources/icons/dart_16.svg new file mode 100644 index 0000000000..5ce9cbc9f0 --- /dev/null +++ b/resources/icons/dart_16.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/resources/icons/dart_16_dark.svg b/resources/icons/dart_16_dark.svg new file mode 100644 index 0000000000..2d0e595261 --- /dev/null +++ b/resources/icons/dart_16_dark.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/io/flutter/FlutterBundle.properties b/src/io/flutter/FlutterBundle.properties index dc34b49db4..81d6f0df52 100644 --- a/src/io/flutter/FlutterBundle.properties +++ b/src/io/flutter/FlutterBundle.properties @@ -4,8 +4,12 @@ app.reload.action.text=Flutter Hot Reload app.reload.action.description=Hot reload changes into the running Flutter app +app.reload.all.action.text=Flutter Hot Reload (All Devices) +app.reload.all.action.description=Hot reload changes into all running Flutter apps on all connected devices app.restart.action.text=Flutter Hot Restart app.restart.action.description=Restart the Flutter app +app.restart.all.action.text=Flutter Hot Restart (All Devices) +app.restart.all.action.description=Restart all running Flutter apps on all connected devices app.profile.action.text=Run in Flutter profile mode app.profile.config.action.text=Flutter Run ''{0}'' in Profile Mode @@ -224,4 +228,3 @@ settings.open.inspector.on.launch=Open Flutter Inspector view on app launch settings.hot.reload.on.save=Perform hot reload on save settings.disable.tracking.widget.creation=Disable tracking widget creation locations settings.enable.bazel.test.runner=Enable new Bazel test runner -settings.enable.bazel.test.runner.mustSyncClientWarning=(Sync required) diff --git a/src/io/flutter/FlutterInitializer.java b/src/io/flutter/FlutterInitializer.java index 4391b424d9..2cdcccb1ac 100644 --- a/src/io/flutter/FlutterInitializer.java +++ b/src/io/flutter/FlutterInitializer.java @@ -30,6 +30,7 @@ import io.flutter.run.FlutterReloadManager; import io.flutter.run.FlutterRunNotifications; import io.flutter.run.daemon.DeviceService; +import io.flutter.samples.FlutterSampleManager; import io.flutter.sdk.FlutterPluginsLibraryManager; import io.flutter.settings.FlutterSettings; import io.flutter.utils.FlutterModuleUtils; @@ -123,6 +124,9 @@ public void runActivity(@NotNull Project project) { // Start the widget perf manager. FlutterWidgetPerfManager.init(project); + // Load the sample index. + FlutterSampleManager.initialize(project); + // Watch save actions for reload on save. FlutterReloadManager.init(project); @@ -158,7 +162,7 @@ public void runActivity(@NotNull Project project) { }); notification.addAction(new AnAction("Sounds good!") { @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { notification.expire(); final Analytics analytics = getAnalytics(); // We only track for flutter projects. @@ -169,7 +173,7 @@ public void actionPerformed(AnActionEvent event) { }); notification.addAction(new AnAction("No thanks") { @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { notification.expire(); setCanReportAnalytics(false); } diff --git a/src/io/flutter/FlutterUtils.java b/src/io/flutter/FlutterUtils.java index 184cad0db4..f44a5c4bf1 100644 --- a/src/io/flutter/FlutterUtils.java +++ b/src/io/flutter/FlutterUtils.java @@ -150,6 +150,10 @@ public static boolean isInTestDir(@Nullable DartFile file) { return relativePath != null && (relativePath.startsWith("test/")); } + public static boolean isIntegrationTestingMode() { + return System.getProperty("idea.required.plugins.id", "").equals("io.flutter.tests.gui.flutter-gui-tests"); + } + @Nullable public static VirtualFile getRealVirtualFile(@Nullable PsiFile psiFile) { return psiFile != null ? psiFile.getOriginalFile().getVirtualFile() : null; @@ -260,6 +264,43 @@ public static PluginId getPluginId() { return pluginId; } + /** + * Returns true if passed pubspec declares a flutter or flutter_web dependency. + *

    + * This method is provided explicitly instead of calling declaresFlutter(VirtualFile) || declaresFlutterWeb(VirtualFile) so only one read + * and parsing of the pubspec is made. + */ + public static boolean declaresFlutterAny(@NotNull final VirtualFile pubspec) { + // It uses Flutter if it contains: + // dependencies: + // flutter: + + try { + final Map yamlMap = readPubspecFileToMap(pubspec); + if (yamlMap == null) { + return false; + } + + // Special case the 'flutter' package itself - this allows us to run their unit tests from IntelliJ. + final Object name = yamlMap.get("name"); + if ("flutter".equals(name)) { + return true; + } + + // Check for a dependency on the flutter package. + final Object dependencies = yamlMap.get("dependencies"); + //noinspection SimplifiableIfStatement + if (dependencies instanceof Map) { + return ((Map)dependencies).containsKey("flutter") || ((Map)dependencies).containsKey("flutter_web"); + } + + return false; + } + catch (IOException e) { + return false; + } + } + /** * Returns true if passed pubspec declares a flutter dependency. */ @@ -269,20 +310,19 @@ public static boolean declaresFlutter(@NotNull final VirtualFile pubspec) { // flutter: try { - final String contents = new String(pubspec.contentsToByteArray(true /* cache contents */)); - final Map yaml = loadPubspecInfo(contents); - if (yaml == null) { + final Map yamlMap = readPubspecFileToMap(pubspec); + if (yamlMap == null) { return false; } // Special case the 'flutter' package itself - this allows us to run their unit tests from IntelliJ. - final Object name = yaml.get("name"); + final Object name = yamlMap.get("name"); if ("flutter".equals(name)) { return true; } // Check for a dependency on the flutter package. - final Object dependencies = yaml.get("dependencies"); + final Object dependencies = yamlMap.get("dependencies"); //noinspection SimplifiableIfStatement if (dependencies instanceof Map) { return ((Map)dependencies).containsKey("flutter"); @@ -295,6 +335,34 @@ public static boolean declaresFlutter(@NotNull final VirtualFile pubspec) { } } + /** + * Returns true if passed pubspec declares a flutter_web dependency. + */ + public static boolean declaresFlutterWeb(@NotNull final VirtualFile pubspec) { + // It uses Flutter if it contains: + // dependencies: + // flutter_web: + + try { + final Map yamlMap = readPubspecFileToMap(pubspec); + if (yamlMap == null) { + return false; + } + + // Check for a dependency on the flutter package. + final Object dependencies = yamlMap.get("dependencies"); + //noinspection SimplifiableIfStatement + if (dependencies instanceof Map) { + return ((Map)dependencies).containsKey("flutter_web"); + } + + return false; + } + catch (IOException e) { + return false; + } + } + /** * Returns true if the passed pubspec indicates that it is a Flutter plugin. */ @@ -304,13 +372,12 @@ public static boolean isFlutterPlugin(@NotNull final VirtualFile pubspec) { // plugin: try { - final String contents = new String(pubspec.contentsToByteArray(true /* cache contents */)); - final Map yaml = loadPubspecInfo(contents); - if (yaml == null) { + final Map yamlMap = readPubspecFileToMap(pubspec); + if (yamlMap == null) { return false; } - final Object flutterEntry = yaml.get("flutter"); + final Object flutterEntry = yamlMap.get("flutter"); //noinspection SimplifiableIfStatement if (flutterEntry instanceof Map) { return ((Map)flutterEntry).containsKey("plugin"); @@ -339,6 +406,11 @@ public static Project findProject(@NotNull String path) { return null; } + private static Map readPubspecFileToMap(@NotNull final VirtualFile pubspec) throws IOException { + final String contents = new String(pubspec.contentsToByteArray(true /* cache contents */)); + return loadPubspecInfo(contents); + } + private static Map loadPubspecInfo(@NotNull String yamlContents) { final Yaml yaml = new Yaml(new SafeConstructor(), new Representer(), new DumperOptions(), new Resolver() { @Override diff --git a/src/io/flutter/ObservatoryConnector.java b/src/io/flutter/ObservatoryConnector.java index d4fb9e1cea..39f5cc25a0 100644 --- a/src/io/flutter/ObservatoryConnector.java +++ b/src/io/flutter/ObservatoryConnector.java @@ -1,4 +1,4 @@ -package com.jetbrains.lang.dart.ide.runner; +package io.flutter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/io/flutter/ProjectOpenActivity.java b/src/io/flutter/ProjectOpenActivity.java index 78bc1415db..a7dd1b5835 100644 --- a/src/io/flutter/ProjectOpenActivity.java +++ b/src/io/flutter/ProjectOpenActivity.java @@ -13,6 +13,8 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectType; +import com.intellij.openapi.project.ProjectTypeService; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.ui.Messages; import icons.FlutterIcons; @@ -28,6 +30,7 @@ * @see FlutterInitializer for actions that run later. */ public class ProjectOpenActivity implements StartupActivity, DumbAware { + public static final ProjectType FLUTTER_PROJECT_TYPE = new ProjectType("io.flutter"); private static final Logger LOG = Logger.getInstance(ProjectOpenActivity.class); @Override @@ -47,6 +50,9 @@ public void runActivity(@NotNull Project project) { Notifications.Bus.notify(new PackagesOutOfDateNotification(project, pubRoot)); } } + if (!FLUTTER_PROJECT_TYPE.equals(ProjectTypeService.getProjectType(project))) { + ProjectTypeService.setProjectType(project, FLUTTER_PROJECT_TYPE); + } } private static class PackagesOutOfDateNotification extends Notification { @@ -64,7 +70,7 @@ public PackagesOutOfDateNotification(@NotNull Project project, @NotNull PubRoot addAction(new AnAction("Run 'flutter packages get'") { @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { expire(); final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); diff --git a/src/io/flutter/actions/FlutterPackagesExplorerActionGroup.java b/src/io/flutter/actions/FlutterPackagesExplorerActionGroup.java index 1df11230d3..d8f7148afa 100644 --- a/src/io/flutter/actions/FlutterPackagesExplorerActionGroup.java +++ b/src/io/flutter/actions/FlutterPackagesExplorerActionGroup.java @@ -23,7 +23,7 @@ private static boolean isFlutterPubspec(@NotNull AnActionEvent e) { } @Override - public void update(AnActionEvent e) { + public void update(@NotNull AnActionEvent e) { final boolean enabled = isFlutterPubspec(e); final Presentation presentation = e.getPresentation(); presentation.setEnabled(enabled); diff --git a/src/io/flutter/actions/OpenEmulatorAction.java b/src/io/flutter/actions/OpenEmulatorAction.java index 8f642897f7..bc6099c213 100644 --- a/src/io/flutter/actions/OpenEmulatorAction.java +++ b/src/io/flutter/actions/OpenEmulatorAction.java @@ -10,6 +10,7 @@ import com.intellij.openapi.project.Project; import io.flutter.android.AndroidEmulator; import io.flutter.android.AndroidSdk; +import org.jetbrains.annotations.NotNull; import java.util.Collections; import java.util.List; @@ -37,7 +38,7 @@ public OpenEmulatorAction(AndroidEmulator emulator) { } @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { emulator.startEmulator(); } } diff --git a/src/io/flutter/actions/ReloadAllFlutterApps.java b/src/io/flutter/actions/ReloadAllFlutterApps.java new file mode 100644 index 0000000000..2ed30ed924 --- /dev/null +++ b/src/io/flutter/actions/ReloadAllFlutterApps.java @@ -0,0 +1,45 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import icons.FlutterIcons; +import io.flutter.FlutterBundle; +import io.flutter.FlutterConstants; +import io.flutter.FlutterInitializer; +import io.flutter.run.FlutterReloadManager; +import io.flutter.run.daemon.FlutterApp; +import org.jetbrains.annotations.NotNull; + +/** + * Action that reloads all running Flutter apps. + */ +public class ReloadAllFlutterApps extends FlutterAppAction { + public static final String ID = "Flutter.ReloadAllFlutterApps"; //NON-NLS + public static final String TEXT = FlutterBundle.message("app.reload.all.action.text"); + public static final String DESCRIPTION = FlutterBundle.message("app.reload.all.action.description"); + + public ReloadAllFlutterApps(@NotNull FlutterApp app, @NotNull Computable isApplicable) { + super(app, TEXT, DESCRIPTION, FlutterIcons.HotReload, isApplicable, ID); + // Shortcut is associated with toolbar action. + copyShortcutFrom(ActionManager.getInstance().getAction("Flutter.Toolbar.ReloadAllAction")); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final Project project = getEventProject(e); + if (project == null) { + return; + } + + FlutterInitializer.sendAnalyticsAction(this); + FlutterReloadManager.getInstance(project) + .saveAllAndReloadAll(FlutterApp.allFromProjectProcess(project), FlutterConstants.RELOAD_REASON_MANUAL); + } +} diff --git a/src/io/flutter/actions/ReloadAllFlutterAppsRetarget.java b/src/io/flutter/actions/ReloadAllFlutterAppsRetarget.java new file mode 100644 index 0000000000..c47a299e05 --- /dev/null +++ b/src/io/flutter/actions/ReloadAllFlutterAppsRetarget.java @@ -0,0 +1,19 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.ActionPlaces; + +public class ReloadAllFlutterAppsRetarget extends FlutterRetargetAppAction { + public ReloadAllFlutterAppsRetarget() { + super(ReloadAllFlutterApps.ID, + ReloadAllFlutterApps.TEXT, + ReloadAllFlutterApps.DESCRIPTION, + ActionPlaces.MAIN_TOOLBAR, + ActionPlaces.NAVIGATION_BAR_TOOLBAR, + ActionPlaces.MAIN_MENU); + } +} diff --git a/src/io/flutter/actions/ReloadFlutterApp.java b/src/io/flutter/actions/ReloadFlutterApp.java index babd828602..9293742f10 100644 --- a/src/io/flutter/actions/ReloadFlutterApp.java +++ b/src/io/flutter/actions/ReloadFlutterApp.java @@ -51,4 +51,14 @@ public void actionPerformed(@NotNull AnActionEvent e) { FlutterReloadManager.getInstance(project).saveAllAndReload(getApp(), FlutterConstants.RELOAD_REASON_MANUAL); } } + + // Override to disable the hot reload action when running flutter web apps. + @Override + public void update(@NotNull AnActionEvent e) { + super.update(e); + + if (!getApp().appSupportsHotReload()) { + e.getPresentation().setEnabled(false); + } + } } diff --git a/src/io/flutter/actions/RestartAllFlutterApps.java b/src/io/flutter/actions/RestartAllFlutterApps.java new file mode 100644 index 0000000000..10a0e809bd --- /dev/null +++ b/src/io/flutter/actions/RestartAllFlutterApps.java @@ -0,0 +1,45 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import icons.FlutterIcons; +import io.flutter.FlutterBundle; +import io.flutter.FlutterConstants; +import io.flutter.FlutterInitializer; +import io.flutter.run.FlutterReloadManager; +import io.flutter.run.daemon.FlutterApp; +import org.jetbrains.annotations.NotNull; + +/** + * Action that restarts all running Flutter apps. + */ +public class RestartAllFlutterApps extends FlutterAppAction { + public static final String ID = "Flutter.RestartAllFlutterApps"; //NON-NLS + public static final String TEXT = FlutterBundle.message("app.restart.all.action.text"); + public static final String DESCRIPTION = FlutterBundle.message("app.restart.all.action.description"); + + public RestartAllFlutterApps(@NotNull FlutterApp app, @NotNull Computable isApplicable) { + super(app, TEXT, DESCRIPTION, FlutterIcons.HotRestart, isApplicable, ID); + // Shortcut is associated with toolbar action. + copyShortcutFrom(ActionManager.getInstance().getAction("Flutter.Toolbar.RestartAllAction")); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + final Project project = getEventProject(e); + if (project == null) { + return; + } + + FlutterInitializer.sendAnalyticsAction(this); + FlutterReloadManager.getInstance(project) + .saveAllAndRestartAll(FlutterApp.allFromProjectProcess(project), FlutterConstants.RELOAD_REASON_MANUAL); + } +} diff --git a/src/io/flutter/actions/RestartAllFlutterAppsRetarget.java b/src/io/flutter/actions/RestartAllFlutterAppsRetarget.java new file mode 100644 index 0000000000..3b0e1c4330 --- /dev/null +++ b/src/io/flutter/actions/RestartAllFlutterAppsRetarget.java @@ -0,0 +1,19 @@ +/* + * 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.actions; + +import com.intellij.openapi.actionSystem.ActionPlaces; + +public class RestartAllFlutterAppsRetarget extends FlutterRetargetAppAction { + public RestartAllFlutterAppsRetarget() { + super(RestartAllFlutterApps.ID, + RestartAllFlutterApps.TEXT, + RestartAllFlutterApps.DESCRIPTION, + ActionPlaces.MAIN_TOOLBAR, + ActionPlaces.NAVIGATION_BAR_TOOLBAR, + ActionPlaces.MAIN_MENU); + } +} diff --git a/src/io/flutter/bazel/PluginConfig.java b/src/io/flutter/bazel/PluginConfig.java index f0f32c25f7..c65ef78ff2 100644 --- a/src/io/flutter/bazel/PluginConfig.java +++ b/src/io/flutter/bazel/PluginConfig.java @@ -21,6 +21,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.regex.PatternSyntaxException; /** @@ -68,11 +69,14 @@ public int hashCode() { /** * Reads plugin configuration from a file, if possible. */ - public static @Nullable - PluginConfig load(@NotNull VirtualFile file) { + @Nullable + public static PluginConfig load(@NotNull VirtualFile file) { final Computable readAction = () -> { - try { - final InputStreamReader input = new InputStreamReader(file.getInputStream(), "UTF-8"); + try ( + // Create the input stream in a try-with-resources statement. This will automatically close the stream + // in an implicit finally section; this addresses a file handle leak issue we had on MacOS. + final InputStreamReader input = new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8) + ) { final Fields fields = GSON.fromJson(input, Fields.class); return new PluginConfig(fields); } diff --git a/src/io/flutter/console/FlutterConsoleFilter.java b/src/io/flutter/console/FlutterConsoleFilter.java index ffc47d8656..4b9aeedcf5 100644 --- a/src/io/flutter/console/FlutterConsoleFilter.java +++ b/src/io/flutter/console/FlutterConsoleFilter.java @@ -76,10 +76,15 @@ public FlutterConsoleFilter(@NotNull Module module) { @VisibleForTesting @Nullable public VirtualFile fileAtPath(@NotNull String pathPart) { - // "lib/main.dart:6" pathPart = pathPart.split(":")[0]; + // We require the pathPart reference to be a file reference, otherwise we'd match things like + // "Build: Running build completed, took 191ms". + if (pathPart.indexOf('.') == -1) { + return null; + } + final VirtualFile[] roots = ModuleRootManager.getInstance(module).getContentRoots(); for (VirtualFile root : roots) { if (!pathPart.isEmpty()) { @@ -189,7 +194,7 @@ public void navigate(final Project project) { return; } - final FlutterApp app = FlutterApp.fromProjectProcess(project); + final FlutterApp app = FlutterApp.firstFromProjectProcess(project); if (app == null) { Messages.showErrorDialog(project, "Running Flutter App not found", "Error"); return; diff --git a/src/io/flutter/dart/DartSyntax.java b/src/io/flutter/dart/DartSyntax.java index 6e6e127cce..1d914da21d 100644 --- a/src/io/flutter/dart/DartSyntax.java +++ b/src/io/flutter/dart/DartSyntax.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Objects; +import java.util.regex.Pattern; /** * Finds Dart PSI elements in IntelliJ's syntax tree. @@ -27,11 +28,36 @@ public class DartSyntax { */ @Nullable public static DartCallExpression findEnclosingFunctionCall(@NotNull PsiElement elt, @NotNull String functionName) { + return findEnclosingFunctionCall(elt, functionName, new Equator() { + @Override + boolean equate(@NotNull String first, @NotNull String second) { + return Objects.equals(first, second); + } + }); + } + + /** + * Finds the enclosing function call where the function being called has a name matching {@param functionRegex}. + *

    + * Returns null if not found. + */ + @Nullable + public static DartCallExpression findEnclosingFunctionCall(@NotNull PsiElement elt, @NotNull Pattern functionRegex) { + return findEnclosingFunctionCall(elt, functionRegex, new Equator() { + @Override + boolean equate(@NotNull Pattern first, @NotNull String second) { + return first.matcher(second).matches(); + } + }); + } + + private static DartCallExpression findEnclosingFunctionCall( + @NotNull PsiElement elt, @NotNull T functionDescriptor, @NotNull Equator equator) { while (elt != null) { if (elt instanceof DartCallExpression) { final DartCallExpression call = (DartCallExpression)elt; final String name = getCalledFunctionName(call); - if (name != null && name.equals(functionName)) { + if (name != null && equator.equate(functionDescriptor, name)) { return call; } } @@ -90,13 +116,23 @@ public static E getArgument(@NotNull DartCallExpressi } /** - * Check if an element is a call to a function with the given name. + * Check if an element is a call to a function with the given {@param functionName}. * - * @return true if the given element is a call to function, false otherwise + * @return true if the given element is a call to the function, false otherwise */ - public static boolean isCallToFunctionNamed(@NotNull DartCallExpression element, @NotNull String function) { + public static boolean isCallToFunctionNamed(@NotNull DartCallExpression element, @NotNull String functionName) { final String name = getCalledFunctionName(element); - return Objects.equals(name, function); + return Objects.equals(name, functionName); + } + + /** + * Check if an element is a call to a function matching the given {@param functionRegex}. + * + * @return true if the given element is a call to the function, false otherwise + */ + public static boolean isCallToFunctionMatching(@NotNull DartCallExpression element, @NotNull Pattern functionRegex) { + final String name = getCalledFunctionName(element); + return name != null && functionRegex.matcher(name).matches(); } /** @@ -136,4 +172,14 @@ private static String getCalledFunctionName(@NotNull DartCallExpression call) { if (!(call.getFirstChild() instanceof DartReference)) return null; return call.getFirstChild().getText(); } + + /** + * {@link java.util.Comparator}, but for equality checks. + * + * @param + * @param + */ + private static abstract class Equator { + abstract boolean equate(@NotNull T first, @NotNull S second); + } } diff --git a/src/io/flutter/dart/FlutterDartAnalysisServer.java b/src/io/flutter/dart/FlutterDartAnalysisServer.java index 8e0d39d0bb..63ccf975d1 100644 --- a/src/io/flutter/dart/FlutterDartAnalysisServer.java +++ b/src/io/flutter/dart/FlutterDartAnalysisServer.java @@ -7,6 +7,7 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.Uninterruptibles; +import com.google.dart.server.AnalysisServerListenerAdapter; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; @@ -15,15 +16,11 @@ import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; -import com.intellij.util.ReflectionUtil; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; -import org.dartlang.analysis.server.protocol.FlutterOutline; -import org.dartlang.analysis.server.protocol.FlutterService; -import org.dartlang.analysis.server.protocol.SourceChange; +import org.dartlang.analysis.server.protocol.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -60,6 +57,16 @@ public static FlutterDartAnalysisServer getInstance(@NotNull final Project proje private FlutterDartAnalysisServer(@NotNull Project project) { analysisService = DartPlugin.getInstance().getAnalysisService(project); analysisService.addResponseListener(FlutterDartAnalysisServer.this::processResponse); + analysisService.addAnalysisServerListener(new AnalysisServerListenerAdapter() { + @Override + public void serverConnected(String s) { + // If the server reconnected we need to let it know that we still care + // about our subscriptions. + if (!subscriptions.isEmpty()) { + sendSubscriptions(); + } + } + }); } public void addOutlineListener(@NotNull final String filePath, @NotNull final FlutterOutlineListener listener) { @@ -103,7 +110,7 @@ public List edit_getAssists(@NotNull VirtualFile file, int offset, @Nullable public SourceChange flutter_getChangeAddForDesignTimeConstructor(@NotNull VirtualFile file, int _offset) { final String filePath = FileUtil.toSystemDependentName(file.getPath()); - final int offset = getOriginalOffset(file, _offset); + final int offset = analysisService.getOriginalOffset(file, _offset); final CountDownLatch latch = new CountDownLatch(1); final AtomicReference result = new AtomicReference<>(); @@ -185,21 +192,4 @@ private boolean processNotification(JsonObject response) { } return true; } - - /** - * Must use it right before sending any offsets and lengths to the AnalysisServer. - */ - private int getOriginalOffset(@Nullable final VirtualFile file, final int convertedOffset) { - // TODO(scheglov) Remove reflection when the method is made public everywhere. - // https://github.com/JetBrains/intellij-plugins/pull/572 - final Method method = ReflectionUtil.getDeclaredMethod(analysisService.getClass(), "getOriginalOffset", VirtualFile.class, int.class); - if (method != null) { - try { - return (Integer)method.invoke(analysisService, file, convertedOffset); - } - catch (Throwable ignored) { - } - } - return convertedOffset; - } } diff --git a/src/io/flutter/devtools/DevToolsManager.java b/src/io/flutter/devtools/DevToolsManager.java new file mode 100644 index 0000000000..e58864d20d --- /dev/null +++ b/src/io/flutter/devtools/DevToolsManager.java @@ -0,0 +1,234 @@ +/* + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.devtools; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; +import com.intellij.execution.process.ProcessEvent; +import com.intellij.ide.browsers.BrowserLauncher; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Key; +import io.flutter.pub.PubRoot; +import io.flutter.pub.PubRoots; +import io.flutter.sdk.FlutterCommand; +import io.flutter.sdk.FlutterSdk; +import io.flutter.utils.JsonUtils; +import org.jetbrains.annotations.NotNull; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Manage installing and opening DevTools. + */ +public class DevToolsManager { + public static DevToolsManager getInstance(@NotNull Project project) { + return ServiceManager.getService(project, DevToolsManager.class); + } + + private final Project project; + + private boolean installedDevTools = false; + + private DevToolsInstance devToolsInstance; + + private DevToolsManager(@NotNull Project project) { + this.project = project; + } + + public boolean hasInstalledDevTools() { + return installedDevTools; + } + + public CompletableFuture installDevTools() { + final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + if (sdk == null) { + return createCompletedFuture(false); + } + + final List pubRoots = PubRoots.forProject(project); + if (pubRoots.isEmpty()) { + return createCompletedFuture(false); + } + + final CompletableFuture result = new CompletableFuture<>(); + final FlutterCommand command = sdk.flutterPackagesPub(pubRoots.get(0), "global", "activate", "devtools"); + + final ProgressManager progressManager = ProgressManager.getInstance(); + progressManager.run(new Task.Backgroundable(project, "Installing DevTools...", true) { + OSProcessHandler processHandler; + + @Override + public void run(@NotNull ProgressIndicator indicator) { + indicator.setText(getTitle()); + + processHandler = command.startInConsole(project); + + try { + final boolean value = processHandler.waitFor(); + if (value) { + installedDevTools = true; + } + result.complete(value); + } + catch (RuntimeException re) { + if (!result.isDone()) { + result.complete(false); + } + } + + processHandler = null; + } + + @Override + public void onCancel() { + if (processHandler != null && !processHandler.isProcessTerminated()) { + processHandler.destroyProcess(); + if (!result.isDone()) { + result.complete(false); + } + } + } + }); + + return result; + } + + public void openBrowser() { + openBrowserImpl(null); + } + + public void openBrowserAndConnect(String uri) { + openBrowserImpl(uri); + } + + private void openBrowserImpl(String uri) { + if (devToolsInstance != null) { + devToolsInstance.openBrowserAndConnect(uri); + return; + } + + final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + if (sdk == null) { + return; + } + + final List pubRoots = PubRoots.forProject(project); + if (pubRoots.isEmpty()) { + return; + } + + // start the server + DevToolsInstance.startServer(project, sdk, pubRoots.get(0), instance -> { + devToolsInstance = instance; + + devToolsInstance.openBrowserAndConnect(uri); + }, instance -> { + // Listen for closing, null out the devToolsInstance. + devToolsInstance = null; + }); + } + + private CompletableFuture createCompletedFuture(boolean value) { + final CompletableFuture result = new CompletableFuture<>(); + result.complete(value); + return result; + } +} + +class DevToolsInstance { + public static void startServer( + Project project, + FlutterSdk sdk, + PubRoot pubRoot, + Callback onSuccess, + Callback onClose + ) { + final FlutterCommand command = sdk.flutterPackagesPub(pubRoot, "global", "run", "devtools", "--machine", "--port=0"); + final OSProcessHandler processHandler = command.startInConsole(project); + + if (processHandler == null) { + return; + } + + processHandler.addProcessListener(new ProcessAdapter() { + @Override + public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) { + final String text = event.getText().trim(); + + if (text.startsWith("{") && text.endsWith("}")) { + // {"method":"server.started","params":{"host":"127.0.0.1","port":9100}} + + try { + final JsonParser jsonParser = new JsonParser(); + final JsonElement element = jsonParser.parse(text); + + // params.port + final JsonObject obj = element.getAsJsonObject(); + final JsonObject params = obj.getAsJsonObject("params"); + final String host = JsonUtils.getStringMember(params, "host"); + final int port = JsonUtils.getIntMember(params, "port"); + + if (port != -1) { + final DevToolsInstance instance = new DevToolsInstance(host, port); + onSuccess.call(instance); + } + else { + processHandler.destroyProcess(); + } + } + catch (JsonSyntaxException e) { + processHandler.destroyProcess(); + } + } + } + + @Override + public void processTerminated(@NotNull ProcessEvent event) { + onClose.call(null); + } + }); + } + + final String devtoolsHost; + final int devtoolsPort; + + DevToolsInstance(String devtoolsHost, int devtoolsPort) { + this.devtoolsHost = devtoolsHost; + this.devtoolsPort = devtoolsPort; + } + + public void openBrowserAndConnect(String serviceProtocolUri) { + if (serviceProtocolUri == null) { + BrowserLauncher.getInstance().browse("http://" + devtoolsHost + ":" + devtoolsPort + "/?hide=debugger&", null); + } + else { + try { + final String urlParam = URLEncoder.encode(serviceProtocolUri, "UTF-8"); + BrowserLauncher.getInstance().browse( + "http://" + devtoolsHost + ":" + devtoolsPort + "/?hide=debugger&uri=" + urlParam, + null + ); + } + catch (UnsupportedEncodingException ignored) { + } + } + } +} + +interface Callback { + void call(T value); +} diff --git a/src/io/flutter/editor/FlutterSaveActionsManager.java b/src/io/flutter/editor/FlutterSaveActionsManager.java index 1f6e757837..9aec7a2e64 100644 --- a/src/io/flutter/editor/FlutterSaveActionsManager.java +++ b/src/io/flutter/editor/FlutterSaveActionsManager.java @@ -47,7 +47,7 @@ public class FlutterSaveActionsManager { * Initialize the save actions manager for the given project. */ public static void init(@NotNull Project project) { - // Call getInstance() will init FlutterFormatManager for the given project. + // Call getInstance() will init FlutterSaveActionsManager for the given project by calling the private constructor below. getInstance(project); } diff --git a/src/io/flutter/editor/NativeEditorNotificationProvider.java b/src/io/flutter/editor/NativeEditorNotificationProvider.java index b889eff514..1ccbc9ab80 100644 --- a/src/io/flutter/editor/NativeEditorNotificationProvider.java +++ b/src/io/flutter/editor/NativeEditorNotificationProvider.java @@ -56,7 +56,7 @@ private EditorNotificationPanel createPanelForAction(VirtualFile file, VirtualFi if (actionName == null) { return null; } - NativeEditorActionsPanel panel = new NativeEditorActionsPanel(file, root, actionName); + final NativeEditorActionsPanel panel = new NativeEditorActionsPanel(file, root, actionName); return panel.isValidForFile() ? panel : null; } @@ -65,7 +65,6 @@ private static String getActionName(VirtualFile root) { return null; } - //noinspection IfCanBeSwitch if (root.getName().equals("android")) { return "flutter.androidstudio.open"; } diff --git a/src/io/flutter/inspector/DiagnosticsNode.java b/src/io/flutter/inspector/DiagnosticsNode.java index 3cd4454ba8..c202d1135c 100644 --- a/src/io/flutter/inspector/DiagnosticsNode.java +++ b/src/io/flutter/inspector/DiagnosticsNode.java @@ -9,13 +9,14 @@ import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.xdebugger.XSourcePosition; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; -import io.flutter.server.vmService.frame.DartVmServiceValue; import io.flutter.run.daemon.FlutterApp; +import io.flutter.server.vmService.frame.DartVmServiceValue; import io.flutter.utils.CustomIconMaker; import io.flutter.utils.JsonUtils; import org.apache.commons.lang.StringUtils; @@ -49,6 +50,8 @@ * also available via the getValue() method. */ public class DiagnosticsNode { + private static final Logger LOG = Logger.getInstance(DiagnosticsNode.class); + private static final CustomIconMaker iconMaker = new CustomIconMaker(); private final FlutterApp app; @@ -104,7 +107,8 @@ public boolean isDisposed() { 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) { + } + catch (Exception e) { // If the service can't be acquired then it is disposed. return false; } @@ -638,12 +642,13 @@ public CompletableFuture> getChildren() { final JsonArray jsonArray = json.get("children").getAsJsonArray(); final ArrayList nodes = new ArrayList<>(); for (JsonElement element : jsonArray) { - DiagnosticsNode child = new DiagnosticsNode(element.getAsJsonObject(), inspectorService, app,false, parent); + DiagnosticsNode child = new DiagnosticsNode(element.getAsJsonObject(), inspectorService, app, false, parent); child.setParent(this); nodes.add(child); } children = CompletableFuture.completedFuture(nodes); - } else if (hasChildren()) { + } + else if (hasChildren()) { children = inspectorService.thenComposeAsync((service) -> { if (service == null) { return null; @@ -720,12 +725,12 @@ ArrayList trackPropertiesMatchingParameters(ArrayList getPropertyDoc() { if (propertyDocFuture == null) { - propertyDocFuture = createPropertyDocFurure(); + propertyDocFuture = createPropertyDocFuture(); } return propertyDocFuture; } - private CompletableFuture createPropertyDocFurure() { + private CompletableFuture createPropertyDocFuture() { final DiagnosticsNode parent = getParent(); if (parent != null) { return inspectorService.thenComposeAsync((service) -> service.toDartVmServiceValueForSourceLocation(parent.getValueRef()) @@ -734,23 +739,26 @@ private CompletableFuture createPropertyDocFurure() { 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(); + .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"; - }); - })); + 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"); @@ -834,7 +842,8 @@ public void safeWhenComplete(CompletableFuture future, BiConsumer libraryNames; CompletableFuture libraryRef; private final Alarm myRequestsScheduler; private static final Logger LOG = Logger.getInstance(EvalOnDartLibrary.class); @@ -94,16 +95,14 @@ public CompletableFuture addRequest(InspectorService.ObjectGroup isAlive, final CompletableFuture previousDone = allPendingRequestsDone; allPendingRequestsDone = response; // Actually schedule this request only after the previous request completes. - previousDone.whenCompleteAsync((v, error) -> { - myRequestsScheduler.addRequest(wrappedRequest, 0); - }); + previousDone.whenCompleteAsync((v, error) -> myRequestsScheduler.addRequest(wrappedRequest, 0)); } } return response; } - public EvalOnDartLibrary(String libraryName, VmService vmService, VMServiceManager vmServiceManager) { - this.libraryName = libraryName; + 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); @@ -135,7 +134,8 @@ public CompletableFuture eval(String expression, Map future = new CompletableFuture<>(); libraryRef.thenAcceptAsync((LibraryRef ref) -> { vmService.evaluate( - getIsolateId(), ref.getId(), expression, scope, + getIsolateId(), ref.getId(), expression, + scope, true, new EvaluateConsumer() { @Override public void onError(RPCError error) { @@ -232,12 +232,13 @@ private void initialize(String isolateId) { @Override public void received(Isolate response) { for (LibraryRef library : response.getLibraries()) { - if (library.getUri().equals(libraryName)) { + + if (libraryNames.contains(library.getUri())) { libraryRef.complete(library); return; } } - libraryRef.completeExceptionally(new RuntimeException("Library " + libraryName + " not found.")); + libraryRef.completeExceptionally(new RuntimeException("No library matching " + libraryNames + " found.")); } @Override diff --git a/src/io/flutter/inspector/HeapState.java b/src/io/flutter/inspector/HeapState.java index a0cc44a652..c4a248c304 100644 --- a/src/io/flutter/inspector/HeapState.java +++ b/src/io/flutter/inspector/HeapState.java @@ -16,15 +16,12 @@ package io.flutter.inspector; import io.flutter.server.vmService.HeapMonitor; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import org.dartlang.vm.service.element.IsolateRef; import org.dartlang.vm.service.element.VM; +import java.text.DecimalFormat; +import java.util.*; + public class HeapState implements HeapMonitor.HeapListener { private static final DecimalFormat df = new DecimalFormat(); private static final DecimalFormat df1 = new DecimalFormat(); @@ -79,6 +76,10 @@ public String getRSSSummary() { return printMb(rssBytes) + " RSS"; } + public int getRssBytes() { + return rssBytes; + } + public String getHeapSummary() { return printMb1(samples.samples.getLast().getBytes()) + " of " + printMb1(heapMaxInBytes); } @@ -112,7 +113,7 @@ public void handleIsolatesInfo(VM vm, List isolates) rssBytes = vm.getJson().get("_currentRSS").getAsInt(); heapMaxInBytes = total; - addSample(new HeapMonitor.HeapSample(current, external,false)); + addSample(new HeapMonitor.HeapSample(current, external, false)); } @Override @@ -129,6 +130,6 @@ public void handleGCEvent(IsolateRef isolateRef, HeapMonitor.HeapSpace newHeapSp } } - addSample(new HeapMonitor.HeapSample(current, external,true)); + addSample(new HeapMonitor.HeapSample(current, external, true)); } } diff --git a/src/io/flutter/inspector/InspectorInstanceRef.java b/src/io/flutter/inspector/InspectorInstanceRef.java index c29f8df3d9..f115b5c402 100644 --- a/src/io/flutter/inspector/InspectorInstanceRef.java +++ b/src/io/flutter/inspector/InspectorInstanceRef.java @@ -23,7 +23,6 @@ public InspectorInstanceRef(String id) { @Override public boolean equals(Object other) { - //noinspection SimplifiableIfStatement if (other instanceof InspectorInstanceRef) { final InspectorInstanceRef otherRef = (InspectorInstanceRef)other; return Objects.equals(id, otherRef.id); diff --git a/src/io/flutter/inspector/InspectorService.java b/src/io/flutter/inspector/InspectorService.java index 619c2803ef..798b353561 100644 --- a/src/io/flutter/inspector/InspectorService.java +++ b/src/io/flutter/inspector/InspectorService.java @@ -11,12 +11,12 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.xdebugger.XSourcePosition; -import io.flutter.server.vmService.ServiceExtensions; -import io.flutter.server.vmService.VmServiceConsumers; -import io.flutter.server.vmService.frame.DartVmServiceValue; import io.flutter.pub.PubRoot; import io.flutter.run.FlutterDebugProcess; import io.flutter.run.daemon.FlutterApp; +import io.flutter.server.vmService.ServiceExtensions; +import io.flutter.server.vmService.VmServiceConsumers; +import io.flutter.server.vmService.frame.DartVmServiceValue; import io.flutter.utils.StreamSubscription; import io.flutter.utils.VmServiceListenerAdapter; import org.dartlang.vm.service.VmService; @@ -55,12 +55,15 @@ public static CompletableFuture createGroup( return create(app, debugProcess, vmService).thenApplyAsync((service) -> service.createObjectGroup(groupName)); } - public static CompletableFuture create(@NotNull FlutterApp app, + 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"); + inspectorLibraryNames.add("package:flutter_web/src/widgets/widget_inspector.dart"); final EvalOnDartLibrary inspectorLibrary = new EvalOnDartLibrary( - "package:flutter/src/widgets/widget_inspector.dart", + inspectorLibraryNames, vmService, app.getVMServiceManager() ); @@ -87,7 +90,7 @@ public static CompletableFuture create(@NotNull FlutterApp app private InspectorService(@NotNull FlutterApp app, @NotNull FlutterDebugProcess debugProcess, @NotNull VmService vmService, - EvalOnDartLibrary inspectorLibrary, + @NotNull EvalOnDartLibrary inspectorLibrary, @NotNull Set supportedServiceMethods) { this.vmService = vmService; this.app = app; @@ -134,7 +137,7 @@ public void connectionClosed() { /** * Returns whether to use the Daemon API or the VM Service protocol directly. - * + *

    * The VM Service protocol must be used when paused at a breakpoint as the * Daemon API calls won't execute until after the current frame is done * rendering. @@ -168,6 +171,7 @@ public ObjectGroup createObjectGroup(String debugName) { return new ObjectGroup(debugName); } + @NotNull private EvalOnDartLibrary getInspectorLibrary() { return inspectorLibrary; } @@ -239,7 +243,7 @@ private void onVmServiceReceived(String streamId, Event event) { public CompletableFuture isWidgetTreeReady() { if (useDaemonApi()) { return invokeServiceMethodDaemonNoGroup("isWidgetTreeReady", new HashMap<>()) - .thenApplyAsync((JsonElement element) -> element.getAsBoolean() == true); + .thenApplyAsync((JsonElement element) -> element.getAsBoolean()); } else { return invokeServiceMethodObservatoryNoGroup("isWidgetTreeReady") @@ -255,6 +259,81 @@ CompletableFuture invokeServiceMethodDaemonNoGroup(String methodNam return invokeServiceMethodDaemonNoGroup(methodName, params); } + /* + * We make a best guess for the pub directory based on the the root directory + * of the first non artifical widget in the tree. + */ + public CompletableFuture inferPubRootDirectoryIfNeeded() { + ObjectGroup group = createObjectGroup("temp"); + + return group.getRoot(FlutterTreeType.widget).thenComposeAsync((DiagnosticsNode root) -> { + if (root == null) { + // No need to do anything as there isn't a valid tree (yet?). + group.dispose(); + return CompletableFuture.completedFuture(null); + } + + return root.getChildren().thenComposeAsync((ArrayList children) -> { + if (children != null && !children.isEmpty()) { + // There are already widgets identified as being from the summary tree so + // no need to guess the pub root directory. + return CompletableFuture.completedFuture(null); + } + + return group.getChildren(root.getDartDiagnosticRef(), false, null).thenComposeAsync((ArrayList allChildren) -> { + + if (allChildren == null || allChildren.isEmpty()) { + return null; + } + + InspectorSourceLocation location = allChildren.get(0).getCreationLocation(); + if (location == null) { + return CompletableFuture.completedFuture(null); + } + String path = location.getPath(); + if (path == null) { + group.dispose(); + return CompletableFuture.completedFuture(null); + } + // TODO(jacobr): it would be nice to use Isolate.rootLib. + // We are currently blocked by the --track-widget-creation transformer + // generating absolute paths instead of package:paths. + // Once https://github.com/flutter/flutter/issues/26615 is fixed we will be + // able to use package: paths. Temporarily all tools tracking widget + // locations will need to support both path formats. + // TODO(jacobr): use the list of loaded scripts to determine the appropriate + // package root directory given that the root script of this project is in + // this directory rather than guessing based on url structure. + ArrayList parts = new ArrayList<>(Arrays.asList(path.split("/"))); + String pubRootDirectory = null; + + for (int i = parts.size() - 1; i >= 0; i--) { + String part = parts.get(i); + if (part.equals("lib") || part.equals("web")) { + pubRootDirectory = String.join("/", parts.subList(0, i)); + break; + } + + if (part.equals("packages")) { + pubRootDirectory = String.join("/", parts.subList(0, i + 1)); + break; + } + } + if (pubRootDirectory == null) { + parts.remove(parts.size() - 1); + pubRootDirectory = String.join("/", parts); + } + + final String finalPubRootDirectory = pubRootDirectory; + return setPubRootDirectories(new ArrayList<>(Collections.singletonList(pubRootDirectory))).thenApplyAsync((ignored) -> { + group.dispose(); + return finalPubRootDirectory; + }); + }); + }); + }); + } + private CompletableFuture setPubRootDirectories(List rootDirectories) { if (useDaemonApi()) { return invokeServiceMethodDaemonNoGroup("setPubRootDirectories", rootDirectories).thenApplyAsync((ignored) -> null); @@ -632,15 +711,20 @@ public CompletableFuture toDartVmServiceValueForSourceLocati })); } - CompletableFuture> parseDiagnosticsNodesObservatory(CompletableFuture instanceRefFuture, DiagnosticsNode parent) { - return nullIfDisposed(() -> instanceRefFuture.thenComposeAsync((instanceRef) -> parseDiagnosticsNodesObservatory(instanceRef, parent))); + CompletableFuture> parseDiagnosticsNodesObservatory(CompletableFuture instanceRefFuture, + DiagnosticsNode parent) { + return nullIfDisposed( + () -> instanceRefFuture.thenComposeAsync((instanceRef) -> parseDiagnosticsNodesObservatory(instanceRef, parent))); } - CompletableFuture> parseDiagnosticsNodesDaemon(CompletableFuture jsonFuture, DiagnosticsNode parent) { + CompletableFuture> parseDiagnosticsNodesDaemon(CompletableFuture jsonFuture, + DiagnosticsNode parent) { return nullIfDisposed(() -> jsonFuture.thenApplyAsync((json) -> parseDiagnosticsNodesHelper(json, parent))); } - CompletableFuture> getChildren(InspectorInstanceRef instanceRef, boolean summaryTree, DiagnosticsNode parent) { + CompletableFuture> getChildren(InspectorInstanceRef instanceRef, + boolean summaryTree, + DiagnosticsNode parent) { if (isDetailsSummaryViewSupported()) { return getListHelper(instanceRef, summaryTree ? "getChildrenSummaryTree" : "getChildrenDetailsSubtree", parent); } diff --git a/src/io/flutter/inspector/InspectorTree.java b/src/io/flutter/inspector/InspectorTree.java index 854a6c83ea..7742700d9a 100644 --- a/src/io/flutter/inspector/InspectorTree.java +++ b/src/io/flutter/inspector/InspectorTree.java @@ -16,6 +16,7 @@ import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl; import io.flutter.view.InspectorTreeUI; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.plaf.TreeUI; @@ -93,10 +94,6 @@ public InspectorTree(final DefaultMutableTreeNode treemodel, super(treemodel); setUI(new InspectorTreeUI()); final BasicTreeUI ui = (BasicTreeUI)getUI(); - // TODO: Consider making changing the background a user preference, in order to do user testing. - if (!detailsSubtree && !legacyMode) { - setBackground(VERY_LIGHT_GREY); - } this.detailsSubtree = detailsSubtree; setRootVisible(rootVisible); @@ -122,7 +119,7 @@ public void dispose() { @Nullable @Override - public Object getData(String dataId) { + public Object getData(@NotNull String dataId) { if (InspectorTree.INSPECTOR_KEY.is(dataId)) { return this; } diff --git a/src/io/flutter/inspector/WidgetPerfTipsPanel.java b/src/io/flutter/inspector/WidgetPerfTipsPanel.java index 403f3f684b..7f560dd327 100644 --- a/src/io/flutter/inspector/WidgetPerfTipsPanel.java +++ b/src/io/flutter/inspector/WidgetPerfTipsPanel.java @@ -25,8 +25,8 @@ import io.flutter.utils.AsyncUtils; import org.jetbrains.annotations.NotNull; -import javax.swing.*; import javax.swing.Timer; +import javax.swing.*; import java.awt.event.ActionEvent; import java.util.*; @@ -146,13 +146,6 @@ private void updateTip() { } } - final ArrayList changedEditors = new ArrayList<>(); - for (TextEditor editor : newTipsForFile.keySet()) { - final List entry = newTipsForFile.get(editor); - if (tipsPerFile == null || !PerfTipRule.equivalentPerfTips(entry, tipsPerFile.get(editor))) { - changedEditors.add(editor); - } - } tipsPerFile = newTipsForFile; if (!PerfTipRule.equivalentPerfTips(currentTips, tips)) { showPerfTips(tips); diff --git a/src/io/flutter/logging/FlutterLogTree.java b/src/io/flutter/logging/FlutterLogTree.java index 8aa4b6f3f6..ce8307501e 100644 --- a/src/io/flutter/logging/FlutterLogTree.java +++ b/src/io/flutter/logging/FlutterLogTree.java @@ -22,10 +22,11 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.Timer; import javax.swing.*; import javax.swing.table.*; import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreePath; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; @@ -177,8 +178,11 @@ interface UpdateCallback { private final FlutterLog log; @NotNull private final Alarm uiThreadAlarm; + @NotNull + final Timer updateTimer; + boolean autoScrollToEnd; - // Cached for hide and restore (because *sigh* Swing). + // Cached for hide and restore. private List tableColumns; private JScrollPane scrollPane; private TreeTable treeTable; @@ -207,6 +211,7 @@ private TreeModel(@NotNull ColumnModel columns, @NotNull Disposable parent, @Not setShowSequenceNumbers(false); uiThreadAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, parent); + updateTimer = new Timer(100, e -> uiExec(this::update, 0)); } public void setUpdateCallback(UpdateCallback updateCallback) { @@ -226,34 +231,40 @@ void scrollToEnd() { } void uiExec(@NotNull Runnable runnable, int delayMillis) { - uiThreadAlarm.addRequest(runnable, delayMillis); + if (!uiThreadAlarm.isDisposed()) { + uiThreadAlarm.addRequest(runnable, delayMillis); + } } - void update(@Nullable int[] selectedRows) { + void update() { columns.update(); reload(getRoot()); treeTable.updateUI(); - // Preserve / restore selection state if unspecified. - if (selectedRows == null) { - selectedRows = treeTable.getSelectedRows(); + final TreeTableTree tree = treeTable.getTree(); + + // Restore/preserve selection state. + tree.setSelectionRows(treeTable.getSelectedRows()); + + // Select and reveal a selection path (e.g., on error) if specified. + if (pathToSelectAndReveal != null) { + // TODO(pq): if there are timing issues, consider a delayed uiExec + // TODO(pq): consider scrolling bounding rectangle to top of scrollpane + tree.setSelectionPath(pathToSelectAndReveal); + tree.scrollPathToVisible(pathToSelectAndReveal); + pathToSelectAndReveal = null; } - treeTable.getTree().setSelectionRows(selectedRows); if (updateCallback != null) { updateCallback.updated(); } if (autoScrollToEnd) { - uiThreadAlarm.addRequest(this::scrollToEnd, 100); + uiExec(this::scrollToEnd, 100); } } - void update() { - update(null); - } - @Override public LogRootTreeNode getRoot() { return (LogRootTreeNode)super.getRoot(); @@ -276,17 +287,26 @@ public void clearEntries() { update(); } + // Cached, for example, by errors. + TreePath pathToSelectAndReveal; + public void appendNode(FlutterLogEntry entry, boolean selectNode) { if (treeTable == null || uiThreadAlarm.isDisposed()) { return; } - uiThreadAlarm.addRequest(() -> { - final MutableTreeNode root = getRoot(); - final int nodeIndex = root.getChildCount(); - insertNodeInto(new FlutterEventNode(entry), root, nodeIndex); - update(selectNode ? new int[]{nodeIndex} : null); - }, 10); + final FlutterEventNode node = new FlutterEventNode(entry); + getRoot().add(node); + + if (selectNode) { + pathToSelectAndReveal = new TreePath(node.getPath()); + } + + if (updateTimer.isRunning()) { + return; + } + + updateTimer.restart(); } public boolean shouldShowTimestamps() { @@ -426,9 +446,12 @@ List getSelectedNodes() { final List nodes = new ArrayList<>(); for (int row : getSelectedRows()) { final int realRow = convertRowIndexToModel(row); - final Object pathComponent = getTree().getPathForRow(realRow).getLastPathComponent(); - if (pathComponent instanceof FlutterLogTree.FlutterEventNode) { - nodes.add((FlutterEventNode)pathComponent); + final TreePath pathForRow = getTree().getPathForRow(realRow); + if (pathForRow != null) { + final Object pathComponent = pathForRow.getLastPathComponent(); + if (pathComponent instanceof FlutterLogTree.FlutterEventNode) { + nodes.add((FlutterEventNode)pathComponent); + } } } return nodes; diff --git a/src/io/flutter/logging/FlutterLogView.java b/src/io/flutter/logging/FlutterLogView.java index b562ce8308..6a56889e56 100644 --- a/src/io/flutter/logging/FlutterLogView.java +++ b/src/io/flutter/logging/FlutterLogView.java @@ -131,7 +131,7 @@ class ConfigureAction extends AnAction implements RightAlignedToolbarAction { private final DefaultActionGroup actionGroup; public ConfigureAction() { - super("Configure", null, AllIcons.General.Gear /* to be removed in IDEA 2020: migrate to: GearPlain */); + super("Configure", null, AllIcons.General.GearPlain); actionGroup = createPopupActionGroup(); } @@ -517,7 +517,7 @@ else if (isScrollToEnd) { private final Gson gsonHelper = new GsonBuilder().setPrettyPrinting().create(); boolean isPinned; - // Auto-scroll defautls to on. + // Auto-scroll defaults to on. boolean prePinAutoScroll = true; public FlutterLogView(@NotNull FlutterApp app) { @@ -687,7 +687,7 @@ private void computeTextAttributesByLogLevelCache() { fontType = fontType | textStyle; } - @SuppressWarnings("MagicConstant") final SimpleTextAttributes textAttributes = new SimpleTextAttributes( + final SimpleTextAttributes textAttributes = new SimpleTextAttributes( attributes.getBackgroundColor(), attributes.getForegroundColor(), effectColor, @@ -750,7 +750,7 @@ public void onEvent(@NotNull FlutterLogEntry entry) { final boolean isError = entry.getKind() == FlutterLogEntry.Kind.FLUTTER_ERROR; logTree.append(entry, isError && !isPinned); - if (isError & !isPinned) { + if (isError && !isPinned) { prePinAutoScroll = logModel.autoScrollToEnd; scrollToEndAction.disableIfNeeded(); isPinned = true; @@ -759,6 +759,7 @@ public void onEvent(@NotNull FlutterLogEntry entry) { @Override public void onEntryContentChange() { + // Called when truncated text values are returned. logModel.uiExec(logModel::update, 10); } diff --git a/src/io/flutter/logging/text/LineParser.java b/src/io/flutter/logging/text/LineParser.java index e30ee11ed2..fff3c495aa 100644 --- a/src/io/flutter/logging/text/LineParser.java +++ b/src/io/flutter/logging/text/LineParser.java @@ -227,7 +227,6 @@ else if (style instanceof Integer) { } } - //noinspection MagicConstant return new SimpleTextAttributes(fontStyle, color); } diff --git a/src/io/flutter/logging/tree/MessageCellRenderer.java b/src/io/flutter/logging/tree/MessageCellRenderer.java index e76bbd174d..c45f46065f 100644 --- a/src/io/flutter/logging/tree/MessageCellRenderer.java +++ b/src/io/flutter/logging/tree/MessageCellRenderer.java @@ -20,7 +20,7 @@ public class MessageCellRenderer extends AbstractEntryCellRender { // TODO(pq): use app for link resolution. - @NotNull + @SuppressWarnings("FieldCanBeLocal") @NotNull private final FlutterApp app; public MessageCellRenderer(@NotNull FlutterApp app, @NotNull FlutterLogTree.EntryModel entryModel) { diff --git a/src/io/flutter/module/FlutterModuleBuilder.java b/src/io/flutter/module/FlutterModuleBuilder.java index f424960f31..c29841caf4 100644 --- a/src/io/flutter/module/FlutterModuleBuilder.java +++ b/src/io/flutter/module/FlutterModuleBuilder.java @@ -51,9 +51,7 @@ public class FlutterModuleBuilder extends ModuleBuilder { private static final Logger LOG = Logger.getInstance(FlutterModuleBuilder.class); private FlutterModuleWizardStep myStep; - @NotNull - private final FlutterCreateAdditionalSettingsFields mySettingsFields = - new FlutterCreateAdditionalSettingsFields(new FlutterCreateAdditionalSettings()); + private FlutterCreateAdditionalSettingsFields mySettingsFields; @Override public String getName() { @@ -258,6 +256,7 @@ public ModuleWizardStep modifyProjectTypeStep(@NotNull SettingsStep settingsStep @Override public ModuleWizardStep getCustomOptionsStep(final WizardContext context, final Disposable parentDisposable) { myStep = new FlutterModuleWizardStep(context); + mySettingsFields = new FlutterCreateAdditionalSettingsFields(new FlutterCreateAdditionalSettings(), myStep.getFlutterSdk()); Disposer.register(parentDisposable, myStep); return myStep; } diff --git a/src/io/flutter/module/settings/FlutterCreateAdditionalSettingsFields.java b/src/io/flutter/module/settings/FlutterCreateAdditionalSettingsFields.java index e04d36e302..8e1f72f6b5 100644 --- a/src/io/flutter/module/settings/FlutterCreateAdditionalSettingsFields.java +++ b/src/io/flutter/module/settings/FlutterCreateAdditionalSettingsFields.java @@ -12,6 +12,7 @@ import io.flutter.FlutterBundle; import io.flutter.module.FlutterProjectType; import io.flutter.sdk.FlutterCreateAdditionalSettings; +import io.flutter.sdk.FlutterSdk; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -26,15 +27,14 @@ public class FlutterCreateAdditionalSettingsFields { private final RadiosForm iosLanguageRadios; private final ProjectType projectTypeForm; private final FlutterCreateParams createParams; - public FlutterCreateAdditionalSettingsFields() { - this(new FlutterCreateAdditionalSettings()); + this(new FlutterCreateAdditionalSettings(), null); } - public FlutterCreateAdditionalSettingsFields(FlutterCreateAdditionalSettings additionalSettings) { + public FlutterCreateAdditionalSettingsFields(FlutterCreateAdditionalSettings additionalSettings, FlutterSdk sdk) { settings = additionalSettings; - projectTypeForm = new ProjectType(); + projectTypeForm = new ProjectType(sdk); projectTypeForm.addListener(e -> { settings.setType(projectTypeForm.getType()); settings.setSampleContent(projectTypeForm.getSample()); diff --git a/src/io/flutter/module/settings/ProjectType.java b/src/io/flutter/module/settings/ProjectType.java index 367aa6c6c4..e8bad4f06f 100644 --- a/src/io/flutter/module/settings/ProjectType.java +++ b/src/io/flutter/module/settings/ProjectType.java @@ -11,13 +11,15 @@ import io.flutter.FlutterBundle; import io.flutter.module.FlutterProjectType; import io.flutter.samples.FlutterSample; -import io.flutter.samples.FlutterSampleManager; +import io.flutter.sdk.FlutterSdk; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.List; @@ -65,11 +67,16 @@ public void setSelectedItem(FlutterProjectType item) { private static final class FlutterSampleComboBoxModel extends AbstractListModel implements ComboBoxModel { - private final List myList = FlutterSampleManager.getSamples(); + private final List myList; private FlutterSample mySelected; public FlutterSampleComboBoxModel() { - mySelected = myList.get(0); + this(null); + } + + public FlutterSampleComboBoxModel(@Nullable FlutterSdk sdk) { + myList = sdk == null ? Collections.emptyList() : sdk.getSamples(); + mySelected = myList.isEmpty() ? null : myList.get(0); } @Override @@ -79,7 +86,7 @@ public int getSize() { @Override public FlutterSample getElementAt(int index) { - return myList.get(index); + return myList.isEmpty() ? null : myList.get(index); } @Override @@ -101,20 +108,28 @@ public void setSelectedItem(FlutterSample item) { private class FlutterSampleCellRenderer extends ColoredListCellRenderer { @Override protected void customizeCellRenderer(@NotNull JList list, - FlutterSample sample, + @Nullable FlutterSample sample, int index, boolean selected, boolean hasFocus) { - final SimpleTextAttributes style = snippetSelectorCombo.isEnabled() ? SimpleTextAttributes.REGULAR_ATTRIBUTES : SimpleTextAttributes.GRAY_ATTRIBUTES; - append(sample.getDisplayLabel(), style); + final SimpleTextAttributes style = + snippetSelectorCombo.isEnabled() ? SimpleTextAttributes.REGULAR_ATTRIBUTES : SimpleTextAttributes.GRAY_ATTRIBUTES; + append(sample == null ? "" : sample.getDisplayLabel(), style); } } + @Nullable + private final FlutterSdk sdk; + private JPanel projectTypePanel; private ComboBox projectTypeCombo; private ComboBox snippetSelectorCombo; private JCheckBox generateSampleContentCheckBox; + public ProjectType(@Nullable FlutterSdk sdk) { + this.sdk = sdk; + } + private void createUIComponents() { projectTypeCombo = new ComboBox<>(); //noinspection unchecked @@ -130,7 +145,7 @@ private void createUIComponents() { }); snippetSelectorCombo = new ComboBox<>(); - snippetSelectorCombo.setModel(new FlutterSampleComboBoxModel()); + snippetSelectorCombo.setModel(new FlutterSampleComboBoxModel(sdk)); snippetSelectorCombo.setRenderer(new FlutterSampleCellRenderer()); snippetSelectorCombo.setToolTipText(FlutterBundle.message("flutter.module.create.settings.sample.tip")); snippetSelectorCombo.setEnabled(false); @@ -138,7 +153,6 @@ private void createUIComponents() { generateSampleContentCheckBox = new JCheckBox(); generateSampleContentCheckBox.setText(FlutterBundle.message("flutter.module.create.settings.sample.text")); generateSampleContentCheckBox.addItemListener(e -> snippetSelectorCombo.setEnabled(e.getStateChange() == ItemEvent.SELECTED)); - } @NotNull @@ -151,7 +165,8 @@ public FlutterProjectType getType() { } public FlutterSample getSample() { - return generateSampleContentCheckBox.isVisible() && generateSampleContentCheckBox.isSelected() ? (FlutterSample)snippetSelectorCombo.getSelectedItem() : null; + return generateSampleContentCheckBox.isVisible() && generateSampleContentCheckBox.isSelected() ? (FlutterSample)snippetSelectorCombo + .getSelectedItem() : null; } public ComboBox getProjectTypeCombo() { diff --git a/src/io/flutter/perf/DocumentFileLocationMapper.java b/src/io/flutter/perf/DocumentFileLocationMapper.java index a22a1008db..513d40c27e 100644 --- a/src/io/flutter/perf/DocumentFileLocationMapper.java +++ b/src/io/flutter/perf/DocumentFileLocationMapper.java @@ -18,6 +18,8 @@ import com.intellij.testFramework.LightVirtualFile; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XSourcePosition; +import com.jetbrains.lang.dart.psi.DartId; +import com.jetbrains.lang.dart.psi.DartReferenceExpression; import io.flutter.inspector.InspectorService; public class DocumentFileLocationMapper implements FileLocationMapper { @@ -47,7 +49,8 @@ public static Document lookupDocument(String path) { psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document); virtualFile = psiFile != null ? psiFile.getVirtualFile() : null; debuggerUtil = XDebuggerUtil.getInstance(); - } else { + } + else { psiFile = null; virtualFile = null; debuggerUtil = null; @@ -73,10 +76,20 @@ public TextRange getIdentifierRange(int line, int column) { return null; } final int offset = pos.getOffset(); - final PsiElement element = psiFile.getOriginalFile().findElementAt(offset); + PsiElement element = psiFile.getOriginalFile().findElementAt(offset); if (element == null) { return null; } + + // Handle named constructors gracefully. + // For example, for the constructor + // Image.asset(...) we want to return "Image.asset" instead of "asset". + if (element.getParent() instanceof DartId) { + element = element.getParent(); + } + while (element.getParent() instanceof DartReferenceExpression) { + element = element.getParent(); + } return element.getTextRange(); } diff --git a/src/io/flutter/perf/EditorPerfDecorations.java b/src/io/flutter/perf/EditorPerfDecorations.java index 755ef0985a..3e0438192c 100644 --- a/src/io/flutter/perf/EditorPerfDecorations.java +++ b/src/io/flutter/perf/EditorPerfDecorations.java @@ -49,7 +49,7 @@ class EditorPerfDecorations implements EditorMouseListener, EditorPerfModel { /** * Experimental option to animate highlighted widget names. - * + *

    * Disabled by default as animating contents of the TextEditor results in * higher than desired memory usage. */ @@ -342,7 +342,7 @@ RangeHighlighter getHighlighter() { public AnAction getClickAction() { return new AnAction() { @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent event) { if (isActive()) { final ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(getApp().getProject()); diff --git a/src/io/flutter/perf/FlutterWidgetPerf.java b/src/io/flutter/perf/FlutterWidgetPerf.java index 019ba1eec5..b87d40d268 100644 --- a/src/io/flutter/perf/FlutterWidgetPerf.java +++ b/src/io/flutter/perf/FlutterWidgetPerf.java @@ -63,7 +63,7 @@ class StatsForReportKind { private boolean requestInProgress = false; private long lastRequestTime; - private final List perfListeners = new ArrayList<>(); + private final Set perfListeners = new HashSet<>(); private final Map editorDecorations = new HashMap<>(); private final TIntObjectHashMap knownLocationIds = new TIntObjectHashMap<>(); @@ -71,7 +71,7 @@ class StatsForReportKind { private final Map stats = new HashMap<>(); final Set currentEditors = new HashSet<>(); - private boolean profilingEnabled = false; + private boolean profilingEnabled; final Timer uiAnimationTimer; private final WidgetPerfProvider perfProvider; private boolean isDisposed = false; @@ -246,6 +246,11 @@ public void addPerfListener(PerfModel listener) { perfListeners.add(listener); } + @Override + public void removePerfListener(PerfModel listener) { + perfListeners.remove(listener); + } + private StatsForReportKind getStatsForKind(PerfReportKind kind) { StatsForReportKind report = stats.get(kind); if (report == null) { diff --git a/src/io/flutter/perf/FlutterWidgetPerfManager.java b/src/io/flutter/perf/FlutterWidgetPerfManager.java index 7373f34004..c405cadd9e 100644 --- a/src/io/flutter/perf/FlutterWidgetPerfManager.java +++ b/src/io/flutter/perf/FlutterWidgetPerfManager.java @@ -62,6 +62,8 @@ public FlutterWidgetPerf getCurrentStats() { private boolean trackRepaintWidgets = trackRepaintWidgetsDefault; private boolean debugIsActive; + private final Set listeners = new HashSet<>(); + /** * File editors visible to the user that might contain widgets. */ @@ -197,15 +199,19 @@ private void debugActive(Project project, FlutterViewMessages.FlutterDebugEvent (TextEditor textEditor) -> new EditorPerfDecorations(textEditor, app), path -> new DocumentFileLocationMapper(path, app.getProject()) ); + + for (PerfModel listener : listeners) { + currentStats.addPerfListener(listener); + } } public void stateChanged(FlutterApp.State newState) { switch (newState) { case RELOADING: - currentStats.clear(); + if (currentStats != null) currentStats.clear(); break; case RESTARTING: - currentStats.onRestart(); + if (currentStats != null) currentStats.onRestart(); break; case STARTED: notifyPerf(); @@ -318,6 +324,21 @@ public void dispose() { if (currentStats != null) { currentStats.dispose(); currentStats = null; + listeners.clear(); + } + } + + public void addPerfListener(PerfModel listener) { + listeners.add(listener); + if (currentStats != null) { + currentStats.addPerfListener(listener); + } + } + + public void removePerfListener(PerfModel listener) { + listeners.remove(listener); + if (currentStats != null) { + currentStats.removePerfListener(listener); } } } diff --git a/src/io/flutter/perf/WidgetPerfListener.java b/src/io/flutter/perf/WidgetPerfListener.java index 2a5a92ec11..ca3754d9cd 100644 --- a/src/io/flutter/perf/WidgetPerfListener.java +++ b/src/io/flutter/perf/WidgetPerfListener.java @@ -18,4 +18,5 @@ public interface WidgetPerfListener { void onNavigation(); void addPerfListener(PerfModel listener); + void removePerfListener(PerfModel listener); } diff --git a/src/io/flutter/preview/PreviewArea.java b/src/io/flutter/preview/PreviewArea.java index 6a109dffe1..b79ae5a272 100644 --- a/src/io/flutter/preview/PreviewArea.java +++ b/src/io/flutter/preview/PreviewArea.java @@ -363,7 +363,7 @@ class TitleAction extends AnAction implements CustomComponentAction { } @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { } @Override diff --git a/src/io/flutter/preview/PreviewView.java b/src/io/flutter/preview/PreviewView.java index a267871f9c..d18c9501c6 100644 --- a/src/io/flutter/preview/PreviewView.java +++ b/src/io/flutter/preview/PreviewView.java @@ -74,8 +74,8 @@ import java.beans.PropertyChangeListener; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.*; import java.util.List; +import java.util.*; import java.util.concurrent.TimeUnit; @com.intellij.openapi.components.State( @@ -342,14 +342,14 @@ public void initToolWindow(@NotNull ToolWindow toolWindow) { if (toolWindow instanceof ToolWindowEx) { final AnAction sendFeedbackAction = new AnAction("Send Feedback", "Send Feedback", FlutterIcons.Feedback) { @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { BrowserUtil.browse(FEEDBACK_URL); } }; final AnAction separator = new AnAction(AllIcons.General.Divider) { @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { } }; @@ -1013,7 +1013,7 @@ private class QuickAssistAction extends AnAction { } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { sendAnalyticEvent(id); final SourceChange change; synchronized (actionToChangeMap) { @@ -1044,7 +1044,7 @@ private class ExtractMethodAction extends AnAction { } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { final AnAction action = ActionManager.getInstance().getAction("ExtractMethod"); if (action != null) { final FlutterOutline outline = getWidgetOutline(); @@ -1097,7 +1097,7 @@ private class ExtractWidgetAction extends AnAction { } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { final AnAction action = ActionManager.getInstance().getAction("Flutter.ExtractWidget"); if (action != null) { TransactionGuard.submitTransaction(project, () -> { @@ -1138,7 +1138,7 @@ private class ShowOnlyWidgetsAction extends AnAction implements Toggleable, Righ } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { final FlutterSettings flutterSettings = FlutterSettings.getInstance(); flutterSettings.setShowOnlyWidgets(!flutterSettings.isShowOnlyWidgets()); if (currentOutline != null) { @@ -1168,7 +1168,7 @@ class OutlineComponent extends SimpleToolWindowPanel { } @Override - public Object getData(String dataId) { + public Object getData(@NotNull String dataId) { if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { return myView.currentFileEditor; } @@ -1393,7 +1393,7 @@ public TextOnlyActionWrapper(AnAction action) { } @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { action.actionPerformed(event); } } diff --git a/src/io/flutter/run/FlutterDebugProcess.java b/src/io/flutter/run/FlutterDebugProcess.java index 5bcf67ccc2..8cef84746e 100644 --- a/src/io/flutter/run/FlutterDebugProcess.java +++ b/src/io/flutter/run/FlutterDebugProcess.java @@ -18,13 +18,14 @@ import com.intellij.xdebugger.XDebugSession; import com.jetbrains.lang.dart.util.DartUrlResolver; import io.flutter.FlutterUtils; +import io.flutter.actions.ReloadAllFlutterApps; import io.flutter.actions.ReloadFlutterApp; +import io.flutter.actions.RestartAllFlutterApps; import io.flutter.actions.RestartFlutterApp; import io.flutter.run.daemon.FlutterApp; import io.flutter.run.daemon.RunMode; import io.flutter.server.vmService.DartVmServiceDebugProcess; import io.flutter.view.FlutterViewMessages; -import io.flutter.view.OpenFlutterViewAction; import io.flutter.view.ToolbarComboBoxAction; import org.dartlang.vm.service.VmService; import org.jetbrains.annotations.NotNull; @@ -104,7 +105,7 @@ public void registerAdditionalActions(@NotNull final DefaultActionGroup leftTool // Add actions common to the run and debug windows. final Computable isSessionActive = () -> app.isStarted() && getVmConnected() && !getSession().isStopped(); final Computable canReload = () -> app.getLaunchMode().supportsReload() && isSessionActive.compute() && !app.isReloading(); - final Computable observatoryAvailable = () -> isSessionActive.compute() && app.getConnector().getBrowserUrl() != null; + final Computable debugUrlAvailable = () -> isSessionActive.compute() && app.getConnector().getBrowserUrl() != null; if (app.getMode() == RunMode.DEBUG) { topToolbar.addSeparator(); @@ -115,9 +116,12 @@ public void registerAdditionalActions(@NotNull final DefaultActionGroup leftTool topToolbar.addAction(new ReloadFlutterApp(app, canReload)); topToolbar.addAction(new RestartFlutterApp(app, canReload)); topToolbar.addSeparator(); - topToolbar.addAction(new OpenFlutterViewAction(isSessionActive)); - topToolbar.addAction(new OverflowAction(app, observatoryAvailable)); + topToolbar.addAction(new OpenDevToolsAction(app.getConnector(), debugUrlAvailable)); + topToolbar.addSeparator(); + topToolbar.addAction(new OverflowAction(app, debugUrlAvailable)); + settings.addAction(new ReloadAllFlutterApps(app, canReload)); + settings.addAction(new RestartAllFlutterApps(app, canReload)); // Don't call super since we have our own observatory action. } diff --git a/src/io/flutter/run/FlutterReloadManager.java b/src/io/flutter/run/FlutterReloadManager.java index 0229faa02a..cb330329d1 100644 --- a/src/io/flutter/run/FlutterReloadManager.java +++ b/src/io/flutter/run/FlutterReloadManager.java @@ -29,7 +29,6 @@ import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.Balloon; -import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowId; @@ -49,7 +48,6 @@ import io.flutter.actions.ReloadFlutterApp; import io.flutter.pub.PubRoot; import io.flutter.pub.PubRoots; -import io.flutter.run.daemon.DeviceService; import io.flutter.run.daemon.FlutterApp; import io.flutter.run.daemon.FlutterDevice; import io.flutter.run.daemon.RunMode; @@ -73,10 +71,6 @@ * Handle the mechanics of performing a hot reload on file save. */ public class FlutterReloadManager { - private static final String RESTART_SUGGESTED_TEXT = - "Not all changed program elements ran during view reassembly; consider restarting (" - + (SystemInfo.isMac ? "⇧⌘S" : "⇧^S") + ")."; - private static final Logger LOG = Logger.getInstance(FlutterReloadManager.class); private static final Map toolWindowNotificationGroups = new HashMap<>(); @@ -176,7 +170,7 @@ private void handleSaveAllNotification(@Nullable Editor editor) { return; } - if (!app.getLaunchMode().supportsReload()) { + if (!app.getLaunchMode().supportsReload() || !app.appSupportsHotReload()) { return; } @@ -196,14 +190,6 @@ private void handleSaveAllNotification(@Nullable Editor editor) { return; } - if (hasErrors(app.getProject(), app.getModule(), editor.getDocument())) { - handlingSave.set(false); - - showAnalysisNotification("Reload not performed", "Analysis issues found", true); - - return; - } - final Notification notification = showRunNotification(app, null, "Reloading…", false); final long startTime = System.currentTimeMillis(); @@ -212,10 +198,6 @@ private void handleSaveAllNotification(@Nullable Editor editor) { notification.expire(); showRunNotification(app, "Hot Reload Error", result.getMessage(), true); } - else if (result.isRestartRecommended()) { - notification.expire(); - showRunNotification(app, "Reloading…", RESTART_SUGGESTED_TEXT, false); - } else { // Make sure the reloading message is displayed for at least 2 seconds (so it doesn't just flash by). final long delay = Math.max(0, 2000 - (System.currentTimeMillis() - startTime)); @@ -233,17 +215,12 @@ else if (result.isRestartRecommended()) { }, reloadDelayMs, TimeUnit.MILLISECONDS); } - public void saveAllAndReload(@NotNull FlutterApp app, String reason) { + private void reloadApp(@NotNull FlutterApp app, @NotNull String reason) { if (app.isStarted()) { - FileDocumentManager.getInstance().saveAllDocuments(); - app.performHotReload(true, reason).thenAccept(result -> { if (!result.ok()) { showRunNotification(app, "Hot Reload", result.getMessage(), true); } - else if (result.isRestartRecommended()) { - showRunNotification(app, "Reloading…", RESTART_SUGGESTED_TEXT, false); - } }).exceptionally(throwable -> { showRunNotification(app, "Hot Reload", throwable.getMessage(), true); return null; @@ -251,9 +228,21 @@ else if (result.isRestartRecommended()) { } } - public void saveAllAndRestart(@NotNull FlutterApp app, String reason) { + public void saveAllAndReload(@NotNull FlutterApp app, @NotNull String reason) { + FileDocumentManager.getInstance().saveAllDocuments(); + reloadApp(app, reason); + } + + public void saveAllAndReloadAll(@NotNull List appsToReload, @NotNull String reason) { + FileDocumentManager.getInstance().saveAllDocuments(); + + for (FlutterApp app : appsToReload) { + reloadApp(app, reason); + } + } + + private void restartApp(@NotNull FlutterApp app, @NotNull String reason) { if (app.isStarted()) { - FileDocumentManager.getInstance().saveAllDocuments(); app.performRestartApp(reason).thenAccept(result -> { if (!result.ok()) { showRunNotification(app, "Hot Restart", result.getMessage(), true); @@ -263,13 +252,26 @@ public void saveAllAndRestart(@NotNull FlutterApp app, String reason) { return null; }); - final FlutterDevice device = DeviceService.getInstance(myProject).getSelectedDevice(); + final FlutterDevice device = app.device(); if (device != null) { device.bringToFront(); } } } + public void saveAllAndRestart(@NotNull FlutterApp app, @NotNull String reason) { + FileDocumentManager.getInstance().saveAllDocuments(); + restartApp(app, reason); + } + + public void saveAllAndRestartAll(@NotNull List appsToRestart, @NotNull String reason) { + FileDocumentManager.getInstance().saveAllDocuments(); + + for (FlutterApp app : appsToRestart) { + restartApp(app, reason); + } + } + @Nullable private FlutterApp getApp(AnAction reloadAction) { if (reloadAction instanceof FlutterAppAction) { @@ -337,11 +339,6 @@ private void removeRunNotifications(FlutterApp app) { } } - private FlutterApp getApp() { - final AnAction action = ProjectActions.getAction(myProject, ReloadFlutterApp.ID); - return action instanceof FlutterAppAction ? ((FlutterAppAction)action).getApp() : null; - } - private boolean hasErrors(@NotNull Project project, @Nullable Module module, @NotNull Document document) { final DartAnalysisServerService analysisServerService = DartAnalysisServerService.getInstance(project); final GlobalSearchScope scope = module == null ? new ProjectAndLibrariesScope(project) : module.getModuleContentScope(); diff --git a/src/io/flutter/run/FlutterRunConfigurationProducer.java b/src/io/flutter/run/FlutterRunConfigurationProducer.java index 33407e050d..d4c6c4eced 100644 --- a/src/io/flutter/run/FlutterRunConfigurationProducer.java +++ b/src/io/flutter/run/FlutterRunConfigurationProducer.java @@ -48,7 +48,7 @@ protected boolean setupConfigurationFromContext(final @NotNull SdkRunConfig conf final VirtualFile main = getFlutterEntryFile(context, true, true); if (main == null) return false; - config.setFields(new SdkFields(main, context.getProject())); + config.setFields(new SdkFields(main)); config.setGeneratedName(); final PsiElement elt = sourceElement.get(); diff --git a/src/io/flutter/run/FlutterRunNotifications.java b/src/io/flutter/run/FlutterRunNotifications.java index b4eae8b01f..cc254140f0 100644 --- a/src/io/flutter/run/FlutterRunNotifications.java +++ b/src/io/flutter/run/FlutterRunNotifications.java @@ -59,7 +59,7 @@ private void checkForDisplayFirstReload() { notification.setIcon(FlutterIcons.HotReload); notification.addAction(new AnAction("Learn more") { @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { BrowserUtil.browse(FlutterBundle.message("flutter.reload.firstRun.url")); notification.expire(); } diff --git a/src/io/flutter/run/LaunchState.java b/src/io/flutter/run/LaunchState.java index 8e3e203856..7756b74b3a 100644 --- a/src/io/flutter/run/LaunchState.java +++ b/src/io/flutter/run/LaunchState.java @@ -45,9 +45,9 @@ import io.flutter.dart.DartPlugin; import io.flutter.logging.FlutterLog; import io.flutter.logging.FlutterLogView; +import io.flutter.pub.PubRoot; import io.flutter.run.bazel.BazelRunConfig; import io.flutter.run.daemon.*; -import io.flutter.view.OpenFlutterViewAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -119,13 +119,31 @@ protected RunContentDescriptor launch(@NotNull ExecutionEnvironment env) throws } final Project project = getEnvironment().getProject(); - final FlutterDevice device = DeviceService.getInstance(project).getSelectedDevice(); + @Nullable FlutterDevice device = DeviceService.getInstance(project).getSelectedDevice(); + + boolean isFlutterWeb = false; + final String filePath = ((SdkRunConfig)runConfig).getFields().getFilePath(); + if (filePath != null) { + final MainFile main = MainFile.verify(filePath, project).get(); + final PubRoot root = PubRoot.forDirectory(main.getAppDir()); + if (root != null) { + isFlutterWeb = FlutterUtils.declaresFlutterWeb(root.getPubspec()); + } + } - if (device == null) { + // Flutter web does not yet support devices. + if (isFlutterWeb) { + device = null; + } + else if (device == null) { showNoDeviceConnectedMessage(project); return null; } + final FlutterApp app = myCreateAppCallback.createApp(device); + if (isFlutterWeb) { + app.setIsFlutterWeb(true); + } // Cache for use in console configuration. FlutterApp.addToEnvironment(env, app); @@ -135,10 +153,8 @@ protected RunContentDescriptor launch(@NotNull ExecutionEnvironment env) throws final ExecutionResult result = setUpConsoleAndActions(app); - // For Bazel run configurations, - // where the console is not null, - // and we find the expected process handler type, - // print the command line command to the console. + // For Bazel run configurations, where the console is not null, and we find the expected + // process handler type, print the command line command to the console. if (runConfig instanceof BazelRunConfig && app.getConsole() != null && app.getProcessHandler() instanceof OSProcessHandler) { @@ -148,7 +164,9 @@ protected RunContentDescriptor launch(@NotNull ExecutionEnvironment env) throws } } - device.bringToFront(); + if (device != null) { + device.bringToFront(); + } // Check for and display any analysis errors when we launch an app. if (env.getRunProfile() instanceof SdkRunConfig) { @@ -251,10 +269,10 @@ protected ExecutionResult setUpConsoleAndActions(@NotNull FlutterApp app) throws final List actions = new ArrayList<>(Arrays.asList( super.createActions(console, app.getProcessHandler(), getEnvironment().getExecutor()))); actions.add(new Separator()); + actions.add(new OpenDevToolsAction(app.getConnector(), observatoryAvailable)); + actions.add(new Separator()); actions.add(new OpenObservatoryAction(app.getConnector(), observatoryAvailable)); actions.add(new OpenTimelineViewAction(app.getConnector(), observatoryAvailable)); - actions.add(new Separator()); - actions.add(new OpenFlutterViewAction(() -> !app.getProcessHandler().isProcessTerminated())); return new DefaultExecutionResult(console, app.getProcessHandler(), actions.toArray(new AnAction[0])); } @@ -380,31 +398,35 @@ protected RunContentDescriptor doExecute(@NotNull RunProfileState state, @NotNul final FlutterApp app = FlutterApp.fromProcess(process); final String selectedDeviceId = getSelectedDeviceId(env.getProject()); - if (app != null && StringUtil.equals(app.deviceId(), selectedDeviceId)) { - if (executorId.equals(app.getMode().mode())) { - if (!identicalCommands(app.getCommand(), launchState.runConfig.getCommand(env, app.device()))) { - // To be safe, relaunch as the arguments to launch have changed. - try { - // TODO(jacobr): ideally we shouldn't be synchronously waiting for futures like this - // but I don't see a better option. In practice this seems fine. - app.shutdownAsync().get(); + if (app != null) { + final boolean sameDevice = app.getIsFlutterWeb() || StringUtil.equals(app.deviceId(), selectedDeviceId); + + if (sameDevice) { + if (executorId.equals(app.getMode().mode())) { + if (!identicalCommands(app.getCommand(), launchState.runConfig.getCommand(env, app.device()))) { + // To be safe, relaunch as the arguments to launch have changed. + try { + // TODO(jacobr): ideally we shouldn't be synchronously waiting for futures like this + // but I don't see a better option. In practice this seems fine. + app.shutdownAsync().get(); + } + catch (InterruptedException | java.util.concurrent.ExecutionException e) { + FlutterUtils.warn(LOG, e); + } + return launchState.launch(env); } - catch (InterruptedException | java.util.concurrent.ExecutionException e) { - FlutterUtils.warn(LOG, e); + + final FlutterLaunchMode launchMode = FlutterLaunchMode.fromEnv(env); + if (launchMode.supportsReload() && app.isStarted()) { + // Map a re-run action to a flutter hot restart. + FileDocumentManager.getInstance().saveAllDocuments(); + FlutterInitializer.sendAnalyticsAction(RestartFlutterApp.class.getSimpleName()); + app.performRestartApp(FlutterConstants.RELOAD_REASON_SAVE); } - return launchState.launch(env); } - final FlutterLaunchMode launchMode = FlutterLaunchMode.fromEnv(env); - if (launchMode.supportsReload() && app.isStarted()) { - // Map a re-run action to a flutter hot restart. - FileDocumentManager.getInstance().saveAllDocuments(); - FlutterInitializer.sendAnalyticsAction(RestartFlutterApp.class.getSimpleName()); - app.performRestartApp(FlutterConstants.RELOAD_REASON_SAVE); - } + return null; } - - return null; } } diff --git a/src/io/flutter/run/OpenDevToolsAction.java b/src/io/flutter/run/OpenDevToolsAction.java new file mode 100644 index 0000000000..d65f545bd5 --- /dev/null +++ b/src/io/flutter/run/OpenDevToolsAction.java @@ -0,0 +1,80 @@ +/* + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.util.Computable; +import icons.FlutterIcons; +import io.flutter.FlutterInitializer; +import io.flutter.ObservatoryConnector; +import io.flutter.devtools.DevToolsManager; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.concurrent.CompletableFuture; + +public class OpenDevToolsAction extends DumbAwareAction { + private final @Nullable ObservatoryConnector myConnector; + private final Computable myIsApplicable; + + public OpenDevToolsAction() { + myConnector = null; + myIsApplicable = null; + } + + public OpenDevToolsAction(@NotNull final ObservatoryConnector connector, @NotNull final Computable isApplicable) { + super("Open DevTools", "Open Dart DevTools", FlutterIcons.Dart_16); + + myConnector = connector; + myIsApplicable = isApplicable; + } + + @Override + public void update(@NotNull final AnActionEvent e) { + if (myIsApplicable == null) { + e.getPresentation().setEnabled(true); + } + else { + e.getPresentation().setEnabled(myIsApplicable.compute()); + } + } + + @Override + public void actionPerformed(@NotNull final AnActionEvent event) { + FlutterInitializer.sendAnalyticsAction(this); + + if (event.getProject() == null) { + return; + } + + final DevToolsManager devToolsManager = DevToolsManager.getInstance(event.getProject()); + + if (myConnector == null) { + if (devToolsManager.hasInstalledDevTools()) { + devToolsManager.openBrowser(); + } + else { + final CompletableFuture result = devToolsManager.installDevTools(); + result.thenAccept(o -> devToolsManager.openBrowser()); + } + } + else { + final String urlString = myConnector.getBrowserUrl(); + if (urlString == null) { + return; + } + + if (devToolsManager.hasInstalledDevTools()) { + devToolsManager.openBrowserAndConnect(urlString); + } + else { + final CompletableFuture result = devToolsManager.installDevTools(); + result.thenAccept(o -> devToolsManager.openBrowserAndConnect(urlString)); + } + } + } +} diff --git a/src/io/flutter/run/OpenObservatoryAction.java b/src/io/flutter/run/OpenObservatoryAction.java index 2ce815bb1d..1aff62d723 100644 --- a/src/io/flutter/run/OpenObservatoryAction.java +++ b/src/io/flutter/run/OpenObservatoryAction.java @@ -9,10 +9,10 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.util.Computable; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import icons.FlutterIcons; import io.flutter.FlutterBundle; import io.flutter.FlutterInitializer; +import io.flutter.ObservatoryConnector; import org.jetbrains.annotations.NotNull; public class OpenObservatoryAction extends DumbAwareAction { diff --git a/src/io/flutter/run/OpenTimelineViewAction.java b/src/io/flutter/run/OpenTimelineViewAction.java index 5742855e12..403c44cf0d 100644 --- a/src/io/flutter/run/OpenTimelineViewAction.java +++ b/src/io/flutter/run/OpenTimelineViewAction.java @@ -9,9 +9,9 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.util.Computable; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import icons.FlutterIcons; import io.flutter.FlutterInitializer; +import io.flutter.ObservatoryConnector; import org.jetbrains.annotations.NotNull; public class OpenTimelineViewAction extends DumbAwareAction { diff --git a/src/io/flutter/run/SdkAttachConfig.java b/src/io/flutter/run/SdkAttachConfig.java index 55516e57c5..4cbdd63c8f 100644 --- a/src/io/flutter/run/SdkAttachConfig.java +++ b/src/io/flutter/run/SdkAttachConfig.java @@ -20,7 +20,6 @@ import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.RunConfiguration; import com.intellij.execution.configurations.RuntimeConfigurationError; -import com.intellij.execution.filters.TextConsoleBuilder; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; @@ -30,11 +29,9 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.refactoring.listeners.RefactoringElementListener; -import com.jetbrains.lang.dart.ide.runner.DartConsoleFilter; import com.jetbrains.lang.dart.sdk.DartConfigurable; import com.jetbrains.lang.dart.sdk.DartSdk; import io.flutter.FlutterBundle; -import io.flutter.console.FlutterConsoleFilter; import io.flutter.dart.DartPlugin; import io.flutter.pub.PubRoot; import io.flutter.run.daemon.FlutterApp; @@ -74,22 +71,22 @@ public LaunchState getState(@NotNull Executor executor, @NotNull ExecutionEnviro throw new ExecutionException(e); } - SdkFields launchFields = getFields(); - MainFile mainFile = MainFile.verify(launchFields.getFilePath(), env.getProject()).get(); - Project project = env.getProject(); - RunMode mode = RunMode.fromEnv(env); - Module module = ModuleUtilCore.findModuleForFile(mainFile.getFile(), env.getProject()); - LaunchState.CreateAppCallback createAppCallback = (device) -> { + final SdkFields launchFields = getFields(); + final MainFile mainFile = MainFile.verify(launchFields.getFilePath(), env.getProject()).get(); + final Project project = env.getProject(); + final RunMode mode = RunMode.fromEnv(env); + final Module module = ModuleUtilCore.findModuleForFile(mainFile.getFile(), env.getProject()); + final LaunchState.CreateAppCallback createAppCallback = (@Nullable FlutterDevice device) -> { if (device == null) return null; - GeneralCommandLine command = getCommand(env, device); + final GeneralCommandLine command = getCommand(env, device); - FlutterApp app = FlutterApp.start(env, project, module, mode, device, command, + final FlutterApp app = FlutterApp.start(env, project, module, mode, device, command, StringUtil.capitalize(mode.mode()) + "App", - "StopApp"); + "StopApp"); // Stop the app if the Flutter SDK changes. - FlutterSdkManager.Listener sdkListener = new FlutterSdkManager.Listener() { + final FlutterSdkManager.Listener sdkListener = new FlutterSdkManager.Listener() { @Override public void flutterSdkRemoved() { app.shutdownAsync(); @@ -101,16 +98,8 @@ public void flutterSdkRemoved() { return app; }; - LaunchState launcher = new AttachState(env, mainFile.getAppDir(), mainFile.getFile(), this, createAppCallback); - - // Set up additional console filters. - TextConsoleBuilder builder = launcher.getConsoleBuilder(); - builder.addFilter(new DartConsoleFilter(env.getProject(), mainFile.getFile())); - - if (module != null) { - builder.addFilter(new FlutterConsoleFilter(module)); - } - + final LaunchState launcher = new AttachState(env, mainFile.getAppDir(), mainFile.getFile(), this, createAppCallback); + addConsoleFilters(launcher, env, mainFile, module); return launcher; } @@ -121,12 +110,12 @@ public GeneralCommandLine getCommand(@NotNull ExecutionEnvironment env, FlutterD } private void checkRunnable(@NotNull Project project) throws RuntimeConfigurationError { - DartSdk sdk = DartPlugin.getDartSdk(project); + final DartSdk sdk = DartPlugin.getDartSdk(project); if (sdk == null) { throw new RuntimeConfigurationError(FlutterBundle.message("dart.sdk.is.not.configured"), () -> DartConfigurable.openDartSettings(project)); } - MainFile.Result main = MainFile.verify(getFields().getFilePath(), project); + final MainFile.Result main = MainFile.verify(getFields().getFilePath(), project); if (!main.canLaunch()) { throw new RuntimeConfigurationError(main.getError()); } diff --git a/src/io/flutter/run/SdkFields.java b/src/io/flutter/run/SdkFields.java index ec4d58ef2a..4fb4322bc9 100644 --- a/src/io/flutter/run/SdkFields.java +++ b/src/io/flutter/run/SdkFields.java @@ -14,6 +14,7 @@ import com.jetbrains.lang.dart.sdk.DartConfigurable; import com.jetbrains.lang.dart.sdk.DartSdk; import io.flutter.FlutterBundle; +import io.flutter.FlutterUtils; import io.flutter.dart.DartPlugin; import io.flutter.pub.PubRoot; import io.flutter.run.daemon.FlutterDevice; @@ -37,7 +38,7 @@ public SdkFields() { /** * Creates SDK fields from a Dart file containing a main method. */ - public SdkFields(VirtualFile launchFile, Project project) { + public SdkFields(VirtualFile launchFile) { filePath = launchFile.getPath(); } @@ -124,11 +125,13 @@ public GeneralCommandLine createFlutterSdkRunCommand(@NotNull Project project, throw new ExecutionException("Entrypoint isn't within a Flutter pub root"); } - String[] args = additionalArgs == null ? new String[]{} : additionalArgs.split(" "); + String[] args = additionalArgs == null ? new String[]{ } : additionalArgs.split(" "); if (buildFlavor != null) { args = ArrayUtil.append(args, "--flavor=" + buildFlavor); } - final FlutterCommand command = flutterSdk.flutterRun(root, main.getFile(), device, runMode, flutterLaunchMode, project, args); + final FlutterCommand command = FlutterUtils.declaresFlutterWeb(root.getPubspec()) ? + flutterSdk.flutterRunWeb(root, runMode) : + flutterSdk.flutterRun(root, main.getFile(), device, runMode, flutterLaunchMode, project, args); return command.createGeneralCommandLine(project); } @@ -150,7 +153,7 @@ public GeneralCommandLine createFlutterSdkAttachCommand(@NotNull Project project throw new ExecutionException("Entrypoint isn't within a Flutter pub root"); } - String[] args = additionalArgs == null ? new String[]{} : additionalArgs.split(" "); + String[] args = additionalArgs == null ? new String[]{ } : additionalArgs.split(" "); if (buildFlavor != null) { args = ArrayUtil.append(args, "--flavor=" + buildFlavor); } diff --git a/src/io/flutter/run/SdkRunConfig.java b/src/io/flutter/run/SdkRunConfig.java index 18459e870d..a25cb851ca 100644 --- a/src/io/flutter/run/SdkRunConfig.java +++ b/src/io/flutter/run/SdkRunConfig.java @@ -12,11 +12,12 @@ import com.intellij.execution.Executor; import com.intellij.execution.configurations.*; import com.intellij.execution.filters.TextConsoleBuilder; +import com.intellij.execution.filters.UrlFilter; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.runners.RunConfigurationWithSuppressedDefaultRunAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.options.SettingsEditor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; @@ -91,13 +92,11 @@ public static class RecursiveDeleter private final PathMatcher matcher; RecursiveDeleter(String pattern) { - matcher = FileSystems.getDefault() - .getPathMatcher("glob:" + pattern); + matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern); } @Override - public FileVisitResult visitFile(Path file, - BasicFileAttributes attrs) { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { final Path name = file.getFileName(); if (name != null && matcher.matches(name)) { try { @@ -112,14 +111,12 @@ public FileVisitResult visitFile(Path file, } @Override - public FileVisitResult preVisitDirectory(Path dir, - BasicFileAttributes attrs) { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { return CONTINUE; } @Override - public FileVisitResult visitFileFailed(Path file, - IOException exc) { + public FileVisitResult visitFileFailed(Path file, IOException exc) { FlutterUtils.warn(LOG, exc); return CONTINUE; } @@ -139,58 +136,58 @@ public LaunchState getState(@NotNull Executor executor, @NotNull ExecutionEnviro final MainFile mainFile = MainFile.verify(launchFields.getFilePath(), env.getProject()).get(); final Project project = env.getProject(); final RunMode mode = RunMode.fromEnv(env); - final Module module = ModuleUtil.findModuleForFile(mainFile.getFile(), env.getProject()); - final LaunchState.CreateAppCallback createAppCallback = (device) -> { - if (device == null) return null; + final Module module = ModuleUtilCore.findModuleForFile(mainFile.getFile(), env.getProject()); + final LaunchState.CreateAppCallback createAppCallback = (@Nullable FlutterDevice device) -> { + // Up until the support for Flutter Web, 'device' was checked for null and returned. The device + // can only be null if this is a Flutter Web execution; this expecation is checked elsewhere. final GeneralCommandLine command = getCommand(env, device); - { - // Workaround for https://github.com/flutter/flutter/issues/16766 - // TODO(jacobr): remove once flutter tool incremental building works - // properly with --track-widget-creation. - final Path buildPath = command.getWorkDirectory().toPath().resolve("build"); - final Path cachedParametersPath = buildPath.resolve("last_build_run.json"); - final String[] parametersToTrack = {"--preview-dart-2", "--track-widget-creation"}; - final JsonArray jsonArray = new JsonArray(); - for (String parameter : command.getParametersList().getList()) { - for (String allowedParameter : parametersToTrack) { - if (parameter.startsWith(allowedParameter)) { - jsonArray.add(new JsonPrimitive(parameter)); - break; - } + + // Workaround for https://github.com/flutter/flutter/issues/16766 + // TODO(jacobr): remove once flutter tool incremental building works + // properly with --track-widget-creation. + final Path buildPath = command.getWorkDirectory().toPath().resolve("build"); + final Path cachedParametersPath = buildPath.resolve("last_build_run.json"); + final String[] parametersToTrack = {"--preview-dart-2", "--track-widget-creation"}; + final JsonArray jsonArray = new JsonArray(); + for (String parameter : command.getParametersList().getList()) { + for (String allowedParameter : parametersToTrack) { + if (parameter.startsWith(allowedParameter)) { + jsonArray.add(new JsonPrimitive(parameter)); + break; } } - final String json = new Gson().toJson(jsonArray); - String existingJson = null; - if (Files.exists(cachedParametersPath)) { - try { - existingJson = new String(Files.readAllBytes(cachedParametersPath), StandardCharsets.UTF_8); - } - catch (IOException e) { - FlutterUtils.warn(LOG, "Unable to get existing json from " + cachedParametersPath); - } + } + final String json = new Gson().toJson(jsonArray); + String existingJson = null; + if (Files.exists(cachedParametersPath)) { + try { + existingJson = new String(Files.readAllBytes(cachedParametersPath), StandardCharsets.UTF_8); } - if (!StringUtil.equals(json, existingJson)) { - // We don't have proof the current run is consistent with the existing run. - // Be safe and delete cached files that could cause problems due to - // https://github.com/flutter/flutter/issues/16766 - // We could just delete app.dill and snapshot_blob.bin.d.fingerprint - // but it is safer to just delete everything as we won't be broken by future changes - // to the underlying Flutter build rules. - try { - if (Files.exists(buildPath)) { - if (Files.isDirectory(buildPath)) { - Files.walkFileTree(buildPath, new RecursiveDeleter("*.{fingerprint,dill}")); - } - } - else { - Files.createDirectory(buildPath); + catch (IOException e) { + FlutterUtils.warn(LOG, "Unable to get existing json from " + cachedParametersPath); + } + } + if (!StringUtil.equals(json, existingJson)) { + // We don't have proof the current run is consistent with the existing run. + // Be safe and delete cached files that could cause problems due to + // https://github.com/flutter/flutter/issues/16766 + // We could just delete app.dill and snapshot_blob.bin.d.fingerprint + // but it is safer to just delete everything as we won't be broken by future changes + // to the underlying Flutter build rules. + try { + if (Files.exists(buildPath)) { + if (Files.isDirectory(buildPath)) { + Files.walkFileTree(buildPath, new RecursiveDeleter("*.{fingerprint,dill}")); } - Files.write(cachedParametersPath, json.getBytes(StandardCharsets.UTF_8)); } - catch (IOException e) { - FlutterUtils.warn(LOG, e); + else { + Files.createDirectory(buildPath); } + Files.write(cachedParametersPath, json.getBytes(StandardCharsets.UTF_8)); + } + catch (IOException e) { + FlutterUtils.warn(LOG, e); } } @@ -212,7 +209,14 @@ public void flutterSdkRemoved() { }; final LaunchState launcher = new LaunchState(env, mainFile.getAppDir(), mainFile.getFile(), this, createAppCallback); + addConsoleFilters(launcher, env, mainFile, module); + return launcher; + } + protected void addConsoleFilters(@NotNull LaunchState launcher, + @NotNull ExecutionEnvironment env, + @NotNull MainFile mainFile, + @Nullable Module module) { // Set up additional console filters. final TextConsoleBuilder builder = launcher.getConsoleBuilder(); builder.addFilter(new DartConsoleFilter(env.getProject(), mainFile.getFile())); @@ -220,8 +224,7 @@ public void flutterSdkRemoved() { if (module != null) { builder.addFilter(new FlutterConsoleFilter(module)); } - - return launcher; + builder.addFilter(new UrlFilter()); } @NotNull @@ -233,12 +236,14 @@ public GeneralCommandLine getCommand(@NotNull ExecutionEnvironment env, @Nullabl return fields.createFlutterSdkRunCommand(project, mode, FlutterLaunchMode.fromEnv(env), device); } + @Override @Nullable public String suggestedName() { final String filePath = fields.getFilePath(); return filePath == null ? null : PathUtil.getFileName(filePath); } + @Override public SdkRunConfig clone() { final SdkRunConfig clone = (SdkRunConfig)super.clone(); clone.fields = fields.copy(); diff --git a/src/io/flutter/run/bazel/BazelFields.java b/src/io/flutter/run/bazel/BazelFields.java index f7f614b864..de5d07a410 100644 --- a/src/io/flutter/run/bazel/BazelFields.java +++ b/src/io/flutter/run/bazel/BazelFields.java @@ -57,7 +57,7 @@ public class BazelFields { *

* *

- * If the user overrides --define flutter_build_mode in {@link bazelArgs}, then this field will be ignored. + * If the user overrides --define flutter_build_mode in {@link #bazelArgs}, then this field will be ignored. */ private final boolean enableReleaseMode; diff --git a/src/io/flutter/run/bazel/BazelRunConfig.java b/src/io/flutter/run/bazel/BazelRunConfig.java index 9f5868bd5b..799c4f7c24 100644 --- a/src/io/flutter/run/bazel/BazelRunConfig.java +++ b/src/io/flutter/run/bazel/BazelRunConfig.java @@ -25,6 +25,7 @@ import io.flutter.run.daemon.RunMode; import org.jdom.Element; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class BazelRunConfig extends RunConfigurationBase implements RunConfigurationWithSuppressedDefaultRunAction, LaunchState.RunConfig { @@ -71,7 +72,7 @@ public LaunchState getState(@NotNull Executor executor, @NotNull ExecutionEnviro final RunMode mode = RunMode.fromEnv(env); final Module module = ModuleUtil.findModuleForFile(workspaceRoot, env.getProject()); - final LaunchState.CreateAppCallback createAppCallback = (device) -> { + final LaunchState.CreateAppCallback createAppCallback = (@Nullable FlutterDevice device) -> { if (device == null) return null; final GeneralCommandLine command = getCommand(env, device); diff --git a/src/io/flutter/run/bazelTest/BazelTestConfig.java b/src/io/flutter/run/bazelTest/BazelTestConfig.java index 87cf7aee2f..382cc90a1a 100644 --- a/src/io/flutter/run/bazelTest/BazelTestConfig.java +++ b/src/io/flutter/run/bazelTest/BazelTestConfig.java @@ -24,7 +24,7 @@ public class BazelTestConfig extends LocatableConfigurationBase { BazelTestConfig(@NotNull final Project project, @NotNull final ConfigurationFactory factory, @NotNull final String name) { super(project, factory, name); - fields = new BazelTestFields(null, null, null); + fields = new BazelTestFields(null, null, null, null); } @NotNull diff --git a/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java b/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java index 189bb15369..c20c22d442 100644 --- a/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java +++ b/src/io/flutter/run/bazelTest/BazelTestDebugProcess.java @@ -9,9 +9,10 @@ import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.xdebugger.XDebugSession; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.util.DartUrlResolver; +import io.flutter.ObservatoryConnector; import io.flutter.run.FlutterPopFrameAction; +import io.flutter.run.OpenDevToolsAction; import io.flutter.run.OpenObservatoryAction; import io.flutter.server.vmService.DartVmServiceDebugProcess; import org.jetbrains.annotations.NotNull; @@ -39,6 +40,7 @@ public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar, @NotNull DefaultActionGroup settings) { topToolbar.addSeparator(); topToolbar.addAction(new FlutterPopFrameAction()); + topToolbar.addAction(new OpenDevToolsAction(connector, this::isActive)); topToolbar.addAction(new OpenObservatoryAction(connector, this::isActive)); } diff --git a/src/io/flutter/run/bazelTest/BazelTestFields.java b/src/io/flutter/run/bazelTest/BazelTestFields.java index bc245ec179..59fc59d8c0 100644 --- a/src/io/flutter/run/bazelTest/BazelTestFields.java +++ b/src/io/flutter/run/bazelTest/BazelTestFields.java @@ -7,6 +7,7 @@ import com.google.common.annotations.VisibleForTesting; import com.intellij.execution.ExecutionException; +import com.intellij.execution.configurations.CommandLineTokenizer; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.configurations.RuntimeConfigurationError; import com.intellij.execution.process.OSProcessHandler; @@ -39,54 +40,34 @@ * The fields in a Bazel test run configuration. */ public class BazelTestFields { - @Nullable private final String testName; @Nullable private final String entryFile; @Nullable private final String bazelTarget; + @Nullable private final String additionalArgs; - BazelTestFields(@Nullable String testName, @Nullable String entryFile, @Nullable String bazelTarget) { + BazelTestFields(@Nullable String testName, @Nullable String entryFile, @Nullable String bazelTarget, @Nullable String additionalArgs) { if (testName != null && entryFile == null) { throw new IllegalArgumentException("testName must be specified with an entryFile"); } this.testName = testName; this.entryFile = entryFile; this.bazelTarget = bazelTarget; + this.additionalArgs = additionalArgs; } /** * Copy constructor */ + @SuppressWarnings("CopyConstructorMissesField") BazelTestFields(@NotNull BazelTestFields template) { - this(template.testName, template.entryFile, template.bazelTarget); - } - - /** - * Returns whether the new test bazel runner is enabled, and if it's available. - */ - private boolean useNewBazelTestRunner(@NotNull Project project) { - if (useNewBazelTestRunnerOverride != null) { - return useNewBazelTestRunnerOverride; - } - // Check that the new test runner is available. - final Workspace workspace = getWorkspace(project); - final FlutterSettings settings = FlutterSettings.getInstance(); - return settings != null && settings.useNewBazelTestRunner(project); + this(template.testName, template.entryFile, template.bazelTarget, template.additionalArgs); } - // The value to use for the bazel test runner setting if no FlutterSettings are available. - // TODO(DaveShuckerow): set up a FlutterSettings implementation that we can use in tests. - // In the meanwhile, we'll assume that if settings is null, this code is running in a test. - // In the tests, we want to cover the new behavior by default, and provide coverage of the old - // behavior in cases where the new test script is not available. - @VisibleForTesting - Boolean useNewBazelTestRunnerOverride = null; - private String getTestScriptFromWorkspace(@NotNull Project project) { final Workspace workspace = getWorkspace(project); String testScript = workspace.getTestScript(); // Fall back on the regular launch script if the test script is not available. - // Also fall back on the regular launch script if the user has opted out of the new bazel test script. - if (testScript == null || !useNewBazelTestRunner(project)) { + if (testScript == null) { testScript = workspace.getLaunchScript(); } if (testScript != null) { @@ -100,21 +81,21 @@ private String getTestScriptFromWorkspace(@NotNull Project project) { */ @NotNull public static BazelTestFields forTestName(@NotNull String testName, @NotNull String path) { - return new BazelTestFields(testName, path, null); + return new BazelTestFields(testName, path, null, null); } /** * Creates settings for running all the tests in a Dart file. */ public static BazelTestFields forFile(@NotNull String path) { - return new BazelTestFields(null, path, null); + return new BazelTestFields(null, path, null, null); } /** * Creates settings for running all the tests in a Bazel target */ public static BazelTestFields forTarget(@NotNull String target) { - return new BazelTestFields(null, null, target); + return new BazelTestFields(null, null, target, null); } @@ -146,6 +127,15 @@ public String getBazelTarget() { return bazelTarget; } + + /** + * Parameters to pass to the test runner, such as --watch. + */ + @Nullable + public String getAdditionalArgs() { + return additionalArgs; + } + @NotNull BazelTestFields copy() { return new BazelTestFields(this); @@ -209,31 +199,32 @@ GeneralCommandLine getLaunchCommand(@NotNull final Project project, final GeneralCommandLine commandLine = new GeneralCommandLine().withWorkDirectory(workspace.getRoot().getPath()); commandLine.setCharset(CharsetToolkit.UTF8_CHARSET); commandLine.setExePath(FileUtil.toSystemDependentName(launchingScript)); - // If we use the normal bazel launch script, then we want to use only flags for that mode. - // This will be invoked if FlutterSettings.useNewBazelTestRunner is false, or if the - // new bazel test script is not available. - if (useNewBazelTestRunner(project)) { - commandLine.addParameter("--no-color"); - final String relativeEntryFilePath = entryFile == null - ? null - : FileUtil.getRelativePath(workspace.getRoot().getPath(), entryFile, '/'); - switch (getScope(project)) { - case NAME: - commandLine.addParameters("--name", testName); - commandLine.addParameter(relativeEntryFilePath); - break; - case FILE: - commandLine.addParameter(relativeEntryFilePath); - break; - case TARGET_PATTERN: - commandLine.addParameter(bazelTarget); - break; - } - } else { - // If the new bazel test runner is disabled, we will simply run the bazel target. - commandLine.addParameter(bazelTarget); + + // User specified additional target arguments. + final CommandLineTokenizer testArgsTokenizer = new CommandLineTokenizer( + StringUtil.notNullize(additionalArgs)); + while (testArgsTokenizer.hasMoreTokens()) { + commandLine.addParameter(testArgsTokenizer.nextToken()); + } + + commandLine.addParameter("--no-color"); + final String relativeEntryFilePath = entryFile == null + ? null + : FileUtil.getRelativePath(workspace.getRoot().getPath(), entryFile, '/'); + switch (getScope(project)) { + case NAME: + commandLine.addParameters("--name", testName); + commandLine.addParameter(relativeEntryFilePath); + break; + case FILE: + commandLine.addParameter(relativeEntryFilePath); + break; + case TARGET_PATTERN: + commandLine.addParameter(bazelTarget); + break; } + if (mode == RunMode.DEBUG) { commandLine.addParameters("--", "--enable-debugging"); } @@ -261,18 +252,14 @@ protected void verifyMainFile(Project project) throws RuntimeConfigurationError * *

*

    - *
  • Scope.NAME: The testName and entryFile fields are both non-null. The bazelTarget field may be null.
  • - *
  • Scope.FILE: The entryFile field is non-null. The bazelTarget field may be null.
  • - *
  • Scope.TARGET_PATTERN: The testName and entryFile fields may both be null. If the bazelTarget field is non-null, this target is - * runnable.
  • + *
  • Scope.NAME: The testName and entryFile fields are both non-null. The bazelTarget field may be null.
  • + *
  • Scope.FILE: The entryFile field is non-null. The bazelTarget field may be null.
  • + *
  • Scope.TARGET_PATTERN: The testName and entryFile fields may both be null. If the bazelTarget field is non-null, this target is + * runnable.
  • *
- * */ @NotNull public Scope getScope(@NotNull Project project) { - if (!useNewBazelTestRunner(project)) { - return Scope.TARGET_PATTERN; - } if (testName != null && entryFile != null) { return Scope.NAME; } @@ -286,6 +273,7 @@ public void writeTo(Element element) { ElementIO.addOption(element, "testName", testName); ElementIO.addOption(element, "entryFile", entryFile); ElementIO.addOption(element, "bazelTarget", bazelTarget); + ElementIO.addOption(element, "additionalArgs", additionalArgs); } public static BazelTestFields readFrom(Element element) { @@ -294,10 +282,12 @@ public static BazelTestFields readFrom(Element element) { final String testName = options.get("testName"); final String entryFile = options.get("entryFile"); final String bazelTarget = options.get("bazelTarget"); + final String additionalArgs = options.get("additionalArgs"); try { - return new BazelTestFields(testName, entryFile, bazelTarget); - } catch (IllegalArgumentException e) { + return new BazelTestFields(testName, entryFile, bazelTarget, additionalArgs); + } + catch (IllegalArgumentException e) { throw new InvalidDataException(e.getMessage()); } } @@ -315,15 +305,10 @@ public void checkRunnable(@NotNull BazelTestFields fields, @NotNull Project proj public void checkRunnable(@NotNull BazelTestFields fields, @NotNull Project project) throws RuntimeConfigurationError { // The new bazel test runner could not be found. final Workspace workspace = fields.getWorkspace(project); - if (workspace == null || workspace.getTestScript() == null) { + if (workspace == null || workspace.getTestScript() == null) { throw new RuntimeConfigurationError(FlutterBundle.message("flutter.run.bazel.newBazelTestRunnerUnavailable"), () -> FlutterSettingsConfigurable.openFlutterSettings(project)); } - // The new bazel test runner was not turned on. - if (!fields.useNewBazelTestRunner(project)) { - throw new RuntimeConfigurationError(FlutterBundle.message("flutter.run.bazel.mustUseNewBazelTestRunner"), - () -> FlutterSettingsConfigurable.openFlutterSettings(project)); - } fields.verifyMainFile(project); } diff --git a/src/io/flutter/run/bazelTest/BazelTestLaunchState.java b/src/io/flutter/run/bazelTest/BazelTestLaunchState.java index c3e98dedf8..a9264f3a64 100644 --- a/src/io/flutter/run/bazelTest/BazelTestLaunchState.java +++ b/src/io/flutter/run/bazelTest/BazelTestLaunchState.java @@ -21,6 +21,7 @@ /** * The Bazel version of the {@link io.flutter.run.test.TestLaunchState}. */ +@SuppressWarnings("JavadocReference") public class BazelTestLaunchState extends CommandLineState { @NotNull private final BazelTestFields fields; diff --git a/src/io/flutter/run/bazelTest/BazelTestRunner.java b/src/io/flutter/run/bazelTest/BazelTestRunner.java index 6b3471ad5e..f834b6b1d2 100644 --- a/src/io/flutter/run/bazelTest/BazelTestRunner.java +++ b/src/io/flutter/run/bazelTest/BazelTestRunner.java @@ -30,11 +30,12 @@ import com.intellij.xdebugger.XDebugProcessStarter; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.sdk.DartSdkLibUtil; import com.jetbrains.lang.dart.util.DartUrlResolver; import io.flutter.FlutterUtils; +import io.flutter.ObservatoryConnector; import io.flutter.run.PositionMapper; +import io.flutter.run.common.CommonTestConfigUtils; import io.flutter.run.test.FlutterTestRunner; import io.flutter.settings.FlutterSettings; import io.flutter.utils.StdoutJsonParser; @@ -48,7 +49,6 @@ * The Bazel version of the {@link FlutterTestRunner}. Runs a Bazel Flutter test configuration in the debugger. */ public class BazelTestRunner extends GenericProgramRunner { - private static final Logger LOG = Logger.getInstance(BazelTestRunner.class); @NotNull @@ -150,10 +150,10 @@ public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDest @Nullable @Override public String getWebSocketUrl() { - if (observatoryUri == null || !observatoryUri.startsWith("http:") || !observatoryUri.endsWith("/")) { + if (observatoryUri == null || !observatoryUri.startsWith("http:")) { return null; } - return observatoryUri.replace("http:", "ws:") + "ws"; + return CommonTestConfigUtils.convertHttpServiceProtocolToWs(observatoryUri); } @Nullable diff --git a/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.form b/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.form index b975d5657c..878eef52fa 100644 --- a/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.form +++ b/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.form @@ -1,6 +1,6 @@
- + @@ -25,7 +25,7 @@ - + @@ -116,6 +116,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.java b/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.java index 19d091253b..84eb6a7afd 100644 --- a/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.java +++ b/src/io/flutter/run/bazelTest/FlutterBazelTestConfigurationEditorForm.java @@ -43,20 +43,17 @@ public class FlutterBazelTestConfigurationEditorForm extends SettingsEditor { + final Scope next = getScope(); + updateFields(next); + render(getScope()); }); scope.setModel(new DefaultComboBoxModel<>(new Scope[]{TARGET_PATTERN, FILE, NAME})); scope.addActionListener((ActionEvent e) -> { @@ -74,9 +71,7 @@ public void customize(final JList list, setText(value.getDisplayName()); } }); - // Only enable scope changes if the new bazel test runner is enabled. - // If the new runner is disabled, all scopes will be blaze target-level. - scope.setEnabled(useNewBazelTestRunner); + scope.setEnabled(true); final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileDescriptor(); @@ -89,6 +84,7 @@ protected void resetEditorFrom(@NotNull final BazelTestConfig configuration) { myTestName.setText(fields.getTestName()); myEntryFile.setText(FileUtil.toSystemDependentName(StringUtil.notNullize(fields.getEntryFile()))); myBuildTarget.setText(StringUtil.notNullize(fields.getBazelTarget())); + myAdditionalArgs.setText(StringUtil.notNullize(fields.getAdditionalArgs())); final Scope next = fields.getScope(configuration.getProject()); scope.setSelectedItem(next); render(next); @@ -99,10 +95,12 @@ protected void applyEditorTo(@NotNull final BazelTestConfig configuration) { final String testName = getTextValue(myTestName); final String entryFile = getFilePathFromTextValue(myEntryFile); final String bazelTarget = getTextValue(myBuildTarget); + final String additionalArgs = getTextValue(myAdditionalArgs); final BazelTestFields fields = new BazelTestFields( testName, entryFile, - bazelTarget + bazelTarget, + additionalArgs ); configuration.setFields(fields); } @@ -119,6 +117,7 @@ protected JComponent createEditor() { * a suitable default. */ private void updateFields(Scope next) { + // TODO(devoncarew): This if path is empty - due to a refactoring? if (next == Scope.TARGET_PATTERN && displayedScope != Scope.TARGET_PATTERN) { } else if (next != Scope.TARGET_PATTERN) { @@ -142,50 +141,34 @@ private Scope getScope() { */ private void render(Scope next) { - if (useNewBazelTestRunner) { - scope.setVisible(true); - scopeLabel.setVisible(true); - scopeLabelHint.setVisible(true); + scope.setVisible(true); + scopeLabel.setVisible(true); + scopeLabelHint.setVisible(true); - myTestName.setVisible(next == Scope.NAME); - myTestNameLabel.setVisible(next == Scope.NAME); - myTestNameHintLabel.setVisible(next == Scope.NAME); + myAdditionalArgs.setVisible(true); + myAdditionalArgsLabel.setVisible(true); + myAdditionalArgsLabel.setVisible(true); - myEntryFile.setVisible(next == Scope.FILE || next == Scope.NAME); - myEntryFileLabel.setVisible(next == Scope.FILE || next == Scope.NAME); - myEntryFileHintLabel.setVisible(next == Scope.FILE || next == Scope.NAME); + myTestName.setVisible(next == Scope.NAME); + myTestNameLabel.setVisible(next == Scope.NAME); + myTestNameHintLabel.setVisible(next == Scope.NAME); - myBuildTarget.setVisible(next == Scope.TARGET_PATTERN); - myBuildTargetLabel.setVisible(next == Scope.TARGET_PATTERN); - myBuildTargetHintLabel.setVisible(next == Scope.TARGET_PATTERN); + myEntryFile.setVisible(next == Scope.FILE || next == Scope.NAME); + myEntryFileLabel.setVisible(next == Scope.FILE || next == Scope.NAME); + myEntryFileHintLabel.setVisible(next == Scope.FILE || next == Scope.NAME); - // Because the scope of the underlying fields is calculated based on which parameters are assigned, - // we remove fields that aren't part of the selected scope. - if (next.equals(Scope.TARGET_PATTERN)) { - myTestName.setText(""); - myEntryFile.setText(""); - } - else if (next.equals(Scope.FILE)) { - myTestName.setText(""); - } - } else { - // If the new bazel test runner is disabled, then all scopes are build target-level. - // We'll preserve the legacy appearance of the form. - scope.setVisible(false); - scopeLabel.setVisible(false); - scopeLabelHint.setVisible(false); - - myTestName.setVisible(false); - myTestNameLabel.setVisible(false); - myTestNameHintLabel.setVisible(false); - - myEntryFile.setVisible(true); - myEntryFileLabel.setVisible(true); - myEntryFileHintLabel.setVisible(true); - - myBuildTarget.setVisible(true); - myBuildTargetLabel.setVisible(true); - myBuildTargetHintLabel.setVisible(true); + myBuildTarget.setVisible(next == Scope.TARGET_PATTERN); + myBuildTargetLabel.setVisible(next == Scope.TARGET_PATTERN); + myBuildTargetHintLabel.setVisible(next == Scope.TARGET_PATTERN); + + // Because the scope of the underlying fields is calculated based on which parameters are assigned, + // we remove fields that aren't part of the selected scope. + if (next.equals(Scope.TARGET_PATTERN)) { + myTestName.setText(""); + myEntryFile.setText(""); + } + else if (next.equals(Scope.FILE)) { + myTestName.setText(""); } displayedScope = next; diff --git a/src/io/flutter/run/bazelTest/FlutterBazelTestLineMarkerContributor.java b/src/io/flutter/run/bazelTest/FlutterBazelTestLineMarkerContributor.java index 820afcde30..f702c8efd0 100644 --- a/src/io/flutter/run/bazelTest/FlutterBazelTestLineMarkerContributor.java +++ b/src/io/flutter/run/bazelTest/FlutterBazelTestLineMarkerContributor.java @@ -24,10 +24,6 @@ public FlutterBazelTestLineMarkerContributor() { @Override public Info getInfo(@NotNull PsiElement element) { final FlutterSettings settings = FlutterSettings.getInstance(); - // Only the new bazel test runner supports running tests inside of a file or for a specific test name. - if (!settings.useNewBazelTestRunner(element.getProject())) { - return null; - } return super.getInfo(element); } } diff --git a/src/io/flutter/run/common/CommonTestConfigUtils.java b/src/io/flutter/run/common/CommonTestConfigUtils.java index 340d872199..c7c36ff000 100644 --- a/src/io/flutter/run/common/CommonTestConfigUtils.java +++ b/src/io/flutter/run/common/CommonTestConfigUtils.java @@ -6,6 +6,7 @@ package io.flutter.run.common; import com.google.common.annotations.VisibleForTesting; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.util.PsiTreeUtil; @@ -16,20 +17,29 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.regex.Pattern; + /** * Common utilities for processing Flutter tests. - * *

* This class is useful for identifying the {@link TestType} of different Dart objects */ public abstract class CommonTestConfigUtils { /** - * Widget test function as defined in package:flutter_test/src/widget_tester.dart. + * Regex that matches customized versions of the Widget test function from package:flutter_test/src/widget_tester.dart. + *

+ * This will match all test methods with names that start with 'test', optionally + * have additional text in the middle, and end with 'Widgets'. */ - public static final String WIDGET_TEST_FUNCTION = "testWidgets"; + public static final Pattern WIDGET_TEST_REGEX = Pattern.compile("test([A-Z][A-Za-z0-9_$]*)?Widgets"); public abstract TestType asTestCall(@NotNull PsiElement element); + public static String convertHttpServiceProtocolToWs(String url) { + return StringUtil.trimTrailing( + url.replaceFirst("http:", "ws:"), '/') + "/ws"; + } + @VisibleForTesting public boolean isMainFunctionDeclarationWithTests(@NotNull PsiElement element) { if (DartSyntax.isMainFunctionDeclaration(element)) { diff --git a/src/io/flutter/run/common/TestType.java b/src/io/flutter/run/common/TestType.java index d6f62ebf11..30b04a9b0d 100644 --- a/src/io/flutter/run/common/TestType.java +++ b/src/io/flutter/run/common/TestType.java @@ -16,6 +16,7 @@ import javax.swing.*; import java.util.Arrays; import java.util.List; +import java.util.regex.Pattern; /** * Different scopes of test that can be run on a Flutter app. @@ -25,8 +26,12 @@ */ public enum TestType { // Note that mapping elements to their most specific enclosing function call depends on the ordering from most to least specific. - SINGLE(AllIcons.RunConfigurations.TestState.Run, "test", CommonTestConfigUtils.WIDGET_TEST_FUNCTION), + SINGLE(AllIcons.RunConfigurations.TestState.Run, CommonTestConfigUtils.WIDGET_TEST_REGEX, "test"), GROUP(AllIcons.RunConfigurations.TestState.Run_run, "group"), + /** + * This {@link TestType} doesn't know how to detect main methods. + * The logic to detect main methods is in {@link CommonTestConfigUtils}. + */ MAIN(AllIcons.RunConfigurations.TestState.Run_run) { @NotNull public String getTooltip(@NotNull PsiElement element) { @@ -37,9 +42,15 @@ public String getTooltip(@NotNull PsiElement element) { @NotNull private final Icon myIcon; private final List myTestFunctionNames; + private final Pattern myTestFunctionRegex; TestType(@NotNull Icon icon, String... testFunctionNames) { + this(icon, null, testFunctionNames); + } + + TestType(@NotNull Icon icon, @Nullable Pattern testFunctionRegex, String... testFunctionNames) { myIcon = icon; + myTestFunctionRegex = testFunctionRegex; myTestFunctionNames = Arrays.asList(testFunctionNames); } @@ -57,10 +68,22 @@ public Icon getIcon() { return myIcon; } + /** + * Describes whether the given {@param element} matches one of the names this {@link TestType} is set up to look for. + *

+ * Does not match the main function. + */ boolean matchesFunction(@NotNull DartCallExpression element) { - return myTestFunctionNames.stream().anyMatch(name -> DartSyntax.isCallToFunctionNamed(element, name)); + final boolean hasTestFunctionName = myTestFunctionNames.stream().anyMatch(name -> DartSyntax.isCallToFunctionNamed(element, name)); + if (!hasTestFunctionName && myTestFunctionRegex != null) { + return DartSyntax.isCallToFunctionMatching(element, myTestFunctionRegex); + } + return hasTestFunctionName; } + /** + * Describes the tooltip to show on a particular {@param element}. + */ @NotNull public String getTooltip(@NotNull PsiElement element, @NotNull CommonTestConfigUtils testConfigUtils) { final String testName = testConfigUtils.findTestName(element); @@ -71,6 +94,9 @@ public String getTooltip(@NotNull PsiElement element, @NotNull CommonTestConfigU return "Run Test"; } + /** + * Finds the closest corresponding test function of this {@link TestType} that encloses the given {@param element}. + */ @Nullable public DartCallExpression findCorrespondingCall(@NotNull PsiElement element) { for (String name : myTestFunctionNames) { @@ -79,6 +105,9 @@ public DartCallExpression findCorrespondingCall(@NotNull PsiElement element) { return call; } } + if (myTestFunctionRegex != null) { + return DartSyntax.findEnclosingFunctionCall(element, myTestFunctionRegex); + } return null; } } diff --git a/src/io/flutter/run/daemon/DaemonApi.java b/src/io/flutter/run/daemon/DaemonApi.java index 3d021110cd..eafd90d0cd 100644 --- a/src/io/flutter/run/daemon/DaemonApi.java +++ b/src/io/flutter/run/daemon/DaemonApi.java @@ -79,6 +79,10 @@ CompletableFuture stopApp(@NotNull String appId) { return send("app.stop", new AppStop(appId)); } + CompletableFuture detachApp(@NotNull String appId) { + return send("app.detach", new AppDetach(appId)); + } + void cancelPending() { // We used to complete the commands with exceptions here (completeExceptionally), but that generally was surfaced // to the user as an exception in the tool. We now choose to not complete the command at all. @@ -309,8 +313,6 @@ private static PrintWriter getStdin(ProcessHandler processHandler) { public static class RestartResult { private int code; private String message; - private String hintMessage; - private String hintId; public boolean ok() { return code == 0; @@ -324,18 +326,6 @@ public String getMessage() { return message; } - public String getHintMessage() { - return hintMessage; - } - - public String getHintId() { - return hintId; - } - - public boolean isRestartRecommended() { - return "restartRecommended".equals(hintId); - } - @Override public String toString() { return getCode() + ":" + getMessage(); @@ -425,6 +415,20 @@ Boolean parseResult(JsonElement result) { } } + @SuppressWarnings("unused") + private static class AppDetach extends Params { + @NotNull final String appId; + + AppDetach(@NotNull String appId) { + this.appId = appId; + } + + @Override + Boolean parseResult(JsonElement result) { + return GSON.fromJson(result, Boolean.class); + } + } + @SuppressWarnings("unused") private static class AppServiceExtension extends Params { final String appId; diff --git a/src/io/flutter/run/daemon/DaemonConsoleView.java b/src/io/flutter/run/daemon/DaemonConsoleView.java index 8907936377..bc0d5a0742 100644 --- a/src/io/flutter/run/daemon/DaemonConsoleView.java +++ b/src/io/flutter/run/daemon/DaemonConsoleView.java @@ -9,7 +9,6 @@ import com.intellij.execution.configurations.SearchScopeProvider; import com.intellij.execution.filters.TextConsoleBuilder; import com.intellij.execution.filters.TextConsoleBuilderImpl; -import com.intellij.execution.filters.UrlFilter; import com.intellij.execution.impl.ConsoleViewImpl; import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.execution.ui.ConsoleView; @@ -47,7 +46,6 @@ protected ConsoleView createConsole() { // Set up basic console filters. (More may be added later.) builder.addFilter(new DartRelativePathsConsoleFilter(env.getProject(), workDir.getPath())); - builder.addFilter(new UrlFilter()); launcher.setConsoleBuilder(builder); } diff --git a/src/io/flutter/run/daemon/DaemonEvent.java b/src/io/flutter/run/daemon/DaemonEvent.java index 297ab2c19b..6f77755cfc 100644 --- a/src/io/flutter/run/daemon/DaemonEvent.java +++ b/src/io/flutter/run/daemon/DaemonEvent.java @@ -56,10 +56,12 @@ static void dispatch(@NotNull JsonObject obj, @NotNull Listener listener) { static DaemonEvent create(@NotNull String eventName, @NotNull JsonObject params) { try { switch (eventName) { + case "daemon.log": + return GSON.fromJson(params, DaemonLog.class); case "daemon.logMessage": - return GSON.fromJson(params, LogMessage.class); + return GSON.fromJson(params, DaemonLogMessage.class); case "daemon.showMessage": - return GSON.fromJson(params, ShowMessage.class); + return GSON.fromJson(params, DaemonShowMessage.class); case "app.start": return GSON.fromJson(params, AppStarting.class); case "app.debugPort": @@ -108,10 +110,13 @@ default void processTerminated(int exitCode) { // daemon domain - default void onDaemonLogMessage(LogMessage event) { + default void onDaemonLog(DaemonLog event) { } - default void onDaemonShowMessage(ShowMessage event) { + default void onDaemonLogMessage(DaemonLogMessage event) { + } + + default void onDaemonShowMessage(DaemonShowMessage event) { } // app domain @@ -149,7 +154,18 @@ default void onDeviceRemoved(DeviceRemoved event) { // daemon domain @SuppressWarnings("unused") - static class LogMessage extends DaemonEvent { + static class DaemonLog extends DaemonEvent { + // "event":"daemon.log" + String log; + boolean error; + + void accept(Listener listener) { + listener.onDaemonLog(this); + } + } + + @SuppressWarnings("unused") + static class DaemonLogMessage extends DaemonEvent { // "event":"daemon.logMessage" String level; String message; @@ -161,7 +177,7 @@ void accept(Listener listener) { } @SuppressWarnings("unused") - static class ShowMessage extends DaemonEvent { + static class DaemonShowMessage extends DaemonEvent { // "event":"daemon.showMessage" String level; String title; @@ -176,10 +192,14 @@ void accept(Listener listener) { @SuppressWarnings("unused") static class AppStarting extends DaemonEvent { + public static final String LAUNCH_MODE_RUN = "run"; + public static final String LAUNCH_MODE_ATTACH = "attach"; + // "event":"app.start" String appId; String deviceId; String directory; + String launchMode; boolean supportsRestart; void accept(Listener listener) { @@ -191,7 +211,7 @@ void accept(Listener listener) { static class AppDebugPort extends DaemonEvent { // "event":"app.eventDebugPort" String appId; - // port seems to be deprecated + // port is deprecated; prefer using wsUri. // int port; String wsUri; String baseUri; diff --git a/src/io/flutter/run/daemon/DeviceDaemon.java b/src/io/flutter/run/daemon/DeviceDaemon.java index 99e4be1ff0..0e2a25a5fa 100644 --- a/src/io/flutter/run/daemon/DeviceDaemon.java +++ b/src/io/flutter/run/daemon/DeviceDaemon.java @@ -140,7 +140,14 @@ static Command chooseCommand(@NotNull final Project project) { try { final String path = FlutterSdkUtil.pathToFlutterTool(sdk.getHomePath()); - return new Command(sdk.getHomePath(), path, ImmutableList.of("daemon"), androidHome); + final ImmutableList list; + if (FlutterUtils.isIntegrationTestingMode()) { + list = ImmutableList.of("--show-test-device", "daemon"); + } + else { + list = ImmutableList.of("daemon"); + } + return new Command(sdk.getHomePath(), path, list, androidHome); } catch (ExecutionException e) { FlutterUtils.warn(LOG, "Unable to calculate command to watch Flutter devices", e); @@ -313,12 +320,12 @@ private static class Listener implements DaemonEvent.Listener { // daemon domain @Override - public void onDaemonLogMessage(@NotNull DaemonEvent.LogMessage message) { + public void onDaemonLogMessage(@NotNull DaemonEvent.DaemonLogMessage message) { LOG.info("flutter device daemon #" + daemonId + ": " + message.message); } @Override - public void onDaemonShowMessage(@NotNull DaemonEvent.ShowMessage event) { + public void onDaemonShowMessage(@NotNull DaemonEvent.DaemonShowMessage event) { if ("error".equals(event.level)) { FlutterMessages.showError(event.title, event.message); } diff --git a/src/io/flutter/run/daemon/FlutterApp.java b/src/io/flutter/run/daemon/FlutterApp.java index 176366f1e9..0399c67af8 100644 --- a/src/io/flutter/run/daemon/FlutterApp.java +++ b/src/io/flutter/run/daemon/FlutterApp.java @@ -29,9 +29,9 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.EventDispatcher; import com.intellij.util.concurrency.AppExecutorUtil; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import io.flutter.FlutterInitializer; import io.flutter.FlutterUtils; +import io.flutter.ObservatoryConnector; import io.flutter.logging.FlutterLog; import io.flutter.pub.PubRoot; import io.flutter.pub.PubRoots; @@ -68,7 +68,9 @@ public class FlutterApp { private final @NotNull Project myProject; private final @Nullable Module myModule; private final @NotNull RunMode myMode; - private final @NotNull FlutterDevice myDevice; + // TODO(jwren): myDevice is not-null for all run configurations except flutter web configurations + // See https://github.com/flutter/flutter-intellij/issues/3293. + private final @Nullable FlutterDevice myDevice; private final @NotNull ProcessHandler myProcessHandler; private final @NotNull ExecutionEnvironment myExecutionEnvironment; private final @NotNull DaemonApi myDaemonApi; @@ -79,6 +81,15 @@ public class FlutterApp { private @Nullable String myBaseUri; private @Nullable ConsoleView myConsole; + private boolean isFlutterWeb = false; + + /** + * The command with which the app was launched. + *

+ * Should be "run" if the app was `flutter run` and "attach" if the app was `flutter attach`. + */ + private @Nullable String myLaunchMode; + private @Nullable List myPubRoots; private int reloadCount; @@ -115,7 +126,7 @@ public static FlutterApp fromEnv(@NotNull ExecutionEnvironment env) { FlutterApp(@NotNull Project project, @Nullable Module module, @NotNull RunMode mode, - @NotNull FlutterDevice device, + @Nullable FlutterDevice device, @NotNull ProcessHandler processHandler, @NotNull ExecutionEnvironment executionEnvironment, @NotNull DaemonApi daemonApi, @@ -186,7 +197,7 @@ public static FlutterApp fromProcess(@NotNull ProcessHandler process) { } @Nullable - public static FlutterApp fromProjectProcess(@NotNull Project project) { + public static FlutterApp firstFromProjectProcess(@NotNull Project project) { final List runningProcesses = ExecutionManager.getInstance(project).getContentManager().getAllDescriptors(); for (RunContentDescriptor descriptor : runningProcesses) { @@ -202,6 +213,24 @@ public static FlutterApp fromProjectProcess(@NotNull Project project) { return null; } + @NotNull + public static List allFromProjectProcess(@NotNull Project project) { + final List allRunningApps = new ArrayList<>(); + final List runningProcesses = + ExecutionManager.getInstance(project).getContentManager().getAllDescriptors(); + for (RunContentDescriptor descriptor : runningProcesses) { + final ProcessHandler process = descriptor.getProcessHandler(); + if (process != null) { + final FlutterApp app = FlutterApp.fromProcess(process); + if (app != null) { + allRunningApps.add(app); + } + } + } + + return allRunningApps; + } + /** * Creates a process that will launch the flutter app. *

@@ -212,7 +241,7 @@ public static FlutterApp start(@NotNull ExecutionEnvironment env, @NotNull Project project, @Nullable Module module, @NotNull RunMode mode, - @NotNull FlutterDevice device, + @Nullable FlutterDevice device, @NotNull GeneralCommandLine command, @Nullable String analyticsStart, @Nullable String analyticsStop) @@ -276,6 +305,19 @@ public ObservatoryConnector getConnector() { return myConnector; } + public void setIsFlutterWeb(boolean value) { + isFlutterWeb = value; + } + + public boolean getIsFlutterWeb() { + return isFlutterWeb; + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean appSupportsHotReload() { + return !isFlutterWeb; + } + public State getState() { return myState.get(); } @@ -305,6 +347,10 @@ void setBaseUri(@NotNull String uri) { myBaseUri = uri; } + void setLaunchMode(@Nullable String launchMode) { + myLaunchMode = launchMode; + } + /** * Perform a hot restart of the the app. */ @@ -322,8 +368,7 @@ public CompletableFuture performRestartApp(@NotNull Str LocalHistory.getInstance().putSystemLabel(getProject(), "Flutter hot restart"); - final long reloadTimestamp = System.currentTimeMillis(); - maxFileTimestamp = reloadTimestamp; + maxFileTimestamp = System.currentTimeMillis(); changeState(State.RESTARTING); final CompletableFuture future = @@ -346,7 +391,7 @@ public boolean isSameModule(@Nullable final Module other) { } /** - * * @return whether the latest of the version of the file is running. + * @return whether the latest of the version of the file is running. */ public boolean isLatestVersionRunning(VirtualFile file) { return file != null && file.getTimeStamp() <= maxFileTimestamp; @@ -369,8 +414,7 @@ public CompletableFuture performHotReload(boolean pause LocalHistory.getInstance().putSystemLabel(getProject(), "hot reload #" + userReloadCount); - final long reloadTimestamp = System.currentTimeMillis(); - maxFileTimestamp = reloadTimestamp; + maxFileTimestamp = System.currentTimeMillis(); changeState(State.RELOADING); final CompletableFuture future = @@ -512,7 +556,13 @@ public Future shutdownAsync() { // Do the rest in the background to avoid freezing the Swing dispatch thread. AppExecutorUtil.getAppExecutorService().submit(() -> { // Try to shut down gracefully (need to wait for a response). - final Future stopDone = myDaemonApi.stopApp(appId); + final Future stopDone; + if (DaemonEvent.AppStarting.LAUNCH_MODE_ATTACH.equals(myLaunchMode)) { + stopDone = myDaemonApi.detachApp(appId); + } + else { + stopDone = myDaemonApi.stopApp(appId); + } final Stopwatch watch = Stopwatch.createStarted(); while (watch.elapsed(TimeUnit.SECONDS) < 10 && getState() == State.TERMINATING) { try { @@ -555,12 +605,14 @@ public boolean isSessionActive() { !debugProcess.getSession().isStopped(); } + @Nullable public FlutterDevice device() { return myDevice; } + @Nullable public String deviceId() { - return myDevice.deviceId(); + return myDevice != null ? myDevice.deviceId() : null; } public void setFlutterDebugProcess(FlutterDebugProcess flutterDebugProcess) { @@ -698,7 +750,16 @@ public void processTerminated(int exitCode) { // daemon domain @Override - public void onDaemonLogMessage(@NotNull DaemonEvent.LogMessage message) { + public void onDaemonLog(@NotNull DaemonEvent.DaemonLog message) { + final ConsoleView console = app.getConsole(); + if (console == null) return; + if (message.log != null) { + console.print(message.log + "\n", message.error ? ConsoleViewContentType.ERROR_OUTPUT : ConsoleViewContentType.NORMAL_OUTPUT); + } + } + + @Override + public void onDaemonLogMessage(@NotNull DaemonEvent.DaemonLogMessage message) { LOG.info("flutter app: " + message.message); } @@ -707,6 +768,7 @@ public void onDaemonLogMessage(@NotNull DaemonEvent.LogMessage message) { @Override public void onAppStarting(DaemonEvent.AppStarting event) { app.setAppId(event.appId); + app.setLaunchMode(event.launchMode); } @Override diff --git a/src/io/flutter/run/test/FlutterTestEventsConverter.java b/src/io/flutter/run/test/FlutterTestEventsConverter.java index 71e29ac8de..43fd0f9955 100644 --- a/src/io/flutter/run/test/FlutterTestEventsConverter.java +++ b/src/io/flutter/run/test/FlutterTestEventsConverter.java @@ -10,8 +10,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.intellij.execution.testframework.TestConsoleProperties; -import com.jetbrains.lang.dart.ide.runner.test.DartTestEventsConverterZ; import com.jetbrains.lang.dart.util.DartUrlResolver; +import io.flutter.test.DartTestEventsConverterZ; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/io/flutter/run/test/FlutterTestLocationProvider.java b/src/io/flutter/run/test/FlutterTestLocationProvider.java index de91406e42..1a4ed250ca 100644 --- a/src/io/flutter/run/test/FlutterTestLocationProvider.java +++ b/src/io/flutter/run/test/FlutterTestLocationProvider.java @@ -9,25 +9,21 @@ import com.intellij.execution.Location; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.jetbrains.lang.dart.ide.runner.util.DartTestLocationProviderZ; import com.jetbrains.lang.dart.psi.DartCallExpression; +import io.flutter.util.DartTestLocationProviderZ; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Objects; - -import static io.flutter.run.test.TestConfigUtils.WIDGET_TEST_FUNCTION; - public class FlutterTestLocationProvider extends DartTestLocationProviderZ { public static final FlutterTestLocationProvider INSTANCE = new FlutterTestLocationProvider(); - @Nullable @Override protected Location getLocationByLineAndColumn(@NotNull PsiFile file, int line, int column) { try { return super.getLocationByLineAndColumn(file, line, column); - } catch (IndexOutOfBoundsException e) { + } + catch (IndexOutOfBoundsException e) { // Line and column info can be wrong, in which case we fall-back on test and group name for test discovery. } @@ -36,6 +32,6 @@ protected Location getLocationByLineAndColumn(@NotNull PsiFile file, @Override protected boolean isTest(@NotNull DartCallExpression expression) { - return super.isTest(expression) || Objects.equals(WIDGET_TEST_FUNCTION, expression.getExpression().getText()); + return super.isTest(expression) || TestConfigUtils.WIDGET_TEST_REGEX.matcher(expression.getExpression().getText()).matches(); } } diff --git a/src/io/flutter/run/test/FlutterTestRunner.java b/src/io/flutter/run/test/FlutterTestRunner.java index 52ff7b0a82..73ec7ffce1 100644 --- a/src/io/flutter/run/test/FlutterTestRunner.java +++ b/src/io/flutter/run/test/FlutterTestRunner.java @@ -21,10 +21,11 @@ import com.intellij.xdebugger.XDebugProcessStarter; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.util.DartUrlResolver; import io.flutter.FlutterUtils; +import io.flutter.ObservatoryConnector; import io.flutter.run.PositionMapper; +import io.flutter.run.common.CommonTestConfigUtils; import io.flutter.sdk.FlutterSdk; import io.flutter.settings.FlutterSettings; import io.flutter.utils.StdoutJsonParser; @@ -132,10 +133,10 @@ public void processWillTerminate(@NotNull ProcessEvent event, boolean willBeDest @Nullable @Override public String getWebSocketUrl() { - if (observatoryUri == null || !observatoryUri.startsWith("http:") || !observatoryUri.endsWith("/")) { + if (observatoryUri == null || !observatoryUri.startsWith("http:")) { return null; } - return observatoryUri.replace("http:", "ws:") + "ws"; + return CommonTestConfigUtils.convertHttpServiceProtocolToWs(observatoryUri); } @Nullable diff --git a/src/io/flutter/run/test/TestConfigUtils.java b/src/io/flutter/run/test/TestConfigUtils.java index c7ebcbc00e..26afd9c102 100644 --- a/src/io/flutter/run/test/TestConfigUtils.java +++ b/src/io/flutter/run/test/TestConfigUtils.java @@ -15,6 +15,7 @@ public class TestConfigUtils extends CommonTestConfigUtils { private static TestConfigUtils instance; + public static TestConfigUtils getInstance() { if (instance == null) { instance = new TestConfigUtils(); @@ -22,7 +23,8 @@ public static TestConfigUtils getInstance() { return instance; } - private TestConfigUtils() {} + private TestConfigUtils() { + } @Nullable @Override diff --git a/src/io/flutter/run/test/TestDebugProcess.java b/src/io/flutter/run/test/TestDebugProcess.java index 9795fbf0d4..cf59808a34 100644 --- a/src/io/flutter/run/test/TestDebugProcess.java +++ b/src/io/flutter/run/test/TestDebugProcess.java @@ -9,9 +9,10 @@ import com.intellij.execution.runners.ExecutionEnvironment; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.xdebugger.XDebugSession; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.util.DartUrlResolver; +import io.flutter.ObservatoryConnector; import io.flutter.run.FlutterPopFrameAction; +import io.flutter.run.OpenDevToolsAction; import io.flutter.run.OpenObservatoryAction; import io.flutter.server.vmService.DartVmServiceDebugProcess; import org.jetbrains.annotations.NotNull; @@ -39,6 +40,7 @@ public void registerAdditionalActions(@NotNull DefaultActionGroup leftToolbar, @NotNull DefaultActionGroup settings) { topToolbar.addSeparator(); topToolbar.addAction(new FlutterPopFrameAction()); + topToolbar.addAction(new OpenDevToolsAction(connector, this::isActive)); topToolbar.addAction(new OpenObservatoryAction(connector, this::isActive)); } diff --git a/src/io/flutter/run/test/TestLaunchState.java b/src/io/flutter/run/test/TestLaunchState.java index 51d917f293..7ba152056e 100644 --- a/src/io/flutter/run/test/TestLaunchState.java +++ b/src/io/flutter/run/test/TestLaunchState.java @@ -98,8 +98,6 @@ protected ProcessHandler startProcess() throws ExecutionException { case OK: assert result.processHandler != null; return result.processHandler; - case ANOTHER_RUNNING: - throw new ExecutionException("Flutter instance already running"); case EXCEPTION: assert result.exception != null; throw new ExecutionException(FlutterBundle.message("flutter.command.exception.message" + result.exception.getMessage())); diff --git a/src/io/flutter/samples/FlutterSample.java b/src/io/flutter/samples/FlutterSample.java index d4df495cd7..5a39eef46a 100644 --- a/src/io/flutter/samples/FlutterSample.java +++ b/src/io/flutter/samples/FlutterSample.java @@ -42,12 +42,32 @@ public String toString() { * Get a label suitable for display in a chooser (e.g., combobox). */ public String getDisplayLabel() { - // TODO(pq): add disambiguation once it's needed. + // TODO(pq): come up with disambiguated labels. + // TODO(pq): consider adding (package suffix too). + // Index isn't quite enough for disambiguation (there are still dups like DeletableChips). + //final String[] parts = id.split("\\."); + //final String lastPart = parts[parts.length-1]; + // + //String suffix = ""; + //try { + // final int index = Integer.parseInt(lastPart); + // if (index != 1) { + // suffix = " (" + index + ")"; + // } + //} catch (NumberFormatException e) { + // // ignore + //} + // + //return getElement() + suffix; return getElement(); } public String getShortHtmlDescription() { - return "" + parseShortHtmlDescription(description) + ""; + // The markdown builder adds paragraph blocks when we don't want them. + String html = parseShortHtmlDescription(description); + html = html.replaceAll("

", ""); + html = html.replaceAll("

", ""); + return "" + html + ""; } @VisibleForTesting @@ -71,11 +91,12 @@ public static String parseHtmlDescription(@NotNull String description) { // Remove links: [Card] => **Card** final StringBuilder builder = new StringBuilder(); - for (int i=0; i < description.length(); ++i) { + for (int i = 0; i < description.length(); ++i) { final char c = description.charAt(i); - if ((c == '[' || c == ']') && (i == 0 || description.charAt(i-1) != '\\')) { + if ((c == '[' || c == ']') && (i == 0 || description.charAt(i - 1) != '\\')) { builder.append("**"); - } else { + } + else { builder.append(c); } } @@ -83,7 +104,6 @@ public static String parseHtmlDescription(@NotNull String description) { return new MarkdownProcessor().markdown(builder.toString()).trim(); } - @NotNull public String getLibrary() { return library; diff --git a/src/io/flutter/samples/FlutterSampleActionsPanel.java b/src/io/flutter/samples/FlutterSampleActionsPanel.java index 05a8d094d7..e3c7de0ab3 100644 --- a/src/io/flutter/samples/FlutterSampleActionsPanel.java +++ b/src/io/flutter/samples/FlutterSampleActionsPanel.java @@ -18,20 +18,25 @@ import icons.FlutterIcons; import io.flutter.FlutterMessages; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.Arrays; import java.util.List; public class FlutterSampleActionsPanel extends JPanel { - protected final JLabel myLabel = new JLabel(); + // Enable to run local main. + private static final boolean TESTING_LOCALLY = false; + + protected final JBLabel myLabel = new JBLabel(); protected final JBLabel goLink; @NotNull private final List samples; - @NotNull private final Project project; + @Nullable private final Project project; protected Color myBackgroundColor; protected ColorKey myBackgroundColorKey; @@ -40,7 +45,7 @@ public class FlutterSampleActionsPanel extends JPanel { // Combo or label. JComponent sampleSelector; - FlutterSampleActionsPanel(@NotNull List samples, @NotNull Project project) { + FlutterSampleActionsPanel(@NotNull List samples, @Nullable Project project) { super(new BorderLayout()); this.samples = samples; this.project = project; @@ -51,16 +56,8 @@ public class FlutterSampleActionsPanel extends JPanel { myLabel.setText("Open sample project:"); myLabel.setBorder(JBUI.Borders.emptyRight(5)); - goLink = new JBLabel("Go..."); + goLink = createLinkLabel("Go...", this::doCreate); goLink.setBorder(JBUI.Borders.emptyLeft(8)); - goLink.setCursor(new Cursor(Cursor.HAND_CURSOR)); - goLink.addMouseListener(new MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - doCreate(); - } - }); - sampleSelector = setupSelectorComponent(); @@ -80,6 +77,20 @@ public void mouseClicked(MouseEvent e) { setupPanel(); } + private static JBLabel createLinkLabel(@NotNull String text, @NotNull Runnable onClick) { + // Standard hyperlinks were rendering oddly on 2018.3, so we create our own. + // See: https://github.com/flutter/flutter-intellij/issues/3197 + final JBLabel label = new JBLabel(text); + label.setForeground(JBColor.blue); + label.setCursor(new Cursor(Cursor.HAND_CURSOR)); + label.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + onClick.run(); + } + }); + return label; + } JComponent setupSelectorComponent() { if (samples.size() == 1) { @@ -97,8 +108,9 @@ private void updateSelection(@NotNull FlutterSample item) { } private void setupPanel() { + setLayout(new BorderLayout(0, 10)); // LABEL | SELECTOR | LINK... - final JPanel subPanel = new NonOpaquePanel(new BorderLayout(0, 10)); + final JPanel subPanel = new NonOpaquePanel(new BorderLayout()); subPanel.add(BorderLayout.WEST, myLabel); subPanel.add(BorderLayout.CENTER, sampleSelector); subPanel.add(BorderLayout.EAST, goLink); @@ -108,19 +120,23 @@ private void setupPanel() { // DESCRIPTION final JPanel descriptionPanel = new NonOpaquePanel(new BorderLayout()); - final int descriptionTopNudge = sampleSelector instanceof FlutterSampleComboBox ? -7 : -14; - descriptionPanel.setBorder(JBUI.Borders.empty(descriptionTopNudge, 12, 5, 5)); + final int topNudge = samples.size() > 1 ? 6 : 0; + descriptionPanel.setBorder(JBUI.Borders.empty(topNudge, 12, 5, 5)); descriptionPanel.add(BorderLayout.NORTH, descriptionText); add(BorderLayout.CENTER, descriptionPanel); // PLACEHOLDER (to force reflow on resize) add(BorderLayout.EAST, new NonOpaquePanel(new BorderLayout())); - setBorder(JBUI.Borders.empty(10, 10, 5, 10)); + setBorder(JBUI.Borders.empty(10, 10, 5, 10)); } @Override public Color getBackground() { + if (TESTING_LOCALLY) { + return UIUtil.getToolTipBackground(); + } + final EditorColorsScheme globalScheme = EditorColorsManager.getInstance().getGlobalScheme(); if (myBackgroundColor != null) return myBackgroundColor; if (myBackgroundColorKey != null) { @@ -137,10 +153,41 @@ FlutterSample getSelectedSample() { } private void doCreate() { + // For testing. + if (project == null) { + if (!TESTING_LOCALLY) { + FlutterMessages.showError("Sample Project Creation", "Error: null project"); + } + return; + } final FlutterSample sample = getSelectedSample(); final String status = FlutterSampleManager.createSampleProject(sample, project); if (status != null) { FlutterMessages.showError("Sample Project Creation", "Error: " + status); } } + + // For testing. + public static void main(String[] args) { + if (!TESTING_LOCALLY) { + throw new IllegalStateException("Set TESTING_LOCALLY and re-run."); + } + + final List samples = Arrays.asList( + new FlutterSample( + "foo", "baz", "baz", "baz", "baz", "This sample shows creation of a [Card] widget that shows album information\nand two actions." + ) + , + new FlutterSample( + "bar", "baz", "baz", "baz", "baz", "This sample shows how to use [onDeleted] to remove an entry when the\ndelete button is tapped." + ) + ); + + final FlutterSampleActionsPanel panel = new FlutterSampleActionsPanel(samples, null); + final JFrame frame = new JFrame("BorderLayoutDemo"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.add(panel); + frame.pack(); + frame.setVisible(true); + } } diff --git a/src/io/flutter/samples/FlutterSampleComboBox.java b/src/io/flutter/samples/FlutterSampleComboBox.java index a73f2deeee..aa9cbb93f3 100644 --- a/src/io/flutter/samples/FlutterSampleComboBox.java +++ b/src/io/flutter/samples/FlutterSampleComboBox.java @@ -64,10 +64,6 @@ protected void customizeCellRenderer(@NotNull JList lis } } - FlutterSampleComboBox() { - this(FlutterSampleManager.getSamples()); - } - FlutterSampleComboBox(@NotNull List samples) { super(new SampleModel(samples)); setRenderer(new SampleCellRenderer()); diff --git a/src/io/flutter/samples/FlutterSampleManager.java b/src/io/flutter/samples/FlutterSampleManager.java index c6bf752aca..c822688121 100644 --- a/src/io/flutter/samples/FlutterSampleManager.java +++ b/src/io/flutter/samples/FlutterSampleManager.java @@ -9,7 +9,11 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.intellij.execution.ExecutionException; import com.intellij.execution.OutputListener; +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.execution.process.OSProcessHandler; +import com.intellij.execution.process.ProcessAdapter; import com.intellij.execution.process.ProcessEvent; import com.intellij.ide.impl.ProjectUtil; import com.intellij.openapi.diagnostic.Logger; @@ -26,52 +30,112 @@ import io.flutter.pub.PubRoot; import io.flutter.sdk.FlutterCreateAdditionalSettings; import io.flutter.sdk.FlutterSdk; -import org.apache.commons.io.IOUtils; import org.jetbrains.annotations.NotNull; +import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; public class FlutterSampleManager { + // TODO(pq): remove after testing. + // See https://github.com/flutter/flutter-intellij/issues/3330. + private static final boolean DISABLE_SAMPLES = false; + + private static final String SNIPPETS_REMOTE_INDEX_URL = "https://docs.flutter.io/snippets/index.json"; private static final Logger LOG = Logger.getInstance(FlutterSampleManager.class); + @NotNull + private final FlutterSdk sdk; - private static List SAMPLES; + private List flutterSamples; - public static List getSamples() { - if (SAMPLES == null) { - // When we're reading from the repo and the file may be changing, consider a fresh read on each access. - SAMPLES = loadSamples(); - } - return SAMPLES; + public FlutterSampleManager(@NotNull FlutterSdk sdk) { + this.sdk = sdk; } - private static List loadSamples() { - final List samples = new ArrayList<>(); + public List getSamples() { + if (DISABLE_SAMPLES) { + return Collections.emptyList(); + } + + if (flutterSamples != null) { + return flutterSamples; + } + try { - // TODO(pq): replace w/ index read from repo (https://github.com/flutter/flutter/pull/25515). - //https://docs.flutter.io/snippets/index.json - - final URL url = FlutterSampleManager.class.getResource("index.json"); - final String contents = IOUtils.toString(url.toURI(), "UTF-8"); - final JsonArray jsonArray = new JsonParser().parse(contents).getAsJsonArray(); - for (JsonElement element : jsonArray) { - final JsonObject sample = element.getAsJsonObject(); - samples.add(new FlutterSample(sample.getAsJsonPrimitive("element").getAsString(), - sample.getAsJsonPrimitive("library").getAsString(), - sample.getAsJsonPrimitive("id").getAsString(), - sample.getAsJsonPrimitive("file").getAsString(), - sample.getAsJsonPrimitive("sourcePath").getAsString(), - sample.getAsJsonPrimitive("description").getAsString())); + final File tempDir = Files.createTempDirectory("flutter-samples-index").toFile(); + tempDir.deleteOnExit(); + final File tempFile = new File(tempDir, "index.json"); + + try { + final GeneralCommandLine commandLine = sdk.flutterListSamples(tempFile).createGeneralCommandLine(null); + final OSProcessHandler process = new OSProcessHandler(commandLine); + process.addProcessListener(new ProcessAdapter() { + @Override + public void processTerminated(@NotNull ProcessEvent event) { + try { + final byte[] bytes = Files.readAllBytes(tempFile.toPath()); + final String content = new String(bytes); + flutterSamples = FlutterSampleManager.readSamples(content); + } + catch (IOException e) { + LOG.warn(e); + // On IOException, short circuit checking again. + flutterSamples = Collections.emptyList(); + } + } + }); + + process.startNotify(); + + final int timeoutInMs = 2000; + // TODO(pq): is this wait right? + if (process.waitFor(timeoutInMs)) { + LOG.info("Flutter sample listing took more than " + timeoutInMs + "ms"); + // Don't cache here so that we can try again later. + } } + catch (ExecutionException e) { + LOG.warn(e); + // On ExecutionException, short circuit checking again. + flutterSamples = Collections.emptyList(); + } + } + catch (IOException e) { + LOG.warn(e); + // On IOException, short circuit checking again. + flutterSamples = Collections.emptyList(); + } + + return flutterSamples != null ? flutterSamples : Collections.emptyList(); + } + + // Called at project initialization. + public static void initialize(Project project) { + final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); + if (sdk != null) { + // Trigger a sample listing. + sdk.getSamples(); } - catch (URISyntaxException | IOException e) { - FlutterUtils.warn(LOG, e); + } + + public static List readSamples(String indexFileContents) { + final List samples = new ArrayList<>(); + final JsonArray json = new JsonParser().parse(indexFileContents).getAsJsonArray(); + for (JsonElement element : json) { + final JsonObject sample = element.getAsJsonObject(); + samples.add(new FlutterSample(sample.getAsJsonPrimitive("element").getAsString(), + sample.getAsJsonPrimitive("library").getAsString(), + sample.getAsJsonPrimitive("id").getAsString(), + sample.getAsJsonPrimitive("file").getAsString(), + sample.getAsJsonPrimitive("sourcePath").getAsString(), + sample.getAsJsonPrimitive("description").getAsString())); } // Sort by display label. @@ -79,6 +143,34 @@ private static List loadSamples() { return samples; } + private static JsonArray readSampleIndex(final URL sampleUrl) throws IOException { + final BufferedInputStream in = new BufferedInputStream(sampleUrl.openStream()); + final StringBuilder contents = new StringBuilder(); + final byte[] bytes = new byte[1024]; + int bytesRead; + while ((bytesRead = in.read(bytes)) != -1) { + contents.append(new String(bytes, 0, bytesRead)); + } + + return new JsonParser().parse(contents.toString()).getAsJsonArray(); + } + + private static JsonArray readSampleIndex() { + // Try fetching snippets index remotely, and fall back to local cache. + try { + return readSampleIndex(new URL(SNIPPETS_REMOTE_INDEX_URL)); + } + catch (IOException ignored) { + try { + return readSampleIndex(FlutterSampleManager.class.getResource("index.json")); + } + catch (IOException e) { + FlutterUtils.warn(LOG, e); + } + } + return new JsonArray(); + } + public static String createSampleProject(@NotNull FlutterSample sample, @NotNull Project project) { final FlutterSdk sdk = FlutterSdk.getFlutterSdk(project); if (sdk == null) { @@ -96,7 +188,7 @@ public static String createSampleProject(@NotNull FlutterSample sample, @NotNull final VirtualFile baseDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(dir); if (baseDir == null) { - return "unable to find project root (" + dir.getPath() +") on refresh"; + return "unable to find project root (" + dir.getPath() + ") on refresh"; } final OutputListener outputListener = new OutputListener() { diff --git a/src/io/flutter/samples/FlutterSampleNotificationProvider.java b/src/io/flutter/samples/FlutterSampleNotificationProvider.java index 9ffd5a843e..19e32f5644 100644 --- a/src/io/flutter/samples/FlutterSampleNotificationProvider.java +++ b/src/io/flutter/samples/FlutterSampleNotificationProvider.java @@ -57,7 +57,7 @@ private List getSamplesForFile(@NotNull VirtualFile file) { final String pathSuffix = FileUtil.normalize(sdk.getHomePath()) + "/packages/flutter/"; final List samples = new ArrayList<>(); - for (FlutterSample sample : FlutterSampleManager.getSamples()) { + for (FlutterSample sample : sdk.getSamples()) { final String samplePath = pathSuffix + sample.getSourcePath(); if (filePath.equals(samplePath)) { samples.add(sample); diff --git a/src/io/flutter/samples/index.json b/src/io/flutter/samples/index.json index d9e636e9ae..adc97218f1 100644 --- a/src/io/flutter/samples/index.json +++ b/src/io/flutter/samples/index.json @@ -1,13 +1,13 @@ [ { "sourcePath": "lib/src/material/icon_button.dart", - "sourceLine": 69, + "sourceLine": 103, "package": "flutter", "library": "material", "element": "IconButton", "id": "material.IconButton", "file": "material.IconButton.dart", - "description": "This sample shows an `IconButton` that uses the Material icon \"volume_up\" to\nincrease the volume." + "description": "In this sample the icon button's background color is defined with an [Ink]\nwidget whose child is an [IconButton]. The icon button's filled background\nis a light shade of blue, it's a filled circle, and it's as big as the\nbutton is." }, { "sourcePath": "lib/src/material/card.dart", @@ -50,4 +50,3 @@ "description": "This example shows a [Scaffold] with an [AppBar], a [BottomAppBar] and a\n[FloatingActionButton]. The [body] is a [Text] placed in a [Center] in order\nto center the text within the [Scaffold] and the [FloatingActionButton] is\ncentered and docked within the [BottomAppBar] using\n[FloatingActionButtonLocation.centerDocked]. The [FloatingActionButton] is\nconnected to a callback that increments a counter." } ] - diff --git a/src/io/flutter/sdk/FlutterCommand.java b/src/io/flutter/sdk/FlutterCommand.java index ee857f6adc..926866d4f0 100644 --- a/src/io/flutter/sdk/FlutterCommand.java +++ b/src/io/flutter/sdk/FlutterCommand.java @@ -25,7 +25,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; /** @@ -38,21 +37,21 @@ public class FlutterCommand { Arrays.asList(Type.PACKAGES_GET, Type.PACKAGES_UPGRADE, Type.UPGRADE)); @NotNull - private final FlutterSdk sdk; + protected final FlutterSdk sdk; - @NotNull - private final VirtualFile workDir; + @Nullable + protected final VirtualFile workDir; @NotNull private final Type type; @NotNull - private final List args; + protected final List args; /** * @see FlutterSdk for methods to create specific commands. */ - FlutterCommand(@NotNull FlutterSdk sdk, @NotNull VirtualFile workDir, @NotNull Type type, String... args) { + FlutterCommand(@NotNull FlutterSdk sdk, @Nullable VirtualFile workDir, @NotNull Type type, String... args) { this.sdk = sdk; this.workDir = workDir; this.type = type; @@ -158,13 +157,6 @@ public String toString() { return "FlutterCommand(" + getDisplayCommand() + ")"; } - /** - * The currently running command. - *

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

@@ -198,11 +190,6 @@ public OSProcessHandler startProcess(boolean sendAnalytics) { */ @NotNull public FlutterCommandStartResult startProcess(@Nullable Project project) { - // TODO(devoncarew): Many flutter commands can legitimately be run in parallel. - if (!inProgress.compareAndSet(null, this)) { - return new FlutterCommandStartResult(FlutterCommandStartResultStatus.ANOTHER_RUNNING); - } - if (isPubRelatedCommand()) { DartPlugin.setPubActionInProgress(true); } @@ -215,7 +202,6 @@ public FlutterCommandStartResult startProcess(@Nullable Project project) { handler.addProcessListener(new ProcessAdapter() { @Override public void processTerminated(@NotNull final ProcessEvent event) { - inProgress.compareAndSet(FlutterCommand.this, null); if (isPubRelatedCommand()) { DartPlugin.setPubActionInProgress(false); } @@ -225,7 +211,6 @@ public void processTerminated(@NotNull final ProcessEvent event) { return new FlutterCommandStartResult(handler); } catch (ExecutionException e) { - inProgress.compareAndSet(this, null); if (isPubRelatedCommand()) { DartPlugin.setPubActionInProgress(false); } @@ -260,16 +245,15 @@ public OSProcessHandler startProcessOrShowError(@Nullable Project project) { public GeneralCommandLine createGeneralCommandLine(@Nullable Project project) { final GeneralCommandLine line = new GeneralCommandLine(); line.setCharset(CharsetToolkit.UTF8_CHARSET); - line.withEnvironment(FlutterSdkUtil.FLUTTER_HOST_ENV, FlutterSdkUtil.getFlutterHostEnvValue()); - final String androidHome = IntelliJAndroidSdk.chooseAndroidHome(project, false); if (androidHome != null) { line.withEnvironment("ANDROID_HOME", androidHome); } - line.setExePath(FileUtil.toSystemDependentName(sdk.getHomePath() + "/bin/" + FlutterSdkUtil.flutterScriptName())); - line.setWorkDirectory(workDir.getPath()); + if (workDir != null) { + line.setWorkDirectory(workDir.getPath()); + } if (!isDoctorCommand()) { line.addParameter("--no-color"); } @@ -289,10 +273,13 @@ enum Type { CONFIG("Flutter config", "config"), CREATE("Flutter create", "create"), DOCTOR("Flutter doctor", "doctor", "--verbose"), + LIST_SAMPLES("Flutter create --lists-samples", "create", "--list-samples"), MAKE_HOST_APP_EDITABLE("Flutter make-host-app-editable", "make-host-app-editable"), PACKAGES_GET("Flutter packages get", "packages", "get"), PACKAGES_UPGRADE("Flutter packages upgrade", "packages", "upgrade"), + PACKAGES_PUB("Flutter packages pub", "packages", "pub"), RUN("Flutter run", "run"), + FLUTTER_WEB_RUN("Flutter Web run", "flutter_web_run"), UPGRADE("Flutter upgrade", "upgrade"), VERSION("Flutter version", "--version"), TEST("Flutter test", "test"); diff --git a/src/io/flutter/sdk/FlutterCommandStartResultStatus.java b/src/io/flutter/sdk/FlutterCommandStartResultStatus.java index 1552a6247c..562edd32fa 100644 --- a/src/io/flutter/sdk/FlutterCommandStartResultStatus.java +++ b/src/io/flutter/sdk/FlutterCommandStartResultStatus.java @@ -6,5 +6,5 @@ package io.flutter.sdk; public enum FlutterCommandStartResultStatus { - OK, ANOTHER_RUNNING, EXCEPTION + OK, EXCEPTION } diff --git a/src/io/flutter/sdk/FlutterPluginLibraryProperties.java b/src/io/flutter/sdk/FlutterPluginLibraryProperties.java index d8b8b6d2eb..611e7ef929 100644 --- a/src/io/flutter/sdk/FlutterPluginLibraryProperties.java +++ b/src/io/flutter/sdk/FlutterPluginLibraryProperties.java @@ -7,6 +7,7 @@ import com.intellij.openapi.roots.libraries.LibraryProperties; import com.intellij.util.xmlb.XmlSerializerUtil; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class FlutterPluginLibraryProperties extends LibraryProperties { @@ -30,7 +31,7 @@ public FlutterPluginLibraryProperties getState() { } @Override - public void loadState(FlutterPluginLibraryProperties properties) { + public void loadState(@NotNull FlutterPluginLibraryProperties properties) { XmlSerializerUtil.copyBean(properties, this); } } diff --git a/src/io/flutter/sdk/FlutterSdk.java b/src/io/flutter/sdk/FlutterSdk.java index 6ee1a53a85..aa99e1edbe 100644 --- a/src/io/flutter/sdk/FlutterSdk.java +++ b/src/io/flutter/sdk/FlutterSdk.java @@ -25,6 +25,8 @@ import io.flutter.run.FlutterLaunchMode; import io.flutter.run.daemon.FlutterDevice; import io.flutter.run.daemon.RunMode; +import io.flutter.samples.FlutterSample; +import io.flutter.samples.FlutterSampleManager; import io.flutter.settings.FlutterSettings; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,6 +54,8 @@ public class FlutterSdk { private final @NotNull FlutterSdkVersion myVersion; private final Map cachedConfigValues = new HashMap<>(); + private FlutterSampleManager sampleManager; + private FlutterSdk(@NotNull final VirtualFile home, @NotNull final FlutterSdkVersion version) { myHome = home; myVersion = version; @@ -174,6 +178,10 @@ public FlutterCommand flutterPackagesUpgrade(@NotNull PubRoot root) { return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.PACKAGES_UPGRADE); } + public FlutterCommand flutterPackagesPub(@Nullable PubRoot root, String... args) { + return new FlutterCommand(this, root == null ? null : root.getRoot(), FlutterCommand.Type.PACKAGES_PUB, args); + } + public FlutterCommand flutterMakeHostAppEditable(@NotNull PubRoot root) { return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.MAKE_HOST_APP_EDITABLE); } @@ -186,6 +194,10 @@ public FlutterCommand flutterConfig(String... additionalArgs) { return new FlutterCommand(this, getHome(), FlutterCommand.Type.CONFIG, additionalArgs); } + public FlutterCommand flutterListSamples(@NotNull File indexFile) { + return new FlutterCommand(this, getHome(), FlutterCommand.Type.LIST_SAMPLES, indexFile.getAbsolutePath()); + } + public FlutterCommand flutterRun(@NotNull PubRoot root, @NotNull VirtualFile main, @Nullable FlutterDevice device, @@ -265,6 +277,17 @@ else if (flutterLaunchMode == FlutterLaunchMode.RELEASE) { return new FlutterCommand(this, root.getRoot(), FlutterCommand.Type.ATTACH, args.toArray(new String[]{ })); } + public FlutterCommand flutterRunWeb(@NotNull PubRoot root, @NotNull RunMode mode) { + // TODO(devoncarew): We need to provision the webdev cli here. + + // TODO(jwren): After debugging is supported by webdev, this should be modified to check for debug and add + // any additional needed flags: i.e. if (mode == RunMode.DEBUG) { args.add("--debug"); } + // See https://github.com/flutter/flutter-intellij/issues/3349. + + // flutter packages pub global run webdev daemon + return new FlutterWebCommand(this, root.getRoot(), FlutterCommand.Type.FLUTTER_WEB_RUN, new String[]{ }); + } + public FlutterCommand flutterRunOnTester(@NotNull PubRoot root, @NotNull String mainPath) { final List args = new ArrayList<>(); args.add("--machine"); @@ -396,6 +419,14 @@ public PubRoot createFiles(@NotNull VirtualFile baseDir, @Nullable Module module return PubRoot.forDirectory(baseDir); } + + public List getSamples() { + if (sampleManager == null) { + sampleManager = new FlutterSampleManager(this); + } + return sampleManager.getSamples(); + } + public Process startMakeHostAppEditable(@NotNull PubRoot root, @NotNull Project project) { final Module module = root.getModule(project); if (module == null) return null; diff --git a/src/io/flutter/sdk/FlutterSettingsConfigurable.form b/src/io/flutter/sdk/FlutterSettingsConfigurable.form index dfaffd440d..b483dbb90b 100644 --- a/src/io/flutter/sdk/FlutterSettingsConfigurable.form +++ b/src/io/flutter/sdk/FlutterSettingsConfigurable.form @@ -117,7 +117,7 @@ - + @@ -158,15 +158,6 @@ - - - - - - - - - diff --git a/src/io/flutter/sdk/FlutterSettingsConfigurable.java b/src/io/flutter/sdk/FlutterSettingsConfigurable.java index 81d02bb750..b24f5a22ac 100644 --- a/src/io/flutter/sdk/FlutterSettingsConfigurable.java +++ b/src/io/flutter/sdk/FlutterSettingsConfigurable.java @@ -64,7 +64,6 @@ public class FlutterSettingsConfigurable implements SearchableConfigurable { private JCheckBox myUseLogViewCheckBox; private JCheckBox mySyncAndroidLibrariesCheckBox; private JCheckBox myDisableMemoryProfilerCheckBox; - private JCheckBox myUseNewBazelTestRunner; private final @NotNull Project myProject; FlutterSettingsConfigurable(@NotNull Project project) { @@ -102,21 +101,9 @@ public void mouseClicked(MouseEvent e) { } }); - //noinspection Convert2Lambda myFormatCodeOnSaveCheckBox .addChangeListener((e) -> myOrganizeImportsOnSaveCheckBox.setEnabled(myFormatCodeOnSaveCheckBox.isSelected())); mySyncAndroidLibrariesCheckBox.setVisible(FlutterUtils.isAndroidStudio()); - - // Disable the bazel test runner experiment if no new bazel test script is available. - final Workspace workspace = Workspace.load(myProject); - if (workspace == null || workspace.getTestScript() == null) { - myUseNewBazelTestRunner.setEnabled(false); - myUseNewBazelTestRunner.setText(FlutterBundle.message("settings.enable.bazel.test.runner") + " " - + FlutterBundle.message("settings.enable.bazel.test.runner.mustSyncClientWarning")); - } else { - myUseNewBazelTestRunner.setText(FlutterBundle.message("settings.enable.bazel.test.runner")); - myUseNewBazelTestRunner.setEnabled(true); - } } private void createUIComponents() { @@ -192,7 +179,6 @@ public boolean isModified() { return true; } - //noinspection RedundantIfStatement if (settings.isSyncingAndroidLibraries() != mySyncAndroidLibrariesCheckBox.isSelected()) { return true; } @@ -201,10 +187,6 @@ public boolean isModified() { return true; } - if (settings.useNewBazelTestRunner(myProject) != myUseNewBazelTestRunner.isSelected()) { - return true; - } - return false; } @@ -237,7 +219,6 @@ public void apply() throws ConfigurationException { settings.setVerboseLogging(myEnableVerboseLoggingCheckBox.isSelected()); settings.setSyncingAndroidLibraries(mySyncAndroidLibrariesCheckBox.isSelected()); settings.setMemoryProfilerDisabled(myDisableMemoryProfilerCheckBox.isSelected()); - settings.setUseNewBazelTestRunner(myUseNewBazelTestRunner.isSelected()); reset(); // because we rely on remembering initial state } @@ -268,7 +249,6 @@ public void reset() { myEnableVerboseLoggingCheckBox.setSelected(settings.isVerboseLogging()); mySyncAndroidLibrariesCheckBox.setSelected(settings.isSyncingAndroidLibraries()); myDisableMemoryProfilerCheckBox.setSelected(settings.isMemoryProfilerDisabled()); - myUseNewBazelTestRunner.setSelected(settings.useNewBazelTestRunner(myProject)); myOrganizeImportsOnSaveCheckBox.setEnabled(myFormatCodeOnSaveCheckBox.isSelected()); } @@ -301,8 +281,7 @@ private void updateVersionTextIfCurrent(@NotNull FlutterSdk sdk, @NotNull String final FlutterSdk current = FlutterSdk.forPath(getSdkPathText()); if (current == null) { myVersionLabel.setText(""); - } - else { + } else { myVersionLabel.setText(value); } } diff --git a/src/io/flutter/sdk/FlutterWebCommand.java b/src/io/flutter/sdk/FlutterWebCommand.java new file mode 100644 index 0000000000..b8db7645ac --- /dev/null +++ b/src/io/flutter/sdk/FlutterWebCommand.java @@ -0,0 +1,40 @@ +/* + * 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.sdk; + +import com.intellij.execution.configurations.GeneralCommandLine; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.CharsetToolkit; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This subclasses FlutterCommand specifically to override the command line creation behavior for + * Flutter Web run commands. + */ +public class FlutterWebCommand extends FlutterCommand { + public FlutterWebCommand(@NotNull FlutterSdk sdk, @Nullable VirtualFile workDir, @NotNull FlutterCommand.Type type, String... args) { + super(sdk, workDir, type, args); + } + + @NotNull + public GeneralCommandLine createGeneralCommandLine(@Nullable Project project) { + final GeneralCommandLine line = new GeneralCommandLine(); + line.setCharset(CharsetToolkit.UTF8_CHARSET); + if (workDir != null) { + line.setWorkDirectory(workDir.getPath()); + } + line.setExePath(FileUtil.toSystemDependentName(sdk.getHomePath() + "/bin/" + FlutterSdkUtil.flutterScriptName())); + // flutter packages pub + line.addParameters(Type.PACKAGES_PUB.subCommand); + // TODO(devoncarew): We need to provision webdev locally. + line.addParameters("global", "run", "webdev", "daemon"); + line.addParameters(args); + return line; + } +} diff --git a/src/io/flutter/server/vmService/DartVmServiceBreakpointHandler.java b/src/io/flutter/server/vmService/DartVmServiceBreakpointHandler.java index da9506c3e3..679fca8856 100644 --- a/src/io/flutter/server/vmService/DartVmServiceBreakpointHandler.java +++ b/src/io/flutter/server/vmService/DartVmServiceBreakpointHandler.java @@ -66,7 +66,7 @@ public void vmBreakpointAdded(@NotNull final XLineBreakpoint xBreakpo private Set getVmBreakpoints(XLineBreakpoint xBreakpoint) { synchronized (myXBreakpointToVmBreakpointIdsMap) { - Set vmBreakpoints = myXBreakpointToVmBreakpointIdsMap.get(xBreakpoint); - if (vmBreakpoints == null) { - vmBreakpoints = new HashSet<>(); - myXBreakpointToVmBreakpointIdsMap.put(xBreakpoint, vmBreakpoints); - } - return vmBreakpoints; + return myXBreakpointToVmBreakpointIdsMap.computeIfAbsent(xBreakpoint, k -> new HashSet<>()); } } } diff --git a/src/io/flutter/server/vmService/DartVmServiceDebugProcess.java b/src/io/flutter/server/vmService/DartVmServiceDebugProcess.java index 2c25e7bdd4..1d1ca65c06 100644 --- a/src/io/flutter/server/vmService/DartVmServiceDebugProcess.java +++ b/src/io/flutter/server/vmService/DartVmServiceDebugProcess.java @@ -18,7 +18,6 @@ import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; import com.intellij.testFramework.LightVirtualFile; @@ -31,7 +30,6 @@ import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.frame.XSuspendContext; import com.jetbrains.lang.dart.ide.runner.DartConsoleFilter; -import com.jetbrains.lang.dart.ide.runner.ObservatoryConnector; import com.jetbrains.lang.dart.ide.runner.actions.DartPopFrameAction; import com.jetbrains.lang.dart.ide.runner.base.DartDebuggerEditorsProvider; import com.jetbrains.lang.dart.ide.runner.server.OpenDartObservatoryUrlAction; @@ -40,6 +38,7 @@ import gnu.trove.TIntObjectHashMap; import io.flutter.FlutterBundle; import io.flutter.FlutterUtils; +import io.flutter.ObservatoryConnector; import io.flutter.run.FlutterLaunchMode; import io.flutter.server.vmService.frame.DartVmServiceEvaluator; import io.flutter.server.vmService.frame.DartVmServiceStackFrame; @@ -66,23 +65,17 @@ public class DartVmServiceDebugProcess extends XDebugProcess { private static final Logger LOG = Logger.getInstance(DartVmServiceDebugProcess.class.getName()); - @Nullable private final ExecutionResult myExecutionResult; + @NotNull private final ExecutionResult myExecutionResult; @NotNull private final DartUrlResolver myDartUrlResolver; - @NotNull private final String myDebuggingHost; - private final int myObservatoryPort; @NotNull private final XBreakpointHandler[] myBreakpointHandlers; private final IsolatesInfo myIsolatesInfo; @NotNull private final Map> mySuspendedIsolateIds = Collections.synchronizedMap(new HashMap<>()); private final Map myScriptIdToContentMap = new THashMap<>(); - private final Map>> myScriptIdToLinesAndColumnsMap = - new THashMap<>(); - private final int myTimeout; + private final Map>> myScriptIdToLinesAndColumnsMap = new THashMap<>(); @Nullable private final VirtualFile myCurrentWorkingDirectory; @NotNull private final ObservatoryConnector myConnector; - @NotNull - private final ExecutionEnvironment executionEnvironment; - @NotNull - private final PositionMapper mapper; + @NotNull private final ExecutionEnvironment executionEnvironment; + @NotNull private final PositionMapper mapper; @Nullable protected String myRemoteProjectRootUri; private boolean myVmConnected = false; @NotNull private final OpenDartObservatoryUrlAction myOpenObservatoryAction = @@ -99,11 +92,8 @@ public DartVmServiceDebugProcess(@NotNull final ExecutionEnvironment executionEn @NotNull final PositionMapper mapper) { super(session); - myDebuggingHost = "localhost"; - myObservatoryPort = 0; myExecutionResult = executionResult; myDartUrlResolver = dartUrlResolver; - myTimeout = 0; myCurrentWorkingDirectory = null; myIsolatesInfo = new IsolatesInfo(); @@ -117,7 +107,7 @@ public DartVmServiceDebugProcess(@NotNull final ExecutionEnvironment executionEn @Override public void sessionPaused() { // This can be removed if XFramesView starts popping the project window to the top of the z-axis stack. - Project project = getSession().getProject(); + final Project project = getSession().getProject(); focusProject(project); stackFrameChanged(); } @@ -258,8 +248,8 @@ public void scheduleConnect() { return; } - // "Flutter run" has given us a websocket; we can assume it's ready immediately, - // because "flutter run" has already connected to it. + // "flutter run" has given us a websocket; we can assume it's ready immediately, because + // "flutter run" has already connected to it. final VmService vmService; final VmOpenSourceLocationListener vmOpenSourceLocationListener; try { @@ -268,8 +258,7 @@ public void scheduleConnect() { } catch (IOException | RuntimeException e) { onConnectFailed("Failed to connect to the VM observatory service at: " + url + "\n" - + e.toString() + "\n" + - formatStackTraces(e)); + + e.toString() + "\n" + formatStackTraces(e)); return; } onConnectSucceeded(vmService, vmOpenSourceLocationListener); @@ -290,12 +279,6 @@ private void connect(@NotNull final String url) throws IOException { myVmConnected = true; } - @NotNull - @Deprecated // returns incorrect URL for Dart SDK 1.22+ because returned URL doesn't contain auth token - private String getObservatoryUrl(@NotNull final String scheme, @Nullable final String path) { - return scheme + "://" + myDebuggingHost + ":" + myObservatoryPort + StringUtil.notNullize(path); - } - @Override protected ProcessHandler doGetProcessHandler() { return myExecutionResult == null ? super.doGetProcessHandler() : myExecutionResult.getProcessHandler(); @@ -391,7 +374,7 @@ public void runToPosition(@NotNull XSourcePosition position, @Nullable XSuspendC public void isolateSuspended(@NotNull final IsolateRef isolateRef) { final String id = isolateRef.getId(); - assert(!mySuspendedIsolateIds.containsKey(id)); + assert (!mySuspendedIsolateIds.containsKey(id)); if (!mySuspendedIsolateIds.containsKey(id)) { mySuspendedIsolateIds.put(id, new CompletableFuture<>()); } @@ -406,10 +389,10 @@ public CompletableFuture whenIsolateResumed(String isolateId) { if (future == null) { // Isolate wasn't actually suspended. return CompletableFuture.completedFuture(null); - } else { + } + else { return future; } - } public boolean isIsolateAlive(@NotNull final String isolateId) { diff --git a/src/io/flutter/server/vmService/VMServiceManager.java b/src/io/flutter/server/vmService/VMServiceManager.java index efe4097101..572c76e9de 100644 --- a/src/io/flutter/server/vmService/VMServiceManager.java +++ b/src/io/flutter/server/vmService/VMServiceManager.java @@ -126,8 +126,12 @@ public void addRegisteredExtensionRPCs(Isolate isolate, boolean attach) { // 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"); + bindingLibraryNames.add("package:flutter_web/src/widgets/binding.dart"); + final EvalOnDartLibrary flutterLibrary = new EvalOnDartLibrary( - "package:flutter/src/widgets/binding.dart", + bindingLibraryNames, vmService, this ); diff --git a/src/io/flutter/server/vmService/frame/DartVmServiceEvaluator.java b/src/io/flutter/server/vmService/frame/DartVmServiceEvaluator.java index 8bae910d16..82861b7247 100644 --- a/src/io/flutter/server/vmService/frame/DartVmServiceEvaluator.java +++ b/src/io/flutter/server/vmService/frame/DartVmServiceEvaluator.java @@ -2,7 +2,10 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.fileEditor.*; +import com.intellij.openapi.fileEditor.FileEditorLocation; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileEditor.TextEditorLocation; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; @@ -16,10 +19,11 @@ import com.intellij.xdebugger.evaluation.ExpressionInfo; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; import com.intellij.xdebugger.frame.XValue; -import io.flutter.server.vmService.DartVmServiceDebugProcess; import com.jetbrains.lang.dart.psi.*; import com.jetbrains.lang.dart.util.DartResolveUtil; import gnu.trove.THashSet; +import io.flutter.server.vmService.DartVmServiceDebugProcess; +import io.flutter.server.vmService.VmServiceWrapper; import org.dartlang.vm.service.consumer.GetObjectConsumer; import org.dartlang.vm.service.element.*; import org.jetbrains.annotations.NotNull; @@ -52,21 +56,27 @@ public void evaluate(@NotNull final String expression, final List libraryFiles = new ArrayList<>(); // Turn off pausing on exceptions as it is confusing to mouse over an expression // and to have that trigger pausing at an exception. - if (myDebugProcess == null || myDebugProcess.getVmServiceWrapper() == null) { + if (myDebugProcess.getVmServiceWrapper() == null) { callback.errorOccurred("Device disconnected"); return; } + final VmServiceWrapper vmService = myDebugProcess.getVmServiceWrapper(); + if (vmService == null) { + // Not connected to the VM yet. + callback.errorOccurred("No connection to the Dart VM"); + return; + } myDebugProcess.getVmServiceWrapper().setExceptionPauseMode(ExceptionPauseMode.None); final XEvaluationCallback wrappedCallback = new XEvaluationCallback() { @Override public void evaluated(@NotNull XValue result) { - myDebugProcess.getVmServiceWrapper().setExceptionPauseMode(myDebugProcess.getBreakOnExceptionMode()); + vmService.setExceptionPauseMode(myDebugProcess.getBreakOnExceptionMode()); callback.evaluated(result); } @Override public void errorOccurred(@NotNull String errorMessage) { - myDebugProcess.getVmServiceWrapper().setExceptionPauseMode(myDebugProcess.getBreakOnExceptionMode()); + vmService.setExceptionPauseMode(myDebugProcess.getBreakOnExceptionMode()); callback.errorOccurred(errorMessage); } }; @@ -87,7 +97,7 @@ public void errorOccurred(@NotNull String errorMessage) { if (virtualFile != null) { psiFile = PsiManager.getInstance(project).findFile(virtualFile); if (psiFile != null && fileEditorLocation instanceof TextEditorLocation) { - TextEditorLocation textEditorLocation = (TextEditorLocation)fileEditorLocation; + final TextEditorLocation textEditorLocation = (TextEditorLocation)fileEditorLocation; element = psiFile.findElementAt(textEditor.getEditor().logicalPositionToOffset(textEditorLocation.getPosition())); } } @@ -105,7 +115,7 @@ public void errorOccurred(@NotNull String errorMessage) { final DartClass dartClass = element != null ? PsiTreeUtil.getParentOfType(element, DartClass.class) : null; final String dartClassName = dartClass != null ? dartClass.getName() : null; - myDebugProcess.getVmServiceWrapper().getCachedIsolate(isolateId).whenComplete((isolate, error) -> { + vmService.getCachedIsolate(isolateId).whenComplete((isolate, error) -> { if (error != null) { wrappedCallback.errorOccurred(error.getMessage()); return; @@ -114,9 +124,9 @@ public void errorOccurred(@NotNull String errorMessage) { wrappedCallback.errorOccurred("No running isolate."); return; } - LibraryRef libraryRef = findMatchingLibrary(isolate, libraryFiles); + final LibraryRef libraryRef = findMatchingLibrary(isolate, libraryFiles); if (dartClassName != null) { - myDebugProcess.getVmServiceWrapper().getObject(isolateId, libraryRef.getId(), new GetObjectConsumer() { + vmService.getObject(isolateId, libraryRef.getId(), new GetObjectConsumer() { @Override public void onError(RPCError error) { @@ -125,16 +135,16 @@ public void onError(RPCError error) { @Override public void received(Obj response) { - Library library = (Library)response; + final Library library = (Library)response; for (ClassRef classRef : library.getClasses()) { if (classRef.getName().equals(dartClassName)) { - myDebugProcess.getVmServiceWrapper().evaluateInTargetContext(isolateId, classRef.getId(), expression, wrappedCallback); + vmService.evaluateInTargetContext(isolateId, classRef.getId(), expression, wrappedCallback); return; } } // Class not found so just use the library. - myDebugProcess.getVmServiceWrapper().evaluateInTargetContext(isolateId, libraryRef.getId(), expression, wrappedCallback); + vmService.evaluateInTargetContext(isolateId, libraryRef.getId(), expression, wrappedCallback); } @Override @@ -151,7 +161,7 @@ public void received(Sentinel response) { private LibraryRef findMatchingLibrary(Isolate isolate, List libraryFiles) { if (libraryFiles != null && !libraryFiles.isEmpty()) { - Set uris = new THashSet<>(); + final Set uris = new THashSet<>(); for (VirtualFile libraryFile : libraryFiles) { uris.addAll(myDebugProcess.getUrisForFile(libraryFile)); @@ -241,14 +251,14 @@ public static ExpressionInfo getExpressionInfo(@NotNull final PsiElement context if (reference != null) { TextRange textRange = reference.getTextRange(); // notes.text - the whole reference expression is notes.txt, but we must return only notes - int endOffset = contextElement.getTextRange().getEndOffset(); + final int endOffset = contextElement.getTextRange().getEndOffset(); if (textRange.getEndOffset() != endOffset) { textRange = new TextRange(textRange.getStartOffset(), endOffset); } return new ExpressionInfo(textRange); } - PsiElement parent = contextElement.getParent(); + final PsiElement parent = contextElement.getParent(); return parent instanceof DartId ? new ExpressionInfo(parent.getTextRange()) : null; } } diff --git a/src/io/flutter/server/vmService/frame/DartVmServiceValue.java b/src/io/flutter/server/vmService/frame/DartVmServiceValue.java index 3cbf54f9c1..4f4599e3e1 100644 --- a/src/io/flutter/server/vmService/frame/DartVmServiceValue.java +++ b/src/io/flutter/server/vmService/frame/DartVmServiceValue.java @@ -149,10 +149,45 @@ public void computePresentation(@NotNull final XValueNode node, @NotNull final X if (computeRegExpPresentation(node)) return; if (computeMapPresentation(node)) return; if (computeListPresentation(node)) return; - computeDefaultPresentation(node); + + // computeDefaultPresentation is called internally here when no result is received. + // The reason for this is that the async method used cannot be properly waited. + computeDebugPresentation(node); + // todo handle other special kinds: Type, TypeParameter, Pattern, may be some others as well } + private void computeDebugPresentation(final XValueNode node) { + myDebugProcess.getVmServiceWrapper() + .evaluateInTargetContext(myIsolateId, myInstanceRef.getId(), "toStringDeep()", new VmServiceConsumers.EvaluateConsumerWrapper() { + @Override + public void received(final InstanceRef toStringInstanceRef) { + // TODO: Get actual DiagnosticsNode object instead of calling toStringDeep here. + if (toStringInstanceRef.getKind() == InstanceKind.String) { + String content = toStringInstanceRef.getValueAsString(); + int firstLineBreak = content.indexOf('\n'); + String summary = firstLineBreak < 0 ? content : content.substring(0, firstLineBreak); + node.setPresentation(getIcon(), myInstanceRef.getClassRef().getName(), summary, true); + if (toStringInstanceRef.getValueAsStringIsTruncated()) { + addFullStringValueEvaluator(node, toStringInstanceRef); + } + else if (firstLineBreak >= 0) { + // Multi-line content. Display a View link to reveal full content. + node.setFullValueEvaluator(new ImmediateFullValueEvaluator("...View", content)); + } + } + else { + noGoodResult(); + } + } + + @Override + public void noGoodResult() { + computeDefaultPresentation(node); + } + }); + } + private Icon getIcon() { if (myIsException) return AllIcons.Debugger.Db_exception_breakpoint; diff --git a/src/io/flutter/settings/FlutterSettings.java b/src/io/flutter/settings/FlutterSettings.java index 61681e10f5..b4f6bea68a 100644 --- a/src/io/flutter/settings/FlutterSettings.java +++ b/src/io/flutter/settings/FlutterSettings.java @@ -91,9 +91,6 @@ public void sendSettingsToAnalytics(Analytics analytics) { if (useFlutterLogView()) { analytics.sendEvent("settings", afterLastPeriod(useFlutterLogView)); } - if (shouldUseNewBazelTestRunner()) { - analytics.sendEvent("settings", afterLastPeriod(newBazelTestRunnerKey)); - } } public void addListener(Listener listener) { @@ -245,36 +242,6 @@ public void setMemoryProfilerDisabled(boolean value) { fireEvent(); } - /** - * Whether to use the new bazel-test script instead of the old bazel-run script to run tests. - * - * Defaults to false. - */ - public boolean useNewBazelTestRunner(Project project) { - // Check that the new test runner is available. - final Workspace workspace = Workspace.load(project); - // If the workspace can't be found, we'll return false. This normally happens during tests. Test code that covers this setting - // has an override for this setting built-in. - if (workspace == null) { - return false; - } - @Nullable String testScript = workspace.getTestScript(); - if (testScript == null) { - // The test script was not found, so it can't be used. - return false; - } - return shouldUseNewBazelTestRunner(); - } - - private boolean shouldUseNewBazelTestRunner() { - return getPropertiesComponent().getBoolean(newBazelTestRunnerKey, false); - } - - public void setUseNewBazelTestRunner(boolean value) { - getPropertiesComponent().setValue(newBazelTestRunnerKey, value, false); - fireEvent(); - } - protected void fireEvent() { dispatcher.getMulticaster().settingsChanged(); } diff --git a/src/io/flutter/template/DartToplevelTemplateContextType.java b/src/io/flutter/template/DartToplevelTemplateContextType.java index a05a82d139..4fa071d522 100644 --- a/src/io/flutter/template/DartToplevelTemplateContextType.java +++ b/src/io/flutter/template/DartToplevelTemplateContextType.java @@ -19,7 +19,6 @@ public DartToplevelTemplateContextType() { @Override protected boolean isInContext(@NotNull PsiElement element) { - //noinspection unchecked return PsiTreeUtil.getNonStrictParentOfType(element, DartClassDefinition.class, PsiComment.class) == null; } } diff --git a/src/io/flutter/test/DartTestEventsConverterZ.java b/src/io/flutter/test/DartTestEventsConverterZ.java index d259baf6c4..adce7f8204 100644 --- a/src/io/flutter/test/DartTestEventsConverterZ.java +++ b/src/io/flutter/test/DartTestEventsConverterZ.java @@ -1,5 +1,4 @@ -package com.jetbrains.lang.dart.ide.runner.test; - +package io.flutter.test; import com.google.gson.*; import com.intellij.execution.testframework.TestConsoleProperties; @@ -725,7 +724,7 @@ public String getUrl() { } public String toString() { - return getClass().getSimpleName() + "(" + String.valueOf(myId) + "," + String.valueOf(myName) + ")"; + return getClass().getSimpleName() + "(" + myId + "," + myName + ")"; } } @@ -781,7 +780,7 @@ public void testDone() { } protected static class Group extends Item { - private int myTestCount = 0; + private int myTestCount; private int myDoneTestsCount = 0; static Group from(JsonObject obj, Map groups, Map suites) { diff --git a/src/io/flutter/util/DartTestLocationProviderZ.java b/src/io/flutter/util/DartTestLocationProviderZ.java index 017412bf1b..06a4255a79 100644 --- a/src/io/flutter/util/DartTestLocationProviderZ.java +++ b/src/io/flutter/util/DartTestLocationProviderZ.java @@ -1,4 +1,4 @@ -package com.jetbrains.lang.dart.ide.runner.util; +package io.flutter.util; import com.google.common.annotations.VisibleForTesting; import com.google.gson.Gson; @@ -19,6 +19,7 @@ import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.lang.dart.ide.runner.util.TestUtil; import com.jetbrains.lang.dart.psi.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; diff --git a/src/io/flutter/utils/FlutterModuleUtils.java b/src/io/flutter/utils/FlutterModuleUtils.java index 283a8ddd1e..1b896144a2 100644 --- a/src/io/flutter/utils/FlutterModuleUtils.java +++ b/src/io/flutter/utils/FlutterModuleUtils.java @@ -68,7 +68,7 @@ public static ModuleType getFlutterModuleType() { * */ public static boolean isFlutterModule(@Nullable final Module module) { - if (module == null) return false; + if (module == null || module.isDisposed()) return false; if (PlatformUtils.isIntelliJ() || FlutterUtils.isAndroidStudio()) { // [Flutter support enabled for a module] === @@ -84,6 +84,8 @@ public static boolean isFlutterModule(@Nullable final Module module) { } public static boolean hasFlutterModule(@NotNull Project project) { + if (project.isDisposed()) return false; + return CollectionUtils.anyMatch(getModules(project), FlutterModuleUtils::isFlutterModule); } @@ -97,7 +99,7 @@ public static boolean isInFlutterModule(@NotNull PsiElement element) { */ @Nullable public static Workspace getFlutterBazelWorkspace(@Nullable Project project) { - if (project == null) return null; + if (project == null || project.isDisposed()) return null; final Workspace workspace = WorkspaceCache.getInstance(project).getNow(); if (workspace == null) return null; @@ -121,6 +123,8 @@ public static boolean isFlutterBazelProject(@Nullable Project project) { @Nullable public static VirtualFile findXcodeProjectFile(@NotNull Project project) { + if (project.isDisposed()) return null; + // Look for XCode metadata file in `ios/`. for (PubRoot root : PubRoots.forProject(project)) { final VirtualFile dir = root.getiOsDir(); @@ -169,6 +173,9 @@ private static VirtualFile findPreferedXcodeMetadataFile(@Nullable VirtualFile i @NotNull public static Module[] getModules(@NotNull Project project) { + // A disposed project has no modules. + if (project.isDisposed()) return new Module[]{}; + return ModuleManager.getInstance(project).getModules(); } @@ -176,9 +183,7 @@ public static Module[] getModules(@NotNull Project project) { * Check if any module in this project {@link #declaresFlutter(Module)}. */ public static boolean declaresFlutter(@NotNull Project project) { - if (project.isDisposed()) { - return false; - } + if (project.isDisposed()) return false; return CollectionUtils.anyMatch(getModules(project), FlutterModuleUtils::declaresFlutter); } @@ -186,6 +191,7 @@ public static boolean declaresFlutter(@NotNull Project project) { * Ensures a Flutter run configuration is selected in the run pull down. */ public static void ensureRunConfigSelected(@NotNull Project project) { + if (project.isDisposed()) return; final FlutterRunConfigurationType configType = FlutterRunConfigurationType.getInstance(); final RunManager runManager = RunManager.getInstance(project); @@ -204,6 +210,7 @@ public static void ensureRunConfigSelected(@NotNull Project project) { */ public static void autoCreateRunConfig(@NotNull Project project, @NotNull PubRoot root) { assert ApplicationManager.getApplication().isReadAccessAllowed(); + if (project.isDisposed()) return; VirtualFile main = root.getLibMain(); if (main == null || !main.exists()) { @@ -241,6 +248,8 @@ public static void autoCreateRunConfig(@NotNull Project project, @NotNull PubRoo * If no files are open, show lib/main.dart for the given PubRoot. */ public static void autoShowMain(@NotNull Project project, @NotNull PubRoot root) { + if (project.isDisposed()) return; + final VirtualFile main = root.getFileToOpen(); if (main == null) return; @@ -312,6 +321,8 @@ public static void setFlutterModuleType(@NotNull Module module) { } public static void setFlutterModuleAndReload(@NotNull Module module, @NotNull Project project) { + if (project.isDisposed()) return; + setFlutterModuleType(module); enableDartSDK(module); project.save(); diff --git a/src/io/flutter/view/FlutterPerfView.java b/src/io/flutter/view/FlutterPerfView.java index a1e15688b7..ac0a32a23e 100644 --- a/src/io/flutter/view/FlutterPerfView.java +++ b/src/io/flutter/view/FlutterPerfView.java @@ -52,6 +52,8 @@ public class FlutterPerfView implements Disposable { private static final Logger LOG = Logger.getInstance(FlutterPerfView.class); + private boolean previouslyVisible = false; + @NotNull private final Project myProject; @@ -85,9 +87,18 @@ private void updateForEmptyContent(ToolWindow toolWindow) { } } + private void updateToolWindowVisibility(ToolWindow toolWindow) { + if (toolWindow.isVisible()) { + return; + } + + if (previouslyVisible) { + toolWindow.show(null); + } + } + void debugActive(@NotNull FlutterViewMessages.FlutterDebugEvent event) { final FlutterApp app = event.app; - final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); if (!(toolWindowManager instanceof ToolWindowManagerEx)) { return; @@ -98,6 +109,15 @@ void debugActive(@NotNull FlutterViewMessages.FlutterDebugEvent event) { return; } + if (toolWindow.isAvailable()) { + updateToolWindowVisibility(toolWindow); + } + else { + toolWindow.setAvailable(true, () -> { + updateToolWindowVisibility(toolWindow); + }); + } + addPerformanceViewContent(app, toolWindow); app.getVmService().addVmServiceListener(new VmServiceListenerAdapter() { @@ -124,6 +144,12 @@ public void connectionClosed() { if (perAppViewState.isEmpty()) { // No more applications are running. updateForEmptyContent(toolWindow); + if (toolWindow.isAvailable()) { + // Store whether the tool window was visible before we decided to close it + // because it had no content. + previouslyVisible = toolWindow.isVisible(); + toolWindow.setAvailable(false, null); + } } }); } @@ -137,13 +163,21 @@ private void addPerformanceViewContent(FlutterApp app, ToolWindow toolWindow) { final JBRunnerTabs runnerTabs = new JBRunnerTabs(myProject, ActionManager.getInstance(), null, this); runnerTabs.setSelectionChangeHandler(this::onTabSelectionChange); - final List existingDevices = new ArrayList<>(); - for (FlutterApp otherApp : perAppViewState.keySet()) { - existingDevices.add(otherApp.device()); + final String tabName; + final FlutterDevice device = app.device(); + if (device == null) { + tabName = app.getProject().getName(); + } + else { + final List existingDevices = new ArrayList<>(); + for (FlutterApp otherApp : perAppViewState.keySet()) { + existingDevices.add(otherApp.device()); + } + tabName = device.getUniqueName(existingDevices); } final JPanel tabContainer = new JPanel(new BorderLayout()); - final Content content = contentManager.getFactory().createContent(null, app.device().getUniqueName(existingDevices), false); + final Content content = contentManager.getFactory().createContent(null, tabName, false); tabContainer.add(runnerTabs.getComponent(), BorderLayout.CENTER); content.setComponent(tabContainer); content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); @@ -312,6 +346,7 @@ private static class PerfViewAppState { @Nullable Content content; @Nullable Disposable disposable; JBRunnerTabs tabs; + // TODO(devoncarew): We never query flutterViewActions. ArrayList flutterViewActions = new ArrayList<>(); } } diff --git a/src/io/flutter/view/FlutterPerfViewFactory.java b/src/io/flutter/view/FlutterPerfViewFactory.java index 1b9baf4486..63bf896a05 100644 --- a/src/io/flutter/view/FlutterPerfViewFactory.java +++ b/src/io/flutter/view/FlutterPerfViewFactory.java @@ -15,6 +15,11 @@ import org.jetbrains.annotations.NotNull; public class FlutterPerfViewFactory implements ToolWindowFactory, DumbAware { + @Override + public void init(ToolWindow window) { + window.setAvailable(false, null); + } + public static void init(@NotNull Project project) { project.getMessageBus().connect().subscribe( FlutterViewMessages.FLUTTER_DEBUG_TOPIC, (event) -> initPerfView(project, event) diff --git a/src/io/flutter/view/FlutterView.java b/src/io/flutter/view/FlutterView.java index 1439e4aead..a4e471d77f 100644 --- a/src/io/flutter/view/FlutterView.java +++ b/src/io/flutter/view/FlutterView.java @@ -13,8 +13,6 @@ import com.intellij.ide.plugins.PluginManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; -import com.intellij.openapi.actionSystem.ex.CustomComponentAction; -import com.intellij.openapi.actionSystem.impl.ActionButton; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; @@ -48,6 +46,7 @@ import io.flutter.FlutterBundle; import io.flutter.FlutterInitializer; import io.flutter.FlutterUtils; +import io.flutter.devtools.DevToolsManager; import io.flutter.inspector.InspectorService; import io.flutter.run.daemon.FlutterApp; import io.flutter.run.daemon.FlutterDevice; @@ -57,7 +56,6 @@ import io.flutter.settings.FlutterSettings; import io.flutter.utils.AsyncUtils; import io.flutter.utils.EventStream; -import io.flutter.utils.UIUtils; import io.flutter.utils.VmServiceListenerAdapter; import org.dartlang.vm.service.VmService; import org.dartlang.vm.service.element.Event; @@ -101,6 +99,8 @@ private static class PerAppState { new EventStream<>(FlutterViewState.HIGHLIGHT_NODES_SHOWN_IN_BOTH_TREES_DEFAULT); + private boolean previouslyVisible = false; + @NotNull private final FlutterViewState state = new FlutterViewState(); @@ -216,12 +216,22 @@ private void addInspectorViewContent(FlutterApp app, @Nullable InspectorService final SimpleToolWindowPanel toolWindowPanel = new SimpleToolWindowPanel(true); final JBRunnerTabs runnerTabs = new JBRunnerTabs(myProject, ActionManager.getInstance(), null, this); runnerTabs.setSelectionChangeHandler(this::onTabSelectionChange); - final List existingDevices = new ArrayList<>(); - for (FlutterApp otherApp : perAppViewState.keySet()) { - existingDevices.add(otherApp.device()); - } final JPanel tabContainer = new JPanel(new BorderLayout()); - final Content content = contentManager.getFactory().createContent(null, app.device().getUniqueName(existingDevices), false); + + final String tabName; + final FlutterDevice device = app.device(); + if (device == null) { + tabName = app.getProject().getName(); + } + else { + final List existingDevices = new ArrayList<>(); + for (FlutterApp otherApp : perAppViewState.keySet()) { + existingDevices.add(otherApp.device()); + } + tabName = device.getUniqueName(existingDevices); + } + + final Content content = contentManager.getFactory().createContent(null, tabName, false); tabContainer.add(runnerTabs.getComponent(), BorderLayout.CENTER); content.setComponent(tabContainer); content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE); @@ -395,16 +405,13 @@ public void debugActive(@NotNull FlutterViewMessages.FlutterDebugEvent event) { FlutterUtils.warn(LOG, throwable); return; } + debugActiveHelper(app, inspectorService); }); } } private void debugActiveHelper(FlutterApp app, @Nullable InspectorService inspectorService) { - if (FlutterSettings.getInstance().isOpenInspectorOnAppLaunch()) { - activateToolWindow(); - } - final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); if (!(toolWindowManager instanceof ToolWindowManagerEx)) { return; @@ -415,6 +422,15 @@ private void debugActiveHelper(FlutterApp app, @Nullable InspectorService inspec return; } + if (toolWindow.isAvailable()) { + updateToolWindowVisibility(toolWindow); + } + else { + toolWindow.setAvailable(true, () -> { + updateToolWindowVisibility(toolWindow); + }); + } + if (emptyContent != null) { final ContentManager contentManager = toolWindow.getContentManager(); contentManager.removeContent(emptyContent, true); @@ -455,6 +471,12 @@ public void connectionClosed() { if (perAppViewState.isEmpty()) { // No more applications are running. updateForEmptyContent(toolWindow); + if (toolWindow.isAvailable()) { + // Store whether the tool window was visible before we decided to close it + // because it had no content. + previouslyVisible = toolWindow.isVisible(); + toolWindow.setAvailable(false, null); + } } }); } @@ -576,21 +598,40 @@ private void onAppChanged(FlutterApp app) { } } - /** - * Activate the tool window. - */ - private void activateToolWindow() { - final ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject); - if (!(toolWindowManager instanceof ToolWindowManagerEx)) { + private void updateToolWindowVisibility(ToolWindow flutterToolWindow) { + if (flutterToolWindow.isVisible()) { return; } - final ToolWindow flutterToolWindow = toolWindowManager.getToolWindow(FlutterView.TOOL_WINDOW_ID); - if (flutterToolWindow.isVisible()) { - return; + if (FlutterSettings.getInstance().isOpenInspectorOnAppLaunch() || previouslyVisible) { + flutterToolWindow.show(null); } + } +} + +class FlutterViewDevToolsAction extends FlutterViewAction { + FlutterViewDevToolsAction(@NotNull FlutterApp app) { + super(app, "Open DevTools", "Open Dart DevTools", FlutterIcons.Dart_16); + } - flutterToolWindow.show(null); + @Override + public void perform(AnActionEvent event) { + if (app.isSessionActive()) { + final String urlString = app.getConnector().getBrowserUrl(); + if (urlString == null) { + return; + } + + final DevToolsManager devToolsManager = DevToolsManager.getInstance(app.getProject()); + + if (devToolsManager.hasInstalledDevTools()) { + devToolsManager.openBrowserAndConnect(urlString); + } + else { + final CompletableFuture result = devToolsManager.installDevTools(); + result.thenAccept(o -> devToolsManager.openBrowserAndConnect(urlString)); + } + } } } @@ -635,7 +676,7 @@ class RepaintRainbowAction extends FlutterViewToggleableAction { class ToggleInspectModeAction extends FlutterViewToggleableAction { ToggleInspectModeAction(@NotNull FlutterApp app) { - super(app, AllIcons.General.LocateHover, ServiceExtensions.toggleSelectWidgetMode); + super(app, AllIcons.General.Locate, ServiceExtensions.toggleSelectWidgetMode); } @Override @@ -643,8 +684,7 @@ protected void perform(AnActionEvent event) { super.perform(event); if (app.isSessionActive()) { - // If toggling inspect mode on, bring all devices to the foreground. - // TODO(jacobr): consider only bringing the device for the currently open inspector TAB. + // If toggling inspect mode on, bring the app's device to the foreground. if (isSelected()) { final FlutterDevice device = app.device(); if (device != null) { @@ -747,59 +787,11 @@ private static DefaultActionGroup createPopupActionGroup(FlutterView view, Flutt group.add(view.registerAction(new AutoHorizontalScrollAction(app, view.shouldAutoHorizontalScroll))); group.add(view.registerAction(new HighlightNodesShownInBothTrees(app, view.highlightNodesShownInBothTrees))); group.addSeparator(); + group.add(view.registerAction(new FlutterViewDevToolsAction(app))); + group.addSeparator(); group.add(view.registerAction(new OpenTimelineViewAction(app))); group.add(view.registerAction(new OpenObservatoryAction(app))); return group; } } - -class ObservatoryActionGroup extends AnAction implements CustomComponentAction { - private final @NotNull FlutterApp app; - private final DefaultActionGroup myActionGroup; - - public ObservatoryActionGroup(@NotNull FlutterView view, @NotNull FlutterApp app) { - super("Observatory actions", null, FlutterIcons.OpenObservatoryGroup); - - this.app = app; - - myActionGroup = createPopupActionGroup(view, app); - } - - @Override - public final void update(AnActionEvent e) { - e.getPresentation().setEnabled(app.isSessionActive()); - } - - @Override - public void actionPerformed(@NotNull AnActionEvent e) { - final JComponent button = UIUtils.getComponentOfActionEvent(e); - if (button == null) { - return; - } - final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu( - ActionPlaces.UNKNOWN, - myActionGroup); - popupMenu.getComponent().show(button, button.getWidth(), 0); - } - - @NotNull - @Override - public JComponent createCustomComponent(@NotNull Presentation presentation) { - final ActionButton button = new ActionButton( - this, - presentation, - ActionPlaces.UNKNOWN, - ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE - ); - presentation.putClientProperty("button", button); - return button; - } - - private static DefaultActionGroup createPopupActionGroup(FlutterView view, FlutterApp app) { - final DefaultActionGroup group = new DefaultActionGroup(); - group.add(view.registerAction(new OpenTimelineViewAction(app))); - group.add(view.registerAction(new OpenObservatoryAction(app))); - return group; - } -} diff --git a/src/io/flutter/view/FlutterViewAction.java b/src/io/flutter/view/FlutterViewAction.java index 6fbbae7f39..be7f7822f9 100644 --- a/src/io/flutter/view/FlutterViewAction.java +++ b/src/io/flutter/view/FlutterViewAction.java @@ -30,7 +30,7 @@ abstract class FlutterViewAction extends DumbAwareAction { } @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { FlutterInitializer.sendAnalyticsAction(this); perform(e); } diff --git a/src/io/flutter/view/FlutterViewFactory.java b/src/io/flutter/view/FlutterViewFactory.java index 033f96dafa..3c078aef2d 100644 --- a/src/io/flutter/view/FlutterViewFactory.java +++ b/src/io/flutter/view/FlutterViewFactory.java @@ -21,6 +21,11 @@ public static void init(@NotNull Project project) { ); } + @Override + public void init(ToolWindow window) { + window.setAvailable(false, null); + } + private static void initFlutterView(@NotNull Project project, FlutterViewMessages.FlutterDebugEvent event) { ApplicationManager.getApplication().invokeLater(() -> { final FlutterView flutterView = ServiceManager.getService(project, FlutterView.class); diff --git a/src/io/flutter/view/FlutterViewLocalToggleableAction.java b/src/io/flutter/view/FlutterViewLocalToggleableAction.java index 35d08d5416..816ed2f2c1 100644 --- a/src/io/flutter/view/FlutterViewLocalToggleableAction.java +++ b/src/io/flutter/view/FlutterViewLocalToggleableAction.java @@ -71,7 +71,7 @@ public void dispose() { } @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { this.setSelected(event, !isSelected()); final Presentation presentation = event.getPresentation(); presentation.putClientProperty("selected", isSelected()); diff --git a/src/io/flutter/view/FlutterViewToggleableAction.java b/src/io/flutter/view/FlutterViewToggleableAction.java index 7e9e24b27b..277f534243 100644 --- a/src/io/flutter/view/FlutterViewToggleableAction.java +++ b/src/io/flutter/view/FlutterViewToggleableAction.java @@ -77,7 +77,7 @@ protected void perform(AnActionEvent event) { } @Override - public void actionPerformed(AnActionEvent event) { + public void actionPerformed(@NotNull AnActionEvent event) { this.setSelected(event, !isSelected()); super.actionPerformed(event); } diff --git a/src/io/flutter/view/HighlightedTable.java b/src/io/flutter/view/HighlightedTable.java index 43095feb90..c7473cf6d5 100644 --- a/src/io/flutter/view/HighlightedTable.java +++ b/src/io/flutter/view/HighlightedTable.java @@ -27,7 +27,8 @@ public class HighlightedTable extends JBTable { public HighlightedTable(TableModel model) { super(model); - RollOverListener listener = new RollOverListener(); + + final RollOverListener listener = new RollOverListener(); addMouseMotionListener(listener); addMouseListener(listener); } @@ -69,7 +70,7 @@ public void mouseExited(MouseEvent e) { @Override public void mouseMoved(MouseEvent e) { int row = rowAtPoint(e.getPoint()); - if( row != rollOverRowIndex ) { + if (row != rollOverRowIndex) { rollOverRowIndex = row; repaint(); } diff --git a/src/io/flutter/view/InspectorMemoryTab.java b/src/io/flutter/view/InspectorMemoryTab.java index 6a1be38531..c3c61a01b0 100644 --- a/src/io/flutter/view/InspectorMemoryTab.java +++ b/src/io/flutter/view/InspectorMemoryTab.java @@ -45,26 +45,26 @@ public class InspectorMemoryTab extends JPanel implements InspectorTabPanel { // new FlutterStudioProfilersView(fsp); // add(view.getComponent(), BorderLayout.CENTER); - Class flutterStudioProfilers_class = Class.forName(CLASS_FlutterStudioProfilers); + final Class flutterStudioProfilers_class = Class.forName(CLASS_FlutterStudioProfilers); - Constructor flutterStudioProfilers_constructor = + @SuppressWarnings("unchecked") final Constructor flutterStudioProfilers_constructor = flutterStudioProfilers_class.getConstructor(Disposable.class, FlutterApp.class); - Object flutterStudioProfilers_instance = + final Object flutterStudioProfilers_instance = flutterStudioProfilers_constructor.newInstance(parentDisposable, app); - Class flutterStudioProfilersView_class = + final Class flutterStudioProfilersView_class = Class.forName(CLASS_FlutterStudioProfilersView); - Constructor flutterStudioProfilersView_constructor = + @SuppressWarnings("unchecked") final Constructor flutterStudioProfilersView_constructor = flutterStudioProfilersView_class.getConstructor(flutterStudioProfilers_class); - Object flutterStudioProfilersView_instance = + final Object flutterStudioProfilersView_instance = flutterStudioProfilersView_constructor.newInstance(flutterStudioProfilers_instance); - Class noArguments[] = new Class[]{}; - Method getComponentMethod = + final Class[] noArguments = new Class[]{ }; + @SuppressWarnings("unchecked") final Method getComponentMethod = flutterStudioProfilersView_class.getMethod("getComponent", noArguments); // call getComponent() - Component component = + @SuppressWarnings("JavaReflectionInvocation") final Component component = (Component)getComponentMethod.invoke(flutterStudioProfilersView_instance, (Object[])noArguments); final JPanel labels = new JPanel(new BorderLayout(6, 0)); diff --git a/src/io/flutter/view/InspectorPanel.java b/src/io/flutter/view/InspectorPanel.java index 8c6f63fa90..196720266a 100644 --- a/src/io/flutter/view/InspectorPanel.java +++ b/src/io/flutter/view/InspectorPanel.java @@ -5,6 +5,7 @@ */ package io.flutter.view; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; @@ -293,6 +294,16 @@ public void componentHidden(ComponentEvent e) { }, true); } + @VisibleForTesting + public boolean isDetailsSubtree() { + return detailsSubtree; + } + + @VisibleForTesting + public boolean isSummaryTree() { + return isSummaryTree; + } + public boolean isHighlightNodesShownInBothTrees() { return highlightNodesShownInBothTrees; } @@ -546,7 +557,10 @@ void maybeLoadUI() { if (flutterAppFrameReady) { // We need to start by quering the inspector service to find out the // current state of the UI. - updateSelectionFromService(); + AsyncUtils.whenCompleteUiThread(inspectorService.inferPubRootDirectoryIfNeeded(), (String directory, Throwable throwable) -> { + // Ignore exceptions, we still want to show the Inspector View. + updateSelectionFromService(); + }); } else { AsyncUtils.whenCompleteUiThread(inspectorService.isWidgetTreeReady(), (Boolean ready, Throwable throwable) -> { @@ -1047,7 +1061,7 @@ else if (parentTree != null) { } private void selectionChanged(TreeSelectionEvent event) { - if (visibleToUser == false) { + if (!visibleToUser) { return; } @@ -1067,7 +1081,7 @@ private void selectionChanged(TreeSelectionEvent event) { final boolean maybeReroot = isSummaryTree && subtreePanel != null && selectedDiagnostic != null && !subtreePanel.hasDiagnosticsValue(selectedDiagnostic.getValueRef()); syncSelectionHelper(maybeReroot, null); - if (maybeReroot == false) { + if (!maybeReroot) { if (isSummaryTree && subtreePanel != null) { subtreePanel.selectAndShowNode(selectedDiagnostic); } diff --git a/src/io/flutter/view/InspectorTreeUI.java b/src/io/flutter/view/InspectorTreeUI.java index 9d8f836b45..09327c71c0 100644 --- a/src/io/flutter/view/InspectorTreeUI.java +++ b/src/io/flutter/view/InspectorTreeUI.java @@ -23,7 +23,6 @@ import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.registry.Registry; -import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.MouseEventAdapter; @@ -31,7 +30,6 @@ import icons.FlutterIcons; import io.flutter.inspector.DiagnosticsNode; import io.flutter.inspector.DiagnosticsTreeStyle; -import io.flutter.inspector.InspectorTree; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -58,7 +56,8 @@ public class InspectorTreeUI extends BasicTreeUI { private static final Border LIST_BACKGROUND_PAINTER = UIManager.getBorder("List.sourceListBackgroundPainter"); private static final Border LIST_SELECTION_BACKGROUND_PAINTER = UIManager.getBorder("List.sourceListSelectionBackgroundPainter"); - private static final Border LIST_FOCUSED_SELECTION_BACKGROUND_PAINTER = UIManager.getBorder("List.sourceListFocusedSelectionBackgroundPainter"); + private static final Border LIST_FOCUSED_SELECTION_BACKGROUND_PAINTER = + UIManager.getBorder("List.sourceListFocusedSelectionBackgroundPainter"); @NotNull private final Condition myWideSelectionCondition; private boolean myWideSelection; @@ -67,8 +66,6 @@ public class InspectorTreeUI extends BasicTreeUI { boolean leftToRight = true; /// TODO(jacobr): actually support RTL mode. - static final JBColor SUBTREE_BOUNDS_COLOR = new JBColor(Color.WHITE, Gray._43); - @SuppressWarnings("unchecked") public InspectorTreeUI() { this(false, Conditions.alwaysFalse()); @@ -77,9 +74,9 @@ public InspectorTreeUI() { /** * Creates new {@code InspectorTreeUI} object. * - * @param wideSelection flag that determines if wide selection should be used - * @param wideSelectionCondition strategy that determine if wide selection should be used for a target row (it's zero-based index - * is given to the condition as an argument) + * @param wideSelection flag that determines if wide selection should be used + * @param wideSelectionCondition strategy that determine if wide selection should be used for a target row (it's zero-based index + * is given to the condition as an argument) */ public InspectorTreeUI(final boolean wideSelection, @NotNull Condition wideSelectionCondition) { myWideSelection = wideSelection; @@ -174,15 +171,15 @@ protected void installKeyboardActions() { public void actionPerformed(ActionEvent e) { final Object source = e.getSource(); if (source instanceof JTree) { - JTree tree = (JTree)source; - int selectionRow = tree.getLeadSelectionRow(); + final JTree tree = (JTree)source; + final int selectionRow = tree.getLeadSelectionRow(); if (selectionRow != -1) { - TreePath selectionPath = tree.getPathForRow(selectionRow); + final TreePath selectionPath = tree.getPathForRow(selectionRow); if (selectionPath != null) { - boolean leaf = tree.getModel().isLeaf(selectionPath.getLastPathComponent()); + final boolean leaf = tree.getModel().isLeaf(selectionPath.getLastPathComponent()); int toSelect = -1; int toScroll = -1; - if ((!leaf && tree.isExpanded(selectionRow)) || leaf) { + if (leaf || tree.isExpanded(selectionRow)) { if (selectionRow + 1 < tree.getRowCount()) { toSelect = selectionRow + 1; toScroll = toSelect; @@ -247,7 +244,8 @@ protected int getRowX(int row, int depth) { if (isCustomIndent()) { final int off = tree.isRootVisible() ? 8 : 0; return 8 * depth + 8 + off; - } else { + } + else { return super.getRowX(row, depth); } } @@ -270,7 +268,7 @@ protected void paintHorizontalPartOfLeg(final Graphics g, if (path.getPathCount() >= 2) { final Object treeNode = path.getPathComponent(path.getPathCount() - 2); if (treeNode instanceof DefaultMutableTreeNode) { - DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeNode; + DefaultMutableTreeNode node = (DefaultMutableTreeNode)treeNode; if (node.getChildCount() < 2) { return; } @@ -294,8 +292,8 @@ protected void paintHorizontalPartOfLeg(final Graphics g, } final int depth = path.getPathCount() - 1; - if((depth == 0 || (depth == 1 && !isRootVisible())) && - !getShowsRootHandles()) { + if ((depth == 0 || (depth == 1 && !isRootVisible())) && + !getShowsRootHandles()) { return; } @@ -307,14 +305,15 @@ protected void paintHorizontalPartOfLeg(final Graphics g, int nodeX = bounds.x - getHorizontalLegBuffer(); leftX = getRowX(row, depth - 1) - getRightChildIndent() + insets.left; - nodeX = isLeaf ? getRowX(row, depth) - leafChildLineInset: + nodeX = isLeaf ? getRowX(row, depth) - leafChildLineInset : getRowX(row, depth - 1); nodeX += insets.left; - if(clipBounds.intersects(leftX, lineY, nodeX - leftX, 1)) { + if (clipBounds.intersects(leftX, lineY, nodeX - leftX, 1)) { g.setColor(JBColor.GRAY); if (dashed) { drawDashedHorizontalLine(g, lineY, leftX, nodeX - 1); - } else { + } + else { paintHorizontalLine(g, tree, lineY, leftX, nodeX - 1); } } @@ -409,10 +408,10 @@ protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBoun continue; } - final int bottom = Math.min(childBounds.y + - (childBounds.height / 2), clipBottom); + final int bottom = Math.min(childBounds.y + + (childBounds.height / 2), clipBottom); - if (top <= bottom && bottom >= clipTop && top <= clipBottom) { + if (top <= bottom && bottom >= clipTop && top <= clipBottom) { g.setColor(JBColor.GRAY); paintVerticalLine(g, tree, lineX, top, bottom, dashed); } @@ -425,15 +424,17 @@ protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBoun protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom, boolean dashed) { if (dashed) { drawDashedVerticalLine(g, x, top, bottom); - } else { + } + else { g.drawLine(x, top, x, bottom); } } - public @NotNull TreePath getLastExpandedDescendant(TreePath path) { + public @NotNull + TreePath getLastExpandedDescendant(TreePath path) { while (tree.isExpanded(path)) { - final DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); + final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); if (node.isLeaf()) { break; } @@ -463,7 +464,8 @@ private Rectangle getSubtreeBounds(DefaultMutableTreeNode node, Rectangle clipBo final int maxY = (int)descendantBounds.getMaxY(); final int maxX = (int)clipBounds.getMaxX(); bounds = new Rectangle(minX, minY, maxX - minX, maxY - minY); - } else { + } + else { // This case shouldn't really happen unless we have a bug but using just // the root node bounds is a safe fallback. bounds = rootBounds; @@ -549,58 +551,36 @@ else if (myWideSelectionCondition.value(row)) { } @Override - protected void paintExpandControl(Graphics g, - Rectangle clipBounds, - Insets insets, - Rectangle bounds, - TreePath path, - int row, - boolean isExpanded, - boolean hasBeenExpanded, - boolean isLeaf) { - final boolean isPathSelected = tree.getSelectionModel().isPathSelected(path); - boolean isPropertyNode = false; - if (!isLeaf(row)) { - Object lastPathComponent = path.getLastPathComponent(); - if (lastPathComponent instanceof DefaultMutableTreeNode) { - final DiagnosticsNode diagnostic = maybeGetDiagnostic((DefaultMutableTreeNode) lastPathComponent); - if (diagnostic != null) { - isPropertyNode = diagnostic.isProperty(); - } - } - if (isPropertyNode) { - setExpandedIcon(FlutterIcons.CollapseProperty); - setCollapsedIcon(FlutterIcons.ExpandProperty); - } else { - setExpandedIcon(UIUtil.getTreeNodeIcon(true, false, false)); - setCollapsedIcon(UIUtil.getTreeNodeIcon(false, false, false)); - } - } - - super.paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); - } - - @Override - public void paint(Graphics g, JComponent c) { - if (tree != c) { - throw new InternalError("incorrect component"); - } - - if (tree instanceof InspectorTree) { - InspectorTree inspectorTree = (InspectorTree)tree; - DefaultMutableTreeNode highlightedRooot = inspectorTree.getHighlightedRoot(); - if (highlightedRooot == null) { - highlightedRooot = (DefaultMutableTreeNode)tree.getModel().getRoot(); - } - if (highlightedRooot != null && highlightedRooot.getUserObject() != null) { - Rectangle subtreeBounds = getSubtreeBounds(highlightedRooot, g.getClipBounds()); - if (subtreeBounds != null && !subtreeBounds.isEmpty()) { - g.setColor(SUBTREE_BOUNDS_COLOR); - g.fillRect(subtreeBounds.x, subtreeBounds.y, subtreeBounds.width, subtreeBounds.height); + protected void paintExpandControl(Graphics g, + Rectangle clipBounds, + Insets insets, + Rectangle bounds, + TreePath path, + int row, + boolean isExpanded, + boolean hasBeenExpanded, + boolean isLeaf) { + final boolean isPathSelected = tree.getSelectionModel().isPathSelected(path); + boolean isPropertyNode = false; + if (!isLeaf(row)) { + Object lastPathComponent = path.getLastPathComponent(); + if (lastPathComponent instanceof DefaultMutableTreeNode) { + final DiagnosticsNode diagnostic = maybeGetDiagnostic((DefaultMutableTreeNode)lastPathComponent); + if (diagnostic != null) { + isPropertyNode = diagnostic.isProperty(); } } + if (isPropertyNode) { + setExpandedIcon(FlutterIcons.CollapseProperty); + setCollapsedIcon(FlutterIcons.ExpandProperty); + } + else { + setExpandedIcon(UIUtil.getTreeNodeIcon(true, false, false)); + setCollapsedIcon(UIUtil.getTreeNodeIcon(false, false, false)); + } } - super.paint(g, c); + + super.paintExpandControl(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); } @Override diff --git a/src/io/flutter/view/WidgetPerfSummaryView.java b/src/io/flutter/view/WidgetPerfSummaryView.java index 1a78993a58..65bf0db77f 100644 --- a/src/io/flutter/view/WidgetPerfSummaryView.java +++ b/src/io/flutter/view/WidgetPerfSummaryView.java @@ -19,7 +19,7 @@ import java.awt.*; import java.awt.event.ActionEvent; -class WidgetPerfSummaryView extends JPanel { +class WidgetPerfSummaryView extends JPanel implements Disposable { private static final int REFRESH_TABLE_DELAY = 100; private final FlutterApp app; private final FlutterWidgetPerfManager perfManager; @@ -45,14 +45,19 @@ class WidgetPerfSummaryView extends JPanel { table = new WidgetPerfTable(app, parentDisposable, metric); - perfManager.getCurrentStats().addPerfListener(table); + Disposer.register(parentDisposable, this); + + perfManager.addPerfListener(table); add(ScrollPaneFactory.createScrollPane(table), BorderLayout.CENTER); // Perf info and tips myWidgetPerfTipsPanel = new WidgetPerfTipsPanel(parentDisposable, app); + } - Disposer.register(parentDisposable, refreshTableTimer::stop); + public void dispose() { + perfManager.removePerfListener(table); + refreshTableTimer.stop(); } public WidgetPerfTipsPanel getWidgetPerfTipsPanel() { diff --git a/testSrc/integration/io/flutter/testing/FlutterTestUtils.java b/testSrc/integration/io/flutter/testing/FlutterTestUtils.java index 97a8fa687d..12ee6fe3ee 100644 --- a/testSrc/integration/io/flutter/testing/FlutterTestUtils.java +++ b/testSrc/integration/io/flutter/testing/FlutterTestUtils.java @@ -25,7 +25,6 @@ import java.util.Collections; public class FlutterTestUtils { - public static final String BASE_TEST_DATA_PATH = findTestDataPath(); public static final String SDK_HOME_PATH = BASE_TEST_DATA_PATH + "/sdk"; diff --git a/testSrc/unit/io/flutter/dart/DartSyntaxTest.java b/testSrc/unit/io/flutter/dart/DartSyntaxTest.java index 4e09a0bbe1..ecbc03d9c1 100644 --- a/testSrc/unit/io/flutter/dart/DartSyntaxTest.java +++ b/testSrc/unit/io/flutter/dart/DartSyntaxTest.java @@ -15,6 +15,8 @@ import io.flutter.AbstractDartElementTest; import org.junit.Test; +import java.util.regex.Pattern; + import static org.junit.Assert.*; public class DartSyntaxTest extends AbstractDartElementTest { @@ -29,6 +31,16 @@ public void isTestCall() throws Exception { }); } + @Test + public void isTestCallPattern() throws Exception { + run(() -> { + final PsiElement testIdentifier = setUpDartElement("main() { test('my first test', () {} ); }", "test", LeafPsiElement.class); + final DartCallExpression call = DartSyntax.findEnclosingFunctionCall(testIdentifier, "test"); + assert call != null; + assertTrue(DartSyntax.isCallToFunctionMatching(call, Pattern.compile("t.*t"))); + }); + } + @Test public void isMainFunctionDeclaration() throws Exception { run(() -> { @@ -49,6 +61,26 @@ public void shouldFindEnclosingFunctionCall() throws Exception { }); } + @Test + public void shouldFindEnclosingFunctionCallPattern() throws Exception { + run(() -> { + final PsiElement helloElt = setUpDartElement("main() { test(\"hello\"); }", "hello", LeafPsiElement.class); + + final DartCallExpression call = DartSyntax.findEnclosingFunctionCall(helloElt, Pattern.compile("t.*t")); + assertNotNull("findEnclosingFunctionCall() didn't find enclosing function call", call); + }); + } + + @Test + public void shouldNotFindEnclosingFunctionCallPattern() throws Exception { + run(() -> { + final PsiElement helloElt = setUpDartElement("main() { test(\"hello\"); }", "hello", LeafPsiElement.class); + + final DartCallExpression call = DartSyntax.findEnclosingFunctionCall(helloElt, Pattern.compile("t.*a")); + assertNull("findEnclosingFunctionCall() didn't find enclosing function call", call); + }); + } + @Test public void shouldGetFirstArgumentFromFunctionCall() throws Exception { run(() -> { diff --git a/testSrc/unit/io/flutter/inspector/FlutterWidgetTest.java b/testSrc/unit/io/flutter/inspector/FlutterWidgetTest.java index f26863f085..1f7b63b629 100644 --- a/testSrc/unit/io/flutter/inspector/FlutterWidgetTest.java +++ b/testSrc/unit/io/flutter/inspector/FlutterWidgetTest.java @@ -158,7 +158,7 @@ static class FakeNode extends DiagnosticsNode { private final String description; public FakeNode(String description) { - super(null, null, false, null); + super(null, null, null, false, null); this.description = description; } diff --git a/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java b/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java index 42fd230dd0..185810026e 100644 --- a/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java +++ b/testSrc/unit/io/flutter/perf/FlutterWidgetPerfTest.java @@ -402,7 +402,7 @@ public void testFileStatsCalculation() throws InterruptedException, ExecutionExc assert clockModel != null; assert mainModel != null; - FilePerfInfo mainStats = null; + FilePerfInfo mainStats; stats = clockModel.getStatsFuture().get(); mainStats = mainModel.getStatsFuture().get(); @@ -515,6 +515,8 @@ public void testOverallStatsCalculation() throws InterruptedException, Execution // Verify that an idle event occurs once we wait the idle time delay. Thread.sleep(IDLE_DELAY_MILISECONDS); assertEquals(1, perfModel.idleCount); + + flutterWidgetPerf.removePerfListener(perfModel); flutterWidgetPerf.dispose(); } } diff --git a/testSrc/unit/io/flutter/project/ProjectWatchTest.java b/testSrc/unit/io/flutter/project/ProjectWatchTest.java index 1480f9195e..87660ecbbf 100644 --- a/testSrc/unit/io/flutter/project/ProjectWatchTest.java +++ b/testSrc/unit/io/flutter/project/ProjectWatchTest.java @@ -15,6 +15,7 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; public class ProjectWatchTest { @@ -28,7 +29,8 @@ public void shouldSendEventWhenProjectCloses() throws Exception { final ProjectWatch listen = ProjectWatch.subscribe(fixture.getProject(), callCount::incrementAndGet); ProjectManager.getInstance().closeProject(fixture.getProject()); - assertEquals(1, callCount.get()); + // The number of events fired is an implementation detail of the project manager. We just need at least one. + assertNotEquals(0, callCount.get()); }); } diff --git a/testSrc/unit/io/flutter/run/bazelTest/BazelTestFieldsTest.java b/testSrc/unit/io/flutter/run/bazelTest/BazelTestFieldsTest.java index a05ad9958c..2e7e5a6fbf 100644 --- a/testSrc/unit/io/flutter/run/bazelTest/BazelTestFieldsTest.java +++ b/testSrc/unit/io/flutter/run/bazelTest/BazelTestFieldsTest.java @@ -23,11 +23,13 @@ public void shouldReadFieldsFromXml() { addOption(elt, "testName", "Test number one"); addOption(elt, "entryFile", "/tmp/test/dir/lib/main.dart"); addOption(elt, "bazelTarget", "//path/to/flutter/app:hello"); + addOption(elt, "additionalArgs", "--no-watch --some-other-args 75"); final BazelTestFields fields = BazelTestFields.readFrom(elt); assertEquals("Test number one", fields.getTestName()); assertEquals("/tmp/test/dir/lib/main.dart", fields.getEntryFile()); assertEquals("//path/to/flutter/app:hello", fields.getBazelTarget()); + assertEquals("--no-watch --some-other-args 75", fields.getAdditionalArgs()); } @Test @@ -42,6 +44,7 @@ public void shouldUpgradeFieldsFromOldXml() { assertEquals(null, fields.getTestName()); assertEquals("/tmp/test/dir/lib/main.dart", fields.getEntryFile()); assertEquals("//path/to/flutter/app:hello", fields.getBazelTarget()); + assertEquals(null, fields.getAdditionalArgs()); } @Test @@ -49,7 +52,8 @@ public void roundTripShouldPreserveFields() { final BazelTestFields before = new BazelTestFields( "Test number two", "/tmp/foo/lib/main_two.dart", - "//path/to/flutter/app:hello2" + "//path/to/flutter/app:hello2", + "--no-watch --other-args" ); final Element elt = new Element("test"); @@ -57,13 +61,14 @@ public void roundTripShouldPreserveFields() { // Verify that we no longer write workingDirectory. assertArrayEquals( - new String[]{"bazelTarget", "entryFile", "testName"}, + new String[]{"additionalArgs", "bazelTarget", "entryFile", "testName"}, getOptionNames(elt).toArray()); final BazelTestFields after = BazelTestFields.readFrom(elt); assertEquals("Test number two", after.getTestName()); assertEquals("/tmp/foo/lib/main_two.dart", after.getEntryFile()); assertEquals("//path/to/flutter/app:hello2", after.getBazelTarget()); + assertEquals("--no-watch --other-args", after.getAdditionalArgs()); } private void addOption(Element elt, String name, String value) { diff --git a/testSrc/unit/io/flutter/run/bazelTest/LaunchCommandsTest.java b/testSrc/unit/io/flutter/run/bazelTest/LaunchCommandsTest.java index 8ea51a756b..18dcd7a73c 100644 --- a/testSrc/unit/io/flutter/run/bazelTest/LaunchCommandsTest.java +++ b/testSrc/unit/io/flutter/run/bazelTest/LaunchCommandsTest.java @@ -24,8 +24,8 @@ import java.util.List; import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class LaunchCommandsTest { @Rule @@ -163,20 +163,8 @@ public void failsForFileWithoutTestScript() { boolean didThrow = false; try { final GeneralCommandLine launchCommand = fields.getLaunchCommand(projectFixture.getProject(), RunMode.RUN); - } catch (ExecutionException e) { - didThrow = true; } - assertTrue("This test method expected to throw an exception, but did not.", didThrow); - } - - @Test - public void failsForFileWithoutNewTestRunner() { - final BazelTestFields fields = forFile("/workspace/foo/test/foo_test.dart"); - fields.useNewBazelTestRunnerOverride = false; - boolean didThrow = false; - try { - final GeneralCommandLine launchCommand = fields.getLaunchCommand(projectFixture.getProject(), RunMode.RUN); - } catch (ExecutionException e) { + catch (ExecutionException e) { didThrow = true; } assertTrue("This test method expected to throw an exception, but did not.", didThrow); @@ -194,7 +182,8 @@ public void failsForTestNameWithoutTestScript() { boolean didThrow = false; try { final GeneralCommandLine launchCommand = fields.getLaunchCommand(projectFixture.getProject(), RunMode.RUN); - } catch (ExecutionException e) { + } + catch (ExecutionException e) { didThrow = true; } assertTrue("This test method expected to throw an exception, but did not.", didThrow); @@ -203,12 +192,15 @@ public void failsForTestNameWithoutTestScript() { @Test public void runsInFileModeWhenBothFileAndBazelTargetAreProvided() throws ExecutionException { final BazelTestFields fields = new FakeBazelTestFields( - new BazelTestFields(null, "/workspace/foo/test/foo_test.dart", "//foo:test") + new BazelTestFields(null, "/workspace/foo/test/foo_test.dart", "//foo:test", "--arg1 --arg2 3") ); final GeneralCommandLine launchCommand = fields.getLaunchCommand(projectFixture.getProject(), RunMode.RUN); final List expectedCommandLine = new ArrayList<>(); expectedCommandLine.add("/workspace/scripts/flutter-test.sh"); + expectedCommandLine.add("--arg1"); + expectedCommandLine.add("--arg2"); + expectedCommandLine.add("3"); expectedCommandLine.add("--no-color"); expectedCommandLine.add("foo/test/foo_test.dart"); assertThat(launchCommand.getCommandLineList(null), equalTo(expectedCommandLine)); @@ -217,7 +209,7 @@ public void runsInFileModeWhenBothFileAndBazelTargetAreProvided() throws Executi @Test public void runsInBazelTargetModeWhenBothFileAndBazelTargetAreProvidedWithoutTestScript() throws ExecutionException { final BazelTestFields fields = new FakeBazelTestFields( - new BazelTestFields(null, "/workspace/foo/test/foo_test.dart", "//foo:test"), + new BazelTestFields(null, "/workspace/foo/test/foo_test.dart", "//foo:test", "--ignored-args"), "scripts/daemon.sh", "scripts/doctor.sh", "scripts/launch.sh", @@ -267,15 +259,9 @@ private static class FakeBazelTestFields extends BazelTestFields { if (launchScript != null) { fs.file("/workspace/" + launchScript, ""); } - if (testScript!= null) { + if (testScript != null) { fs.file("/workspace/" + testScript, ""); } - if (testScript == null) { - // When the test script is null, Flutter Settings will report the new Bazel test script as disabled. - useNewBazelTestRunnerOverride = false; - } else { - useNewBazelTestRunnerOverride = true; - } fakeWorkspace = Workspace.forTest( fs.findFileByPath("/workspace/"), PluginConfig.forTest( @@ -294,8 +280,7 @@ private static class FakeBazelTestFields extends BazelTestFields { "scripts/flutter-doctor.sh", "scripts/bazel-run.sh", "scripts/flutter-test.sh" - ); - + ); } @Override diff --git a/testSrc/unit/io/flutter/run/common/TestTypeTest.java b/testSrc/unit/io/flutter/run/common/TestTypeTest.java new file mode 100644 index 0000000000..fa8eab3519 --- /dev/null +++ b/testSrc/unit/io/flutter/run/common/TestTypeTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2019 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +package io.flutter.run.common; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.source.tree.LeafPsiElement; +import io.flutter.AbstractDartElementTest; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; + +/** + * Verifies that named test targets can be identified correctly as part of a group or as an individual test target. + */ +public class TestTypeTest extends AbstractDartElementTest { + + private static final String fileContents = "void main() {\n" + + " group('group 0', () {\n" + + " test('test 0', () {\n" + + " print('test contents');\n" + + " });\n" + + " testWidgets('test widgets 0', (tester) {\n" + + " print('test widgets contents');\n" + + " });\n" + + " testFooBarWidgets('test foobar widgets 0', (testers) {\n" + + " print('test foobar widgets contents');\n" + + " });\n" + + " });\n" + + " test('test 1', () {});\n" + + " testingWidgets('does not test widgets');\n" + + "}"; + + @Test + public void shouldMatchGroup() throws Exception { + run(() -> { + final PsiElement group0 = getGroupCall(); + assertThat(TestType.GROUP.findCorrespondingCall(group0), not(equalTo(null))); + assertThat(TestType.SINGLE.findCorrespondingCall(group0), equalTo(null)); + }); + } + + + @Test + public void shouldMatchTest0() throws Exception { + run(() -> { + final PsiElement test0 = getTestCall("test", "test 0"); + assertThat(TestType.GROUP.findCorrespondingCall(test0), not(equalTo(null))); + assertThat(TestType.SINGLE.findCorrespondingCall(test0), not(equalTo(null))); + }); + } + + @Test + public void shouldMatchTestWidgets0() throws Exception { + run(() -> { + final PsiElement testWidgets0 = getTestCall("testWidgets", "test widgets 0"); + assertThat(TestType.GROUP.findCorrespondingCall(testWidgets0), not(equalTo(null))); + assertThat(TestType.SINGLE.findCorrespondingCall(testWidgets0), not(equalTo(null))); + }); + } + + @Test + public void shouldMatchTestFooBarWidgets0() throws Exception { + run(() -> { + final PsiElement testFooBarWidgets0 = getTestCall("testFooBarWidgets", "test foobar widgets 0"); + assertThat(TestType.GROUP.findCorrespondingCall(testFooBarWidgets0), not(equalTo(null))); + assertThat(TestType.SINGLE.findCorrespondingCall(testFooBarWidgets0), not(equalTo(null))); + }); + } + + @Test + public void shouldMatchTest1() throws Exception { + run(() -> { + final PsiElement test1 = getTestCall("test", "test 1"); + assertThat(TestType.GROUP.findCorrespondingCall(test1), equalTo(null)); + assertThat(TestType.SINGLE.findCorrespondingCall(test1), not(equalTo(null))); + }); + } + + @Test + public void shouldNotMatchTestingWidgets() throws Exception { + run(() -> { + final PsiElement testingWidgets = getTestCall("testingWidgets", "does not test widgets"); + assertThat(TestType.GROUP.findCorrespondingCall(testingWidgets), equalTo(null)); + assertThat(TestType.SINGLE.findCorrespondingCall(testingWidgets), equalTo(null)); + }); + } + + @NotNull + private PsiElement getGroupCall() { + // Set up fake source code. + final PsiElement groupIdentifier = setUpDartElement( + fileContents, "group 0", LeafPsiElement.class); + assertThat(groupIdentifier, not(equalTo(null))); + + return groupIdentifier; + } + + /** + * Gets a specific test call. + * + * @param functionName The name of the function being called, eg test() or testWidgets() + * @param testName The name of the test desired, such as 'test 0' or 'test widgets 0' + * @return + */ + @NotNull + private PsiElement getTestCall(String functionName, String testName) { + // Set up fake source code. + final PsiElement testIdentifier = setUpDartElement( + fileContents, testName, LeafPsiElement.class); + assertThat(testIdentifier, not(equalTo(null))); + + return testIdentifier; + } +} diff --git a/testSrc/unit/io/flutter/run/daemon/DaemonEventTest.java b/testSrc/unit/io/flutter/run/daemon/DaemonEventTest.java index 2b371333fe..c986dc3c33 100644 --- a/testSrc/unit/io/flutter/run/daemon/DaemonEventTest.java +++ b/testSrc/unit/io/flutter/run/daemon/DaemonEventTest.java @@ -15,8 +15,8 @@ import java.util.Arrays; import java.util.List; -import static org.junit.Assert.assertEquals; import static io.flutter.testing.JsonTesting.curly; +import static org.junit.Assert.assertEquals; /** * Verifies that we can read events sent using the Flutter daemon protocol. @@ -33,12 +33,12 @@ public void setUp() { // daemon domain @Override - public void onDaemonLogMessage(DaemonEvent.LogMessage event) { + public void onDaemonLogMessage(DaemonEvent.DaemonLogMessage event) { logEvent(event, event.level, event.message, event.stackTrace); } @Override - public void onDaemonShowMessage(DaemonEvent.ShowMessage event) { + public void onDaemonShowMessage(DaemonEvent.DaemonShowMessage event) { logEvent(event, event.level, event.title, event.message); } @@ -46,7 +46,7 @@ public void onDaemonShowMessage(DaemonEvent.ShowMessage event) { @Override public void onAppStarting(DaemonEvent.AppStarting event) { - logEvent(event, event.appId, event.deviceId, event.directory); + logEvent(event, event.appId, event.deviceId, event.directory, event.launchMode); } @Override @@ -122,8 +122,8 @@ public void canReceiveShowMessage() { @Test public void canReceiveAppStarting() { - send("app.start", curly("appId:42", "deviceId:456", "directory:somedir")); - checkLog("AppStarting: 42, 456, somedir"); + send("app.start", curly("appId:42", "deviceId:456", "directory:somedir", "launchMode:run")); + checkLog("AppStarting: 42, 456, somedir, run"); } @Test diff --git a/testSrc/unit/io/flutter/testing/AdaptedFixture.java b/testSrc/unit/io/flutter/testing/AdaptedFixture.java index 0dfae0c865..795432fd41 100644 --- a/testSrc/unit/io/flutter/testing/AdaptedFixture.java +++ b/testSrc/unit/io/flutter/testing/AdaptedFixture.java @@ -36,16 +36,27 @@ public void evaluate() throws Throwable { inner = factory.create(description.getClassName()); if (runOnDispatchThread) { Testing.runOnDispatchThread(inner::setUp); - } else { + } + else { inner.setUp(); } try { base.evaluate(); - } finally { + } + finally { if (runOnDispatchThread) { Testing.runOnDispatchThread(inner::tearDown); - } else { - inner.tearDown(); + } + else { + try { + inner.tearDown(); + } + catch (RuntimeException ex) { + // TraceableDisposable.DisposalException is private. + // It gets thrown during CodeInsightTestFixtureImpl.tearDown, + // apparently because of a Kotlin test framework convenience + // feature that we don't have. + } } inner = null; } diff --git a/testSrc/unit/io/flutter/utils/EventStreamTest.java b/testSrc/unit/io/flutter/utils/EventStreamTest.java index e6e660cc96..0557a265b9 100644 --- a/testSrc/unit/io/flutter/utils/EventStreamTest.java +++ b/testSrc/unit/io/flutter/utils/EventStreamTest.java @@ -5,33 +5,32 @@ */ package io.flutter.utils; -import com.google.common.collect.ImmutableList; -import org.junit.Before; -import org.junit.Test; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; -import javax.swing.*; +import com.google.common.collect.ImmutableList; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; +import java.util.TimerTask; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; - -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import org.junit.Before; +import org.junit.Test; public class EventStreamTest { - private EventStream eventStream; - private final List logEntries = new ArrayList<>(); + private final Object logValueListenerLock = new Object(); + private EventStream eventStream; private CompletableFuture callbacksDone; - private volatile int expectedEvents; private volatile int numEvents; private boolean onUiThread; - private final Object logValueListenerLock = new Object(); @Before public void setUp() { @@ -86,9 +85,9 @@ public void calledWithDefaultValue() { } @Test - public void duplicateValues() { + public void duplicateValues() throws Exception { expectedEvents = 6; - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeAndWait(() -> { addLogValueListener(true); eventStream.setValue(100); eventStream.setValue(100); @@ -96,20 +95,18 @@ public void duplicateValues() { eventStream.setValue(200); eventStream.setValue(200); }); - checkLog("42", "100", "100", "100", "200", "200"); } @Test - public void nullInitialValue() { + public void nullInitialValue() throws Exception { expectedEvents = 3; - SwingUtilities.invokeLater(() -> { + SwingUtilities.invokeAndWait(() -> { eventStream = new EventStream<>(); addLogValueListener(true); eventStream.setValue(100); eventStream.setValue(200); }); - checkLog("null", "100", "200"); } @@ -246,8 +243,19 @@ private void reportFailure(Exception e) { } private void checkLog(String... expectedEntries) { + java.util.Timer timer = new java.util.Timer(); try { + TimerTask task = new TimerTask() { + @Override + public void run() { + timer.cancel(); + callbacksDone.completeExceptionally(new InterruptedException("Expected more events")); + fail(); + } + }; + timer.schedule(task, 1000); callbacksDone.get(); + timer.cancel(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); diff --git a/testSrc/unit/io/flutter/utils/FileWatchTest.java b/testSrc/unit/io/flutter/utils/FileWatchTest.java index 6005860f58..e0064fcd7f 100644 --- a/testSrc/unit/io/flutter/utils/FileWatchTest.java +++ b/testSrc/unit/io/flutter/utils/FileWatchTest.java @@ -16,6 +16,7 @@ import java.util.concurrent.atomic.AtomicInteger; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; /** * Verifies that we can watch files and directories. @@ -37,14 +38,16 @@ public void shouldFireEvents() throws Exception { // create tmp.writeFile("abc/child", ""); - assertEquals(1, eventCount.get()); + int count; + // The number of events fired is an implementation detail of the VFS. We just need at least one. + assertNotEquals(0, count = eventCount.get()); // modify tmp.writeFile("abc/child", "hello"); - assertEquals(2, eventCount.get()); + assertEquals(count + 1, eventCount.get()); // delete tmp.deleteFile("abc/child"); - assertEquals(3, eventCount.get()); + assertEquals(count + 2, eventCount.get()); } } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceCompleter.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceCompleter.java index 57c4373659..48ae3eae99 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceCompleter.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceCompleter.java @@ -18,15 +18,17 @@ public interface RemoteServiceCompleter { /** * Should be called when a service request completes successfully. + * * @param result the result of the request */ - public void result(JsonObject result); + void result(JsonObject result); /** * Should be called when a service request completes with an error. - * @param code the error code generated by the request + * + * @param code the error code generated by the request * @param message the description of the error - * @param data [optional] the description of the error + * @param data [optional] the description of the error */ - public void error(int code, String message, JsonObject data); + void error(int code, String message, JsonObject data); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceRunner.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceRunner.java index 71701cbb40..cc42969d46 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceRunner.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/RemoteServiceRunner.java @@ -21,8 +21,9 @@ public interface RemoteServiceRunner { /** * Called when a service request has been received. - * @param params the parameters of the request + * + * @param params the parameters of the request * @param completer the completer to invoke at the end of the execution */ - public void run(JsonObject params, RemoteServiceCompleter completer); + void run(JsonObject params, RemoteServiceCompleter completer); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/VmService.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/VmService.java index 7304378dc5..a9906cfe81 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/VmService.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/VmService.java @@ -48,7 +48,7 @@ * More specifically, you should not make any calls to {@link VmService} * from within any {@link Consumer} method. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class VmService extends VmServiceBase { public static final String DEBUG_STREAM_ID = "Debug"; @@ -77,13 +77,13 @@ public class VmService extends VmServiceBase { /** * The minor version number of the protocol supported by this client. */ - public static final int versionMinor = 12; + public static final int versionMinor = 15; /** * The [addBreakpoint] RPC is used to add a breakpoint at a specific line of some script. */ public void addBreakpoint(String isolateId, String scriptId, int line, BreakpointConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("scriptId", scriptId); params.addProperty("line", line); @@ -95,7 +95,7 @@ public void addBreakpoint(String isolateId, String scriptId, int line, Breakpoin * @param column This parameter is optional and may be null. */ public void addBreakpoint(String isolateId, String scriptId, int line, Integer column, BreakpointConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("scriptId", scriptId); params.addProperty("line", line); @@ -107,7 +107,7 @@ public void addBreakpoint(String isolateId, String scriptId, int line, Integer c * The [addBreakpointAtEntry] RPC is used to add a breakpoint at the entrypoint of some function. */ public void addBreakpointAtEntry(String isolateId, String functionId, BreakpointConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("functionId", functionId); request("addBreakpointAtEntry", params, consumer); @@ -119,7 +119,7 @@ public void addBreakpointAtEntry(String isolateId, String functionId, Breakpoint * deferred library which has not yet been loaded. */ public void addBreakpointWithScriptUri(String isolateId, String scriptUri, int line, BreakpointConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("scriptUri", scriptUri); params.addProperty("line", line); @@ -133,7 +133,7 @@ public void addBreakpointWithScriptUri(String isolateId, String scriptUri, int l * @param column This parameter is optional and may be null. */ public void addBreakpointWithScriptUri(String isolateId, String scriptUri, int line, Integer column, BreakpointConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("scriptUri", scriptUri); params.addProperty("line", line); @@ -145,7 +145,7 @@ public void addBreakpointWithScriptUri(String isolateId, String scriptUri, int l * @undocumented */ public void clearCpuProfile(String isolateId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("_clearCpuProfile", params, consumer); } @@ -154,7 +154,7 @@ public void clearCpuProfile(String isolateId, SuccessConsumer consumer) { * @undocumented */ public void clearVMTimeline(SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); request("_clearVMTimeline", params, consumer); } @@ -164,7 +164,7 @@ public void clearVMTimeline(SuccessConsumer consumer) { * @undocumented */ public void collectAllGarbage(String isolateId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("_collectAllGarbage", params, consumer); } @@ -173,7 +173,7 @@ public void collectAllGarbage(String isolateId, SuccessConsumer consumer) { * The [evaluate] RPC is used to evaluate an expression in the context of some target. */ public void evaluate(String isolateId, String targetId, String expression, EvaluateConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("targetId", targetId); params.addProperty("expression", expression); @@ -183,13 +183,15 @@ public void evaluate(String isolateId, String targetId, String expression, Evalu /** * The [evaluate] RPC is used to evaluate an expression in the context of some target. * @param scope This parameter is optional and may be null. + * @param disableBreakpoints This parameter is optional and may be null. */ - public void evaluate(String isolateId, String targetId, String expression, Map scope, EvaluateConsumer consumer) { - JsonObject params = new JsonObject(); + public void evaluate(String isolateId, String targetId, String expression, Map scope, Boolean disableBreakpoints, EvaluateConsumer consumer) { + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("targetId", targetId); params.addProperty("expression", expression); if (scope != null) params.add("scope", convertMapToJsonObject(scope)); + if (disableBreakpoints != null) params.addProperty("disableBreakpoints", disableBreakpoints); request("evaluate", params, consumer); } @@ -199,7 +201,7 @@ public void evaluate(String isolateId, String targetId, String expression, Map scope, EvaluateInFrameConsumer consumer) { - JsonObject params = new JsonObject(); + public void evaluateInFrame(String isolateId, int frameIndex, String expression, Map scope, Boolean disableBreakpoints, EvaluateInFrameConsumer consumer) { + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("frameIndex", frameIndex); params.addProperty("expression", expression); if (scope != null) params.add("scope", convertMapToJsonObject(scope)); + if (disableBreakpoints != null) params.addProperty("disableBreakpoints", disableBreakpoints); request("evaluateInFrame", params, consumer); } @@ -227,7 +231,7 @@ public void evaluateInFrame(String isolateId, int frameIndex, String expression, * @undocumented */ public void getAllocationProfile(String isolateId, AllocationProfileConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("_getAllocationProfile", params, consumer); } @@ -241,7 +245,7 @@ public void getAllocationProfile(String isolateId, AllocationProfileConsumer con * @param reset This parameter is optional and may be null. */ public void getAllocationProfile(String isolateId, String gc, Boolean reset, AllocationProfileConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); if (gc != null) params.addProperty("gc", gc); if (reset != null) params.addProperty("reset", reset); @@ -254,7 +258,7 @@ public void getAllocationProfile(String isolateId, String gc, Boolean reset, All * @undocumented */ public void getCpuProfile(String isolateId, String tags, CpuProfileConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("tags", tags); request("_getCpuProfile", params, consumer); @@ -265,7 +269,7 @@ public void getCpuProfile(String isolateId, String tags, CpuProfileConsumer cons * current values. */ public void getFlagList(FlagListConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); request("getFlagList", params, consumer); } @@ -275,7 +279,7 @@ public void getFlagList(FlagListConsumer consumer) { * @undocumented */ public void getInstances(String isolateId, String classId, int limit, ObjRefConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("classId", classId); params.addProperty("limit", limit); @@ -286,7 +290,7 @@ public void getInstances(String isolateId, String classId, int limit, ObjRefCons * The [getIsolate] RPC is used to lookup an [Isolate] object by its [id]. */ public void getIsolate(String isolateId, GetIsolateConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("getIsolate", params, consumer); } @@ -295,7 +299,7 @@ public void getIsolate(String isolateId, GetIsolateConsumer consumer) { * The [getObject] RPC is used to lookup an [object] from some isolate by its [id]. */ public void getObject(String isolateId, String objectId, GetObjectConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("objectId", objectId); request("getObject", params, consumer); @@ -307,7 +311,7 @@ public void getObject(String isolateId, String objectId, GetObjectConsumer consu * @param count This parameter is optional and may be null. */ public void getObject(String isolateId, String objectId, Integer offset, Integer count, GetObjectConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("objectId", objectId); if (offset != null) params.addProperty("offset", offset); @@ -320,7 +324,7 @@ public void getObject(String isolateId, String objectId, Integer offset, Integer * based on the isolate's [isolateId]. */ public void getScripts(String isolateId, ScriptListConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("getScripts", params, consumer); } @@ -330,7 +334,7 @@ public void getScripts(String isolateId, ScriptListConsumer consumer) { * isolate. */ public void getSourceReport(String isolateId, List reports, SourceReportConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.add("reports", convertIterableToJsonArray(reports)); request("getSourceReport", params, consumer); @@ -345,7 +349,7 @@ public void getSourceReport(String isolateId, List reports, So * @param forceCompile This parameter is optional and may be null. */ public void getSourceReport(String isolateId, List reports, String scriptId, Integer tokenPos, Integer endTokenPos, Boolean forceCompile, SourceReportConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.add("reports", convertIterableToJsonArray(reports)); if (scriptId != null) params.addProperty("scriptId", scriptId); @@ -360,7 +364,7 @@ public void getSourceReport(String isolateId, List reports, St * isolate. The isolate does not need to be paused. */ public void getStack(String isolateId, StackConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("getStack", params, consumer); } @@ -369,7 +373,7 @@ public void getStack(String isolateId, StackConsumer consumer) { * The [getVM] RPC returns global information about a Dart virtual machine. */ public void getVM(VMConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); request("getVM", params, consumer); } @@ -377,7 +381,7 @@ public void getVM(VMConsumer consumer) { * @undocumented */ public void getVMTimeline(ResponseConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); request("_getVMTimeline", params, consumer); } @@ -386,17 +390,33 @@ public void getVMTimeline(ResponseConsumer consumer) { * VM. */ public void getVersion(VersionConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); request("getVersion", params, consumer); } + /** + * The [invoke] RPC is used to perform regular method invocation on some receiver, as if by + * dart:mirror's ObjectMirror.invoke. Note this does not provide a way to perform getter, setter + * or constructor invocation. + * @param disableBreakpoints This parameter is optional and may be null. + */ + public void invoke(String isolateId, String targetId, String selector, List argumentIds, Boolean disableBreakpoints, InvokeConsumer consumer) { + final JsonObject params = new JsonObject(); + params.addProperty("isolateId", isolateId); + params.addProperty("targetId", targetId); + params.addProperty("selector", selector); + params.add("argumentIds", convertIterableToJsonArray(argumentIds)); + if (disableBreakpoints != null) params.addProperty("disableBreakpoints", disableBreakpoints); + request("invoke", params, consumer); + } + /** * The [invoke] RPC is used to perform regular method invocation on some receiver, as if by * dart:mirror's ObjectMirror.invoke. Note this does not provide a way to perform getter, setter * or constructor invocation. */ public void invoke(String isolateId, String targetId, String selector, List argumentIds, InvokeConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("targetId", targetId); params.addProperty("selector", selector); @@ -409,7 +429,7 @@ public void invoke(String isolateId, String targetId, String selector, ListIsolate.kill(IMMEDIATE)Isolate.kill(IMMEDIATE). */ public void kill(String isolateId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("kill", params, consumer); } @@ -419,7 +439,7 @@ public void kill(String isolateId, SuccessConsumer consumer) { * and potentially returns before the isolate is paused. */ public void pause(String isolateId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("pause", params, consumer); } @@ -428,7 +448,7 @@ public void pause(String isolateId, SuccessConsumer consumer) { * @undocumented */ public void registerService(String service, String alias, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("service", service); params.addProperty("alias", alias); request("_registerService", params, consumer); @@ -442,7 +462,7 @@ public void registerService(String service, String alias, SuccessConsumer consum * @param packagesUri This parameter is optional and may be null. */ public void reloadSources(String isolateId, Boolean force, Boolean pause, String rootLibUri, String packagesUri, ReloadReportConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); if (force != null) params.addProperty("force", force); if (pause != null) params.addProperty("pause", pause); @@ -455,7 +475,7 @@ public void reloadSources(String isolateId, Boolean force, Boolean pause, String * The [reloadSources] RPC is used to perform a hot reload of an Isolate's sources. */ public void reloadSources(String isolateId, ReloadReportConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("reloadSources", params, consumer); } @@ -464,7 +484,7 @@ public void reloadSources(String isolateId, ReloadReportConsumer consumer) { * The [removeBreakpoint] RPC is used to remove a breakpoint by its [id]. */ public void removeBreakpoint(String isolateId, String breakpointId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("breakpointId", breakpointId); request("removeBreakpoint", params, consumer); @@ -476,7 +496,7 @@ public void removeBreakpoint(String isolateId, String breakpointId, SuccessConsu * @undocumented */ public void requestHeapSnapshot(String isolateId, String roots, boolean collectGarbage, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("roots", roots); params.addProperty("collectGarbage", collectGarbage); @@ -485,11 +505,12 @@ public void requestHeapSnapshot(String isolateId, String roots, boolean collectG /** * The [resume] RPC is used to resume execution of a paused isolate. - * @param step This parameter is optional and may be null. + * @param step A [StepOption] indicates which form of stepping is requested in a resume RPC. This + * parameter is optional and may be null. * @param frameIndex This parameter is optional and may be null. */ public void resume(String isolateId, StepOption step, Integer frameIndex, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); if (step != null) params.addProperty("step", step.name()); if (frameIndex != null) params.addProperty("frameIndex", frameIndex); @@ -500,7 +521,7 @@ public void resume(String isolateId, StepOption step, Integer frameIndex, Succes * The [resume] RPC is used to resume execution of a paused isolate. */ public void resume(String isolateId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); request("resume", params, consumer); } @@ -508,9 +529,11 @@ public void resume(String isolateId, SuccessConsumer consumer) { /** * The [setExceptionPauseMode] RPC is used to control if an isolate pauses when an exception is * thrown. + * @param mode An [ExceptionPauseMode] indicates how the isolate pauses when an exception is + * thrown. */ public void setExceptionPauseMode(String isolateId, ExceptionPauseMode mode, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("mode", mode.name()); request("setExceptionPauseMode", params, consumer); @@ -521,7 +544,7 @@ public void setExceptionPauseMode(String isolateId, ExceptionPauseMode mode, Suc * not exist, the flag may not be set at runtime, or the value is of the wrong type for the flag. */ public void setFlag(String name, String value, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("name", name); params.addProperty("value", value); request("setFlag", params, consumer); @@ -532,7 +555,7 @@ public void setFlag(String name, String value, SuccessConsumer consumer) { * work for a given library. */ public void setLibraryDebuggable(String isolateId, String libraryId, boolean isDebuggable, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("libraryId", libraryId); params.addProperty("isDebuggable", isDebuggable); @@ -543,7 +566,7 @@ public void setLibraryDebuggable(String isolateId, String libraryId, boolean isD * The [setName] RPC is used to change the debugging name for an isolate. */ public void setName(String isolateId, String name, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("isolateId", isolateId); params.addProperty("name", name); request("setName", params, consumer); @@ -553,7 +576,7 @@ public void setName(String isolateId, String name, SuccessConsumer consumer) { * The [setVMName] RPC is used to change the debugging name for the vm. */ public void setVMName(String name, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("name", name); request("setVMName", params, consumer); } @@ -562,7 +585,7 @@ public void setVMName(String name, SuccessConsumer consumer) { * @undocumented */ public void setVMTimelineFlags(List recordedStreams, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.add("recordedStreams", convertIterableToJsonArray(recordedStreams)); request("_setVMTimelineFlags", params, consumer); } @@ -571,7 +594,7 @@ public void setVMTimelineFlags(List recordedStreams, SuccessConsumer con * The [streamCancel] RPC cancels a stream subscription in the VM. */ public void streamCancel(String streamId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("streamId", streamId); request("streamCancel", params, consumer); } @@ -581,7 +604,7 @@ public void streamCancel(String streamId, SuccessConsumer consumer) { * begin receiving events from the stream. */ public void streamListen(String streamId, SuccessConsumer consumer) { - JsonObject params = new JsonObject(); + final JsonObject params = new JsonObject(); params.addProperty("streamId", streamId); request("streamListen", params, consumer); } @@ -811,6 +834,10 @@ void forwardResponse(Consumer consumer, String responseType, JsonObject json) { ((ResponseConsumer) consumer).received(new AllocationProfile(json)); return; } + if (responseType.equals("BoundVariable")) { + ((ResponseConsumer) consumer).received(new BoundVariable(json)); + return; + } if (responseType.equals("Breakpoint")) { ((ResponseConsumer) consumer).received(new Breakpoint(json)); return; diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceBase.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceBase.java index 0dfd37e354..746beee3c0 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceBase.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceBase.java @@ -17,20 +17,12 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; - import de.roderick.weberknecht.WebSocket; import de.roderick.weberknecht.WebSocketEventHandler; import de.roderick.weberknecht.WebSocketException; import de.roderick.weberknecht.WebSocketMessage; - import org.dartlang.vm.service.consumer.*; -import org.dartlang.vm.service.element.Event; -import org.dartlang.vm.service.element.Instance; -import org.dartlang.vm.service.element.Library; -import org.dartlang.vm.service.element.Obj; -import org.dartlang.vm.service.element.RPCError; -import org.dartlang.vm.service.element.Sentinel; -import org.dartlang.vm.service.element.Version; +import org.dartlang.vm.service.element.*; import org.dartlang.vm.service.internal.RequestSink; import org.dartlang.vm.service.internal.VmServiceConst; import org.dartlang.vm.service.internal.WebSocketRequestSink; @@ -63,7 +55,8 @@ public static VmService connect(final String url) throws IOException { URI uri; try { uri = new URI(url); - } catch (URISyntaxException e) { + } + catch (URISyntaxException e) { throw new IOException("Invalid URL: " + url, e); } String wsScheme = uri.getScheme(); @@ -75,7 +68,8 @@ public static VmService connect(final String url) throws IOException { WebSocket webSocket; try { webSocket = new WebSocket(uri); - } catch (WebSocketException e) { + } + catch (WebSocketException e) { throw new IOException("Failed to create websocket: " + url, e); } final VmService vmService = new VmService(); @@ -120,9 +114,11 @@ public void onPong() { //noinspection TryWithIdenticalCatches try { webSocket.connect(); - } catch (WebSocketException e) { + } + catch (WebSocketException e) { throw new IOException("Failed to connect: " + url, e); - } catch (ArrayIndexOutOfBoundsException e) { + } + catch (ArrayIndexOutOfBoundsException e) { // The weberknecht can occasionally throw an array index exception if a connect terminates on initial connect // (de.roderick.weberknecht.WebSocket.connect, WebSocket.java:126). throw new IOException("Failed to connect: " + url, e); @@ -136,7 +132,7 @@ public void onPong() { @Override public void onError(RPCError error) { String msg = "Failed to determine protocol version: " + error.getCode() + "\n message: " - + error.getMessage() + "\n details: " + error.getDetails(); + + error.getMessage() + "\n details: " + error.getDetails(); Logging.getLogger().logInformation(msg); errMsg[0] = msg; } @@ -148,11 +144,12 @@ public void received(Version response) { if (major != VmService.versionMajor || minor != VmService.versionMinor) { if (major == 2 || major == 3) { Logging.getLogger().logInformation( - "Difference in protocol version: client=" + VmService.versionMajor + "." - + VmService.versionMinor + " vm=" + major + "." + minor); - } else { + "Difference in protocol version: client=" + VmService.versionMajor + "." + + VmService.versionMinor + " vm=" + major + "." + minor); + } + else { String msg = "Incompatible protocol version: client=" + VmService.versionMajor + "." - + VmService.versionMinor + " vm=" + major + "." + minor; + + VmService.versionMinor + " vm=" + major + "." + minor; Logging.getLogger().logError(msg); errMsg[0] = msg; } @@ -167,7 +164,8 @@ public void received(Version response) { if (errMsg[0] != null) { throw new IOException(errMsg[0]); } - } catch (InterruptedException e) { + } + catch (InterruptedException e) { throw new RuntimeException("Interrupted while waiting for response", e); } @@ -202,7 +200,7 @@ public static VmService localConnect(int port) throws IOException { /** * A list of objects to which {@link Event}s from the VM are forwarded. */ - private final List vmListeners = new ArrayList(); + private final List vmListeners = new ArrayList<>(); /** * A list of objects to which {@link Event}s from the VM are forwarded. @@ -263,8 +261,9 @@ public void onError(RPCError error) { @Override public void received(Obj response) { if (response instanceof Instance) { - consumer.received((Instance) response); - } else { + consumer.received((Instance)response); + } + else { onError(RPCError.unexpected("Instance", response)); } } @@ -290,8 +289,9 @@ public void onError(RPCError error) { @Override public void received(Obj response) { if (response instanceof Library) { - consumer.received((Library) response); - } else { + consumer.received((Library)response); + } + else { onError(RPCError.unexpected("Library", response)); } } @@ -307,7 +307,7 @@ public void received(Sentinel response) { /** * Invoke a specific service protocol extension method. - * + *

* See https://api.dartlang.org/stable/dart-developer/dart-developer-library.html. */ public void callServiceExtension(String isolateId, String method, ServiceExtensionConsumer consumer) { @@ -318,7 +318,7 @@ public void callServiceExtension(String isolateId, String method, ServiceExtensi /** * Invoke a specific service protocol extension method. - * + *

* See https://api.dartlang.org/stable/dart-developer/dart-developer-library.html. */ public void callServiceExtension(String isolateId, String method, JsonObject params, ServiceExtensionConsumer consumer) { @@ -353,7 +353,8 @@ public void connectionOpened() { for (VmServiceListener listener : vmListeners) { try { listener.connectionOpened(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Exception notifying listener", e); } } @@ -363,7 +364,8 @@ private void forwardEvent(String streamId, Event event) { for (VmServiceListener listener : vmListeners) { try { listener.received(streamId, event); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Exception processing event: " + streamId + ", " + event.getJson(), e); } } @@ -373,7 +375,8 @@ public void connectionClosed() { for (VmServiceListener listener : vmListeners) { try { listener.connectionClosed(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Exception notifying listener", e); } } @@ -404,8 +407,9 @@ void processMessage(String jsonText) { // Decode the JSON JsonObject json; try { - json = (JsonObject) new JsonParser().parse(jsonText); - } catch (Exception e) { + json = (JsonObject)new JsonParser().parse(jsonText); + } + catch (Exception e) { Logging.getLogger().logError("Parse message failed: " + jsonText, e); return; } @@ -425,13 +429,16 @@ void processMessage(String jsonText) { } if (json.has("id")) { processRequest(json); - } else { + } + else { processNotification(json); } - } else if (json.has("result") || json.has("error")) { + } + else if (json.has("result") || json.has("error")) { processResponse(json); - } else { - Logging.getLogger().logError("Malformed message"); + } + else { + Logging.getLogger().logError("Malformed message"); } } @@ -443,7 +450,8 @@ void processRequest(JsonObject json) { String id; try { id = json.get(ID).getAsString(); - } catch (Exception e) { + } + catch (Exception e) { final String message = "Request malformed " + ID; Logging.getLogger().logError(message, e); final JsonObject error = new JsonObject(); @@ -459,7 +467,8 @@ void processRequest(JsonObject json) { String method; try { method = json.get(METHOD).getAsString(); - } catch (Exception e) { + } + catch (Exception e) { final String message = "Request malformed " + METHOD; Logging.getLogger().logError(message, e); final JsonObject error = new JsonObject(); @@ -473,7 +482,8 @@ void processRequest(JsonObject json) { JsonObject params; try { params = json.get(PARAMS).getAsJsonObject(); - } catch (Exception e) { + } + catch (Exception e) { final String message = "Request malformed " + METHOD; Logging.getLogger().logError(message, e); final JsonObject error = new JsonObject(); @@ -498,23 +508,24 @@ void processRequest(JsonObject json) { final RemoteServiceRunner runner = remoteServiceRunners.get(method); try { runner.run(params, new RemoteServiceCompleter() { - public void result(JsonObject result) { - response.add(RESULT, result); - requestSink.add(response); - } + public void result(JsonObject result) { + response.add(RESULT, result); + requestSink.add(response); + } - public void error(int code, String message, JsonObject data) { - final JsonObject error = new JsonObject(); - error.addProperty(CODE, code); - error.addProperty(MESSAGE, message); - if (data != null) { - error.add(DATA, data); - } - response.add(ERROR, error); - requestSink.add(response); + public void error(int code, String message, JsonObject data) { + final JsonObject error = new JsonObject(); + error.addProperty(CODE, code); + error.addProperty(MESSAGE, message); + if (data != null) { + error.add(DATA, data); } + response.add(ERROR, error); + requestSink.add(response); + } }); - } catch (Exception e) { + } + catch (Exception e) { final String message = "Internal Server Error"; Logging.getLogger().logError(message, e); final JsonObject error = new JsonObject(); @@ -522,33 +533,34 @@ public void error(int code, String message, JsonObject data) { error.addProperty(MESSAGE, message); response.add(ERROR, error); requestSink.add(response); - return; } } private static final RemoteServiceCompleter ignoreCallback = new RemoteServiceCompleter() { - public void result(JsonObject result) { - // ignore - } + public void result(JsonObject result) { + // ignore + } - public void error(int code, String message, JsonObject data) { - // ignore - } + public void error(int code, String message, JsonObject data) { + // ignore + } }; void processNotification(JsonObject json) { String method; try { method = json.get(METHOD).getAsString(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Request malformed " + METHOD, e); return; } JsonObject params; try { params = json.get(PARAMS).getAsJsonObject(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Event missing " + PARAMS, e); return; } @@ -556,19 +568,22 @@ void processNotification(JsonObject json) { String streamId; try { streamId = params.get(STREAM_ID).getAsString(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Event missing " + STREAM_ID, e); return; } Event event; try { event = new Event(params.get(EVENT).getAsJsonObject()); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Event missing " + EVENT, e); return; } forwardEvent(streamId, event); - } else { + } + else { if (!remoteServiceRunners.containsKey(method)) { Logging.getLogger().logError("Unknown service " + method); return; @@ -577,9 +592,9 @@ void processNotification(JsonObject json) { final RemoteServiceRunner runner = remoteServiceRunners.get(method); try { runner.run(params, ignoreCallback); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Internal Server Error", e); - return; } } } @@ -595,7 +610,8 @@ void processResponse(JsonObject json) { String id; try { id = idElem.getAsString(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Response missing " + ID, e); return; } @@ -611,14 +627,16 @@ void processResponse(JsonObject json) { JsonObject result; try { result = resultElem.getAsJsonObject(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Response has invalid " + RESULT, e); return; } String responseType; try { responseType = result.get(TYPE).getAsString(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Response missing " + TYPE, e); return; } @@ -632,7 +650,8 @@ void processResponse(JsonObject json) { JsonObject error; try { error = resultElem.getAsJsonObject(); - } catch (Exception e) { + } + catch (Exception e) { Logging.getLogger().logError("Response has invalid " + RESULT, e); return; } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceListener.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceListener.java index 50da1405f0..7e59bea964 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceListener.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/VmServiceListener.java @@ -19,14 +19,15 @@ * Interface used by {@link VmService} to notify others of VM events. */ public interface VmServiceListener { - public void connectionOpened(); + void connectionOpened(); /** * Called when a VM event has been received. + * * @param streamId the stream identifier (e.g. {@link VmService#DEBUG_STREAM_ID} - * @param event the event + * @param event the event */ - public void received(String streamId, Event event); + void received(String streamId, Event event); - public void connectionClosed(); + void connectionClosed(); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/AllocationProfileConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/AllocationProfileConsumer.java index 22a404cacc..d3bbc7a551 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/AllocationProfileConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/AllocationProfileConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.AllocationProfile; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface AllocationProfileConsumer extends Consumer { - public void received(AllocationProfile response); + void received(AllocationProfile response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/BreakpointConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/BreakpointConsumer.java index 1509481c94..7fdc2ddbc3 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/BreakpointConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/BreakpointConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.Breakpoint; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface BreakpointConsumer extends Consumer { - public void received(Breakpoint response); + void received(Breakpoint response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/Consumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/Consumer.java index ebf0672682..3175d25274 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/Consumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/Consumer.java @@ -1,11 +1,11 @@ /* * Copyright (c) 2015, the Dart project authors. - * + * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.eclipse.org/legal/epl-v10.html - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -19,7 +19,6 @@ * Consumer is a common interface for all consumer interfaces. */ public interface Consumer { - /** * Called if the request failed for some reason. */ diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/CpuProfileConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/CpuProfileConsumer.java index db04f2b18b..25878b1009 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/CpuProfileConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/CpuProfileConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.CpuProfile; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface CpuProfileConsumer extends Consumer { - public void received(CpuProfile response); + void received(CpuProfile response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateConsumer.java index 022329551d..e256aab5ad 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateConsumer.java @@ -19,12 +19,12 @@ import org.dartlang.vm.service.element.InstanceRef; import org.dartlang.vm.service.element.Sentinel; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface EvaluateConsumer extends Consumer { - public void received(ErrorRef response); + void received(ErrorRef response); - public void received(InstanceRef response); + void received(InstanceRef response); - public void received(Sentinel response); + void received(Sentinel response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java index cd8f4e2c8f..21ac3e7ab4 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/EvaluateInFrameConsumer.java @@ -19,12 +19,12 @@ import org.dartlang.vm.service.element.InstanceRef; import org.dartlang.vm.service.element.Sentinel; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface EvaluateInFrameConsumer extends Consumer { - public void received(ErrorRef response); + void received(ErrorRef response); - public void received(InstanceRef response); + void received(InstanceRef response); - public void received(Sentinel response); + void received(Sentinel response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/FlagListConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/FlagListConsumer.java index 4f9e04443a..b885561a95 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/FlagListConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/FlagListConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.FlagList; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface FlagListConsumer extends Consumer { - public void received(FlagList response); + void received(FlagList response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetInstanceConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetInstanceConsumer.java index efef32259c..ae6658cd90 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetInstanceConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetInstanceConsumer.java @@ -1,11 +1,11 @@ /* * Copyright (c) 2015, the Dart project authors. - * + * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.eclipse.org/legal/epl-v10.html - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -16,5 +16,5 @@ import org.dartlang.vm.service.element.Instance; public interface GetInstanceConsumer extends Consumer { - public void received(Instance response); + void received(Instance response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetIsolateConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetIsolateConsumer.java index a018fb47a6..767aab5876 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetIsolateConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetIsolateConsumer.java @@ -18,10 +18,10 @@ import org.dartlang.vm.service.element.Isolate; import org.dartlang.vm.service.element.Sentinel; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface GetIsolateConsumer extends Consumer { - public void received(Isolate response); + void received(Isolate response); - public void received(Sentinel response); + void received(Sentinel response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetLibraryConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetLibraryConsumer.java index bd15d5cc5c..5d61e604d3 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetLibraryConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetLibraryConsumer.java @@ -1,11 +1,11 @@ /* * Copyright (c) 2015, the Dart project authors. - * + * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.eclipse.org/legal/epl-v10.html - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -16,5 +16,5 @@ import org.dartlang.vm.service.element.Library; public interface GetLibraryConsumer extends Consumer { - public void received(Library response); + void received(Library response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetObjectConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetObjectConsumer.java index b0f0ecb3a8..1f7b17c01d 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetObjectConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/GetObjectConsumer.java @@ -18,10 +18,10 @@ import org.dartlang.vm.service.element.Obj; import org.dartlang.vm.service.element.Sentinel; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface GetObjectConsumer extends Consumer { - public void received(Obj response); + void received(Obj response); - public void received(Sentinel response); + void received(Sentinel response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/InvokeConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/InvokeConsumer.java index c2df74033c..b542ea6502 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/InvokeConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/InvokeConsumer.java @@ -19,12 +19,12 @@ import org.dartlang.vm.service.element.InstanceRef; import org.dartlang.vm.service.element.Sentinel; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface InvokeConsumer extends Consumer { - public void received(ErrorRef response); + void received(ErrorRef response); - public void received(InstanceRef response); + void received(InstanceRef response); - public void received(Sentinel response); + void received(Sentinel response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ObjRefConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ObjRefConsumer.java index 76957c4719..848684e949 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ObjRefConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ObjRefConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.ObjRef; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface ObjRefConsumer extends Consumer { - public void received(ObjRef response); + void received(ObjRef response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ReloadReportConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ReloadReportConsumer.java index 67b921f938..ca1c31952e 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ReloadReportConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ReloadReportConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.ReloadReport; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface ReloadReportConsumer extends Consumer { - public void received(ReloadReport response); + void received(ReloadReport response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ResponseConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ResponseConsumer.java index ed46bd2e81..f9d8b3363d 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ResponseConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ResponseConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.Response; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface ResponseConsumer extends Consumer { - public void received(Response response); + void received(Response response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ScriptListConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ScriptListConsumer.java index 9db2d4896b..0a5fb29200 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ScriptListConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ScriptListConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.ScriptList; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface ScriptListConsumer extends Consumer { - public void received(ScriptList response); + void received(ScriptList response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java index 180d13d476..71b676d45d 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/ServiceExtensionConsumer.java @@ -16,5 +16,5 @@ import com.google.gson.JsonObject; public interface ServiceExtensionConsumer extends Consumer { - public void received(JsonObject result); + void received(JsonObject result); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SourceReportConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SourceReportConsumer.java index 4abfedc5b3..0a9fd1f2d3 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SourceReportConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SourceReportConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.SourceReport; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface SourceReportConsumer extends Consumer { - public void received(SourceReport response); + void received(SourceReport response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/StackConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/StackConsumer.java index 30526212aa..d82bd0e770 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/StackConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/StackConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.Stack; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface StackConsumer extends Consumer { - public void received(Stack response); + void received(Stack response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SuccessConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SuccessConsumer.java index cbcf5247e9..e9e8b88bf4 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SuccessConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/SuccessConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.Success; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface SuccessConsumer extends Consumer { - public void received(Success response); + void received(Success response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VMConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VMConsumer.java index 1a853861f9..48a96b865e 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VMConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VMConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.VM; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface VMConsumer extends Consumer { - public void received(VM response); + void received(VM response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VersionConsumer.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VersionConsumer.java index 010ffee7e7..91a7fd9ecf 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VersionConsumer.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/consumer/VersionConsumer.java @@ -17,8 +17,8 @@ import org.dartlang.vm.service.element.Version; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public interface VersionConsumer extends Consumer { - public void received(Version response); + void received(Version response); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/AllocationProfile.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/AllocationProfile.java index 5437fcb30d..eebff720fb 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/AllocationProfile.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/AllocationProfile.java @@ -18,7 +18,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class AllocationProfile extends Response { public AllocationProfile(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundField.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundField.java index 8699da53c6..696cb6cfbc 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundField.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundField.java @@ -21,7 +21,7 @@ /** * A {@link BoundField} represents a field bound to a particular value in an {@link Instance}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class BoundField extends Element { public BoundField(JsonObject json) { @@ -36,10 +36,10 @@ public FieldRef getDecl() { * @return one of InstanceRef or Sentinel */ public InstanceRef getValue() { - JsonElement elem = json.get("value"); + final JsonElement elem = json.get("value"); if (!elem.isJsonObject()) return null; - JsonObject child = elem.getAsJsonObject(); - String type = child.get("type").getAsString(); + final JsonObject child = elem.getAsJsonObject(); + final String type = child.get("type").getAsString(); if ("Sentinel".equals(type)) return null; return new InstanceRef(child); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundVariable.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundVariable.java index 087940c244..dedb8e116c 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundVariable.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/BoundVariable.java @@ -21,8 +21,8 @@ * A {@link BoundVariable} represents a local variable bound to a particular value in a {@link * Frame}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) -public class BoundVariable extends Element { +@SuppressWarnings({"WeakerAccess", "unused"}) +public class BoundVariable extends Response { public BoundVariable(JsonObject json) { super(json); @@ -58,7 +58,7 @@ public int getScopeStartTokenPos() { * Sentinel */ public Object getValue() { - JsonObject elem = (JsonObject)json.get("value"); + final JsonObject elem = (JsonObject)json.get("value"); if (elem == null) return null; if (elem.get("type").getAsString().equals("@Instance")) return new InstanceRef(elem); diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Breakpoint.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Breakpoint.java index 2ecebc87fc..bd0c9f5f6c 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Breakpoint.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Breakpoint.java @@ -20,7 +20,7 @@ /** * A {@link Breakpoint} describes a debugger breakpoint. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Breakpoint extends Obj { public Breakpoint(JsonObject json) { @@ -51,7 +51,7 @@ public boolean getIsSyntheticAsyncContinuation() { * @return one of SourceLocation or UnresolvedSourceLocation */ public Object getLocation() { - JsonObject elem = (JsonObject)json.get("location"); + final JsonObject elem = (JsonObject)json.get("location"); if (elem == null) return null; if (elem.get("type").getAsString().equals("SourceLocation")) return new SourceLocation(elem); diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassHeapStats.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassHeapStats.java index f7d7b8ba7b..80d096c776 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassHeapStats.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassHeapStats.java @@ -18,7 +18,7 @@ import com.google.gson.JsonObject; import java.util.List; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ClassHeapStats extends Response { public ClassHeapStats(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassList.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassList.java index 5f1f854fea..85e87028ab 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassList.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassList.java @@ -18,7 +18,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ClassList extends Response { public ClassList(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassObj.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassObj.java index 9014f64831..06b7e34ee8 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassObj.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassObj.java @@ -21,7 +21,7 @@ /** * A {@link ClassObj} provides information about a Dart language class. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ClassObj extends Obj { public ClassObj(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassRef.java index f9d5887d58..c2f2cda26d 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ClassRef.java @@ -20,7 +20,7 @@ /** * {@link ClassRef} is a reference to a {@link ClassObj}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ClassRef extends ObjRef { public ClassRef(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Code.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Code.java index 9fd17fd6aa..56a634986c 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Code.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Code.java @@ -21,7 +21,7 @@ /** * A {@link Code} object represents compiled code in the Dart VM. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Code extends ObjRef { public Code(JsonObject json) { @@ -32,7 +32,7 @@ public Code(JsonObject json) { * What kind of code object is this? */ public CodeKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? CodeKind.Unknown : CodeKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeKind.java index 37c9b7ad03..35324616b8 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeKind.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeKind.java @@ -15,7 +15,7 @@ // This is a generated file. -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public enum CodeKind { Collected, diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRef.java index ca4f38d1e6..590d891166 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRef.java @@ -21,7 +21,7 @@ /** * {@link CodeRef} is a reference to a {@link Code} object. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class CodeRef extends ObjRef { public CodeRef(JsonObject json) { @@ -32,7 +32,7 @@ public CodeRef(JsonObject json) { * What kind of code object is this? */ public CodeKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? CodeKind.Unknown : CodeKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRegion.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRegion.java index 869e4cd16b..1fbe3e1a75 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRegion.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CodeRegion.java @@ -17,7 +17,7 @@ import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class CodeRegion extends Element { public CodeRegion(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Context.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Context.java index b90f770842..fcd31490dc 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Context.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Context.java @@ -21,7 +21,7 @@ /** * A {@link Context} is a data structure which holds the captured variables for some closure. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Context extends Obj { public Context(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextElement.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextElement.java index 150cbb2181..0ff560912c 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextElement.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextElement.java @@ -18,7 +18,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ContextElement extends Element { public ContextElement(JsonObject json) { @@ -29,10 +29,10 @@ public ContextElement(JsonObject json) { * @return one of InstanceRef or Sentinel */ public InstanceRef getValue() { - JsonElement elem = json.get("value"); + final JsonElement elem = json.get("value"); if (!elem.isJsonObject()) return null; - JsonObject child = elem.getAsJsonObject(); - String type = child.get("type").getAsString(); + final JsonObject child = elem.getAsJsonObject(); + final String type = child.get("type").getAsString(); if ("Sentinel".equals(type)) return null; return new InstanceRef(child); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextRef.java index e49896c9e1..9b66e407af 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ContextRef.java @@ -17,7 +17,7 @@ import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ContextRef extends ObjRef { public ContextRef(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CpuProfile.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CpuProfile.java index ed9674b3fb..50785b786c 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CpuProfile.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/CpuProfile.java @@ -19,7 +19,7 @@ import com.google.gson.JsonObject; import java.util.List; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class CpuProfile extends Response { public CpuProfile(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Element.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Element.java index fa73a67e22..2319998c5f 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Element.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Element.java @@ -10,8 +10,7 @@ * Superclass for all observatory elements. */ public class Element { - - protected JsonObject json; + protected final JsonObject json; public Element(JsonObject json) { this.json = json; @@ -47,7 +46,7 @@ List> getListListInt(String memberName) { return null; } int size = array.size(); - List> result = new ArrayList>(); + List> result = new ArrayList<>(); for (int index = 0; index < size; ++index) { result.add(jsonArrayToListInt(array.get(index).getAsJsonArray())); } @@ -56,7 +55,7 @@ List> getListListInt(String memberName) { private List jsonArrayToListInt(JsonArray array) { int size = array.size(); - List result = new ArrayList(); + List result = new ArrayList<>(); for (int index = 0; index < size; ++index) { result.add(array.get(index).getAsInt()); } @@ -65,7 +64,7 @@ private List jsonArrayToListInt(JsonArray array) { private List jsonArrayToListString(JsonArray array) { int size = array.size(); - List result = new ArrayList(); + List result = new ArrayList<>(); for (int index = 0; index < size; ++index) { result.add(array.get(index).getAsString()); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorKind.java index 746229cb31..ffdc440774 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorKind.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorKind.java @@ -15,11 +15,11 @@ // This is a generated file. -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public enum ErrorKind { /** - * The isolate has encounted an internal error. These errors should be reported as bugs. + * The isolate has encountered an internal error. These errors should be reported as bugs. */ InternalError, diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorObj.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorObj.java index 581265edba..c22b7939c7 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorObj.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorObj.java @@ -21,7 +21,7 @@ /** * An {@link ErrorObj} represents a Dart language level error. This is distinct from an rpc error. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ErrorObj extends Obj { public ErrorObj(JsonObject json) { @@ -41,7 +41,7 @@ public InstanceRef getException() { * What kind of error is this? */ public ErrorKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? ErrorKind.Unknown : ErrorKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorRef.java index d5b30feaa2..5506847777 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ErrorRef.java @@ -21,7 +21,7 @@ /** * {@link ErrorRef} is a reference to an {@link ErrorObj}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ErrorRef extends ObjRef { public ErrorRef(JsonObject json) { @@ -32,7 +32,7 @@ public ErrorRef(JsonObject json) { * What kind of error is this? */ public ErrorKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? ErrorKind.Unknown : ErrorKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Event.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Event.java index 06e83dab04..b606b87ad3 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Event.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Event.java @@ -23,7 +23,7 @@ * An {@link Event} is an asynchronous notification from the VM. It is delivered only when the * client has subscribed to an event stream using the streamListen RPC. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Event extends Response { public Event(JsonObject json) { @@ -150,7 +150,7 @@ public IsolateRef getIsolate() { * What kind of event is this? */ public EventKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? EventKind.Unknown : EventKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/EventKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/EventKind.java index 60ace8a6c4..1348d89002 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/EventKind.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/EventKind.java @@ -19,7 +19,7 @@ * Adding new values to {@link EventKind} is considered a backwards compatible change. Clients * should ignore unrecognized events. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public enum EventKind { /** diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExceptionPauseMode.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExceptionPauseMode.java index ee4ab5c6e4..861edb9254 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExceptionPauseMode.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExceptionPauseMode.java @@ -18,7 +18,7 @@ /** * An {@link ExceptionPauseMode} indicates how the isolate pauses when an exception is thrown. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public enum ExceptionPauseMode { All, diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExtensionData.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExtensionData.java index 8170f932bd..c0639d0f3d 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExtensionData.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ExtensionData.java @@ -20,7 +20,7 @@ /** * An {@link ExtensionData} is an arbitrary map that can have any contents. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ExtensionData extends Element { public ExtensionData(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Field.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Field.java index 6a314e4092..7ce5a1a4ff 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Field.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Field.java @@ -20,7 +20,7 @@ /** * A {@link Field} provides information about a Dart language field or variable. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Field extends Obj { public Field(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FieldRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FieldRef.java index c13265e219..29acd523ba 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FieldRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FieldRef.java @@ -20,7 +20,7 @@ /** * An {@link FieldRef} is a reference to a {@link Field}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class FieldRef extends ObjRef { public FieldRef(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Flag.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Flag.java index c9b8a60761..18ac152d98 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Flag.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Flag.java @@ -20,7 +20,7 @@ /** * A {@link Flag} represents a single VM command line flag. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Flag extends Element { public Flag(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FlagList.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FlagList.java index 4c5133542d..5acceff17d 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FlagList.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FlagList.java @@ -21,7 +21,7 @@ /** * A {@link FlagList} represents the complete set of VM command line flags. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class FlagList extends Response { public FlagList(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Frame.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Frame.java index 748ed540b5..5dbd0b64c5 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Frame.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Frame.java @@ -19,7 +19,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Frame extends Response { public Frame(JsonObject json) { @@ -50,7 +50,7 @@ public int getIndex() { public FrameKind getKind() { if (json.get("kind") == null) return null; - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? FrameKind.Unknown : FrameKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FrameKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FrameKind.java index 27f2405ebf..b824457fde 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FrameKind.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FrameKind.java @@ -18,7 +18,7 @@ /** * A {@link FrameKind} is used to distinguish different kinds of {@link Frame} objects. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public enum FrameKind { AsyncActivation, diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Func.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Func.java index fa38b22476..2073d85376 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Func.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Func.java @@ -20,7 +20,7 @@ /** * A {@link Func} represents a Dart language function. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Func extends Obj { public Func(JsonObject json) { @@ -58,7 +58,7 @@ public String getName() { * @return one of LibraryRef, ClassRef or FuncRef */ public Object getOwner() { - JsonObject elem = (JsonObject)json.get("owner"); + final JsonObject elem = (JsonObject)json.get("owner"); if (elem == null) return null; if (elem.get("type").getAsString().equals("@Library")) return new LibraryRef(elem); diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FuncRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FuncRef.java index 176e64f0eb..31fcb98abe 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FuncRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/FuncRef.java @@ -20,7 +20,7 @@ /** * An {@link FuncRef} is a reference to a {@link Func}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class FuncRef extends ObjRef { public FuncRef(JsonObject json) { @@ -40,7 +40,7 @@ public String getName() { * @return one of LibraryRef, ClassRef or FuncRef */ public Object getOwner() { - JsonObject elem = (JsonObject)json.get("owner"); + final JsonObject elem = (JsonObject)json.get("owner"); if (elem == null) return null; if (elem.get("type").getAsString().equals("@Library")) return new LibraryRef(elem); diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/HeapSpace.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/HeapSpace.java index a23c5971f7..e67d65fc93 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/HeapSpace.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/HeapSpace.java @@ -17,7 +17,7 @@ import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class HeapSpace extends Response { public HeapSpace(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Instance.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Instance.java index b3dc96bba3..00283ccd7e 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Instance.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Instance.java @@ -22,7 +22,7 @@ /** * An {@link Instance} represents an instance of the Dart language class {@link Obj}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Instance extends Obj { public Instance(JsonObject json) { @@ -97,18 +97,6 @@ public ClassRef getClassRef() { return new ClassRef((JsonObject) json.get("class")); } - /** - * The context associated with a Closure instance. - * - * Provided for instance kinds: - * - Closure - * - * Can return null. - */ - public ContextRef getClosureContext() { - return json.get("closureContext") == null ? null : new ContextRef((JsonObject) json.get("closureContext")); - } - /** * The function associated with a Closure instance. * @@ -215,7 +203,7 @@ public boolean getIsMultiLine() { * What kind of instance is this? */ public InstanceKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? InstanceKind.Unknown : InstanceKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { @@ -253,7 +241,12 @@ public int getLength() { } /** - * The referent of a MirrorReference instance. + * TODO(devoncarew): this can return an InstanceRef + * + * The context associated with a Closure instance. + * + * Provided for instance kinds: + * - Closure@Context closureContext [optional]; The referent of a MirrorReference instance. * * Provided for instance kinds: * - MirrorReference @@ -428,7 +421,7 @@ public String getValueAsString() { * Can return null. */ public boolean getValueAsStringIsTruncated() { - JsonElement elem = json.get("valueAsStringIsTruncated"); + final JsonElement elem = json.get("valueAsStringIsTruncated"); return elem != null ? elem.getAsBoolean() : false; } } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceKind.java index fee6c95114..7fea3e50eb 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceKind.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceKind.java @@ -19,7 +19,7 @@ * Adding new values to {@link InstanceKind} is considered a backwards compatible change. Clients * should treat unrecognized instance kinds as {@link PlainInstance}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public enum InstanceKind { /** diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceRef.java index 1c1246e666..f30f8489c8 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/InstanceRef.java @@ -21,7 +21,7 @@ /** * {@link InstanceRef} is a reference to an {@link Instance}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class InstanceRef extends ObjRef { public InstanceRef(JsonObject json) { @@ -39,7 +39,7 @@ public ClassRef getClassRef() { * What kind of instance is this? */ public InstanceKind getKind() { - JsonElement value = json.get("kind"); + final JsonElement value = json.get("kind"); try { return value == null ? InstanceKind.Unknown : InstanceKind.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { @@ -155,7 +155,7 @@ public String getValueAsString() { * Can return null. */ public boolean getValueAsStringIsTruncated() { - JsonElement elem = json.get("valueAsStringIsTruncated"); + final JsonElement elem = json.get("valueAsStringIsTruncated"); return elem != null ? elem.getAsBoolean() : false; } } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Isolate.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Isolate.java index 8da2457e5c..a7df1235ef 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Isolate.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Isolate.java @@ -23,7 +23,7 @@ /** * An {@link Isolate} object provides information about one isolate in the VM. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Isolate extends Response { public Isolate(JsonObject json) { @@ -55,7 +55,7 @@ public ErrorObj getError() { * The current pause on exception mode for this isolate. */ public ExceptionPauseMode getExceptionPauseMode() { - JsonElement value = json.get("exceptionPauseMode"); + final JsonElement value = json.get("exceptionPauseMode"); try { return value == null ? ExceptionPauseMode.Unknown : ExceptionPauseMode.valueOf(value.getAsString()); } catch (IllegalArgumentException e) { @@ -72,6 +72,16 @@ public List getExtensionRPCs() { return json.get("extensionRPCs") == null ? null : getListString("extensionRPCs"); } + /** + * Provided and set to true if the id of an Object is fixed. If true, the id of an Object is + * guaranteed not to change or expire. The object may, however, still be _Collected_. + * + * Can return null. + */ + public boolean getFixedId() { + return json.get("fixedId") == null ? false : json.get("fixedId").getAsBoolean(); + } + /** * The id which is passed to the getIsolate RPC to reload this isolate. */ diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/IsolateRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/IsolateRef.java index e05e9b4037..b8cb311a01 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/IsolateRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/IsolateRef.java @@ -20,13 +20,23 @@ /** * {@link IsolateRef} is a reference to an {@link Isolate} object. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class IsolateRef extends Response { public IsolateRef(JsonObject json) { super(json); } + /** + * Provided and set to true if the id of an Object is fixed. If true, the id of an Object is + * guaranteed not to change or expire. The object may, however, still be _Collected_. + * + * Can return null. + */ + public boolean getFixedId() { + return json.get("fixedId") == null ? false : json.get("fixedId").getAsBoolean(); + } + /** * The id which is passed to the getIsolate RPC to load this isolate. */ diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Library.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Library.java index c9c7a04a42..520a8dc784 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Library.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Library.java @@ -21,7 +21,7 @@ /** * A {@link Library} provides information about a Dart language library. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Library extends Obj { public Library(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryDependency.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryDependency.java index c8696fc32f..ae42411192 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryDependency.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryDependency.java @@ -20,7 +20,7 @@ /** * A {@link LibraryDependency} provides information about an import or export. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class LibraryDependency extends Element { public LibraryDependency(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryRef.java index 92f07b5e5d..d6402902d7 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/LibraryRef.java @@ -20,7 +20,7 @@ /** * {@link LibraryRef} is a reference to a {@link Library}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class LibraryRef extends ObjRef { public LibraryRef(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/MapAssociation.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/MapAssociation.java index d45d8d1118..290416c08b 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/MapAssociation.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/MapAssociation.java @@ -18,7 +18,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class MapAssociation extends Element { public MapAssociation(JsonObject json) { @@ -29,10 +29,10 @@ public MapAssociation(JsonObject json) { * @return one of InstanceRef or Sentinel */ public InstanceRef getKey() { - JsonElement elem = json.get("key"); + final JsonElement elem = json.get("key"); if (!elem.isJsonObject()) return null; - JsonObject child = elem.getAsJsonObject(); - String type = child.get("type").getAsString(); + final JsonObject child = elem.getAsJsonObject(); + final String type = child.get("type").getAsString(); if ("Sentinel".equals(type)) return null; return new InstanceRef(child); } @@ -41,10 +41,10 @@ public InstanceRef getKey() { * @return one of InstanceRef or Sentinel */ public InstanceRef getValue() { - JsonElement elem = json.get("value"); + final JsonElement elem = json.get("value"); if (!elem.isJsonObject()) return null; - JsonObject child = elem.getAsJsonObject(); - String type = child.get("type").getAsString(); + final JsonObject child = elem.getAsJsonObject(); + final String type = child.get("type").getAsString(); if ("Sentinel".equals(type)) return null; return new InstanceRef(child); } diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Message.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Message.java index d16c59a40d..45ecca346f 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Message.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Message.java @@ -21,7 +21,7 @@ * A {@link Message} provides information about a pending isolate message and the function that * will be invoked to handle it. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Message extends Response { public Message(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Null.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Null.java index 35bb956c29..637055ae77 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Null.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Null.java @@ -20,7 +20,7 @@ /** * A {@link Null} object represents the Dart language value null. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Null extends Instance { public Null(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/NullRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/NullRef.java index 8f2502e4fd..f4da440e57 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/NullRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/NullRef.java @@ -20,7 +20,7 @@ /** * {@link NullRef} is a reference to an a {@link Null}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class NullRef extends InstanceRef { public NullRef(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Obj.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Obj.java index b6f289ec31..26130945f4 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Obj.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Obj.java @@ -20,7 +20,7 @@ /** * An {@link Obj} is a persistent object that is owned by some isolate. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class Obj extends Response { public Obj(JsonObject json) { @@ -41,6 +41,16 @@ public ClassRef getClassRef() { return json.get("class") == null ? null : new ClassRef((JsonObject) json.get("class")); } + /** + * Provided and set to true if the id of an Object is fixed. If true, the id of an Object is + * guaranteed not to change or expire. The object may, however, still be _Collected_. + * + * Can return null. + */ + public boolean getFixedId() { + return json.get("fixedId") == null ? false : json.get("fixedId").getAsBoolean(); + } + /** * A unique identifier for an Object. Passed to the getObject RPC to reload this Object. * diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ObjRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ObjRef.java index d7115a2ef5..4c8833b5bd 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ObjRef.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ObjRef.java @@ -20,13 +20,23 @@ /** * {@link ObjRef} is a reference to a {@link Obj}. */ -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ObjRef extends Response { public ObjRef(JsonObject json) { super(json); } + /** + * Provided and set to true if the id of an Object is fixed. If true, the id of an Object is + * guaranteed not to change or expire. The object may, however, still be _Collected_. + * + * Can return null. + */ + public boolean getFixedId() { + return json.get("fixedId") == null ? false : json.get("fixedId").getAsBoolean(); + } + /** * A unique identifier for an Object. Passed to the getObject RPC to load this Object. */ diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ProfileFunction.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ProfileFunction.java index f99e72d727..3a0ba7a5c7 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ProfileFunction.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ProfileFunction.java @@ -18,7 +18,7 @@ import com.google.gson.JsonObject; import java.util.List; -@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"}) +@SuppressWarnings({"WeakerAccess", "unused"}) public class ProfileFunction extends Element { public ProfileFunction(JsonObject json) { diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/RPCError.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/RPCError.java index de22ea5a6a..91d21d70be 100644 --- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/RPCError.java +++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/RPCError.java @@ -1,11 +1,11 @@ /* * Copyright (c) 2015, the Dart project authors. - * + * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at - * + * * http://www.eclipse.org/legal/epl-v10.html - * + * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under @@ -15,7 +15,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; - import org.dartlang.vm.service.internal.VmServiceConst; /** @@ -24,7 +23,7 @@ * Here is an example error response for our [streamListen](#streamlisten) request above. This error * would be generated if we were attempting to subscribe to the _GC_ stream multiple times from the * same client. - * + * *

  * {
  *   "jsonrpc": "2.0",
@@ -38,10 +37,10 @@
  *   "id": "2"
  * }
  * 
- * + *

* In addition the the [error codes](http://www.jsonrpc.org/specification#error_object) specified in * the JSON-RPC spec, we use the following application specific error codes: - * + * *

  * code | message | meaning
  * ---- | ------- | -------
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ReloadReport.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ReloadReport.java
index 631ec0fed5..4ff5792b05 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ReloadReport.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ReloadReport.java
@@ -17,7 +17,7 @@
 
 import com.google.gson.JsonObject;
 
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class ReloadReport extends Response {
 
   public ReloadReport(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Response.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Response.java
index beeae68baf..256fe4a3f1 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Response.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Response.java
@@ -21,7 +21,7 @@
  * Every non-error response returned by the Service Protocol extends {@link Response}. By using the
  * {@link type} property, the client can determine which type of response has been provided.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class Response extends Element {
 
   public Response(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Script.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Script.java
index daac209d72..aa8c7a5932 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Script.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Script.java
@@ -21,7 +21,7 @@
 /**
  * A {@link Script} provides information about a Dart language script.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class Script extends Obj {
 
   public Script(JsonObject json) {
@@ -45,7 +45,8 @@ public String getSource() {
   }
 
   /**
-   * A table encoding a mapping from token position to line and column.
+   * A table encoding a mapping from token position to line and column. This field is null if
+   * sources aren't available.
    *
    * Can return null.
    */
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptList.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptList.java
index 69655d2927..85607d36cd 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptList.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptList.java
@@ -18,7 +18,7 @@
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class ScriptList extends Response {
 
   public ScriptList(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptRef.java
index ed60b96553..aa1e32d565 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptRef.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/ScriptRef.java
@@ -20,7 +20,7 @@
 /**
  * {@link ScriptRef} is a reference to a {@link Script}.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class ScriptRef extends ObjRef {
 
   public ScriptRef(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Sentinel.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Sentinel.java
index aa7a7adae5..be052c69a0 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Sentinel.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Sentinel.java
@@ -21,7 +21,7 @@
 /**
  * A {@link Sentinel} is used to indicate that the normal response is not available.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class Sentinel extends Response {
 
   public Sentinel(JsonObject json) {
@@ -32,7 +32,7 @@ public Sentinel(JsonObject json) {
    * What kind of sentinel is this?
    */
   public SentinelKind getKind() {
-    JsonElement value = json.get("kind");
+    final JsonElement value = json.get("kind");
     try {
       return value == null ? SentinelKind.Unknown : SentinelKind.valueOf(value.getAsString());
     } catch (IllegalArgumentException e) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SentinelKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SentinelKind.java
index d02b940aa3..67684f5296 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SentinelKind.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SentinelKind.java
@@ -18,7 +18,7 @@
 /**
  * A {@link SentinelKind} is used to distinguish different kinds of {@link Sentinel} objects.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public enum SentinelKind {
 
   /**
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceLocation.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceLocation.java
index 9cdb9d0f23..7947c304bd 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceLocation.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceLocation.java
@@ -20,7 +20,7 @@
 /**
  * The {@link SourceLocation} class is used to designate a position or range in some script.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class SourceLocation extends Response {
 
   public SourceLocation(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReport.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReport.java
index 43a92e2066..96641b2e8b 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReport.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReport.java
@@ -22,7 +22,7 @@
  * The {@link SourceReport} class represents a set of reports tied to source locations in an
  * isolate.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class SourceReport extends Response {
 
   public SourceReport(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportCoverage.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportCoverage.java
index f812b03f71..21246e7615 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportCoverage.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportCoverage.java
@@ -22,7 +22,7 @@
  * The {@link SourceReportCoverage} class represents coverage information for one
  * SourceReportRange.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class SourceReportCoverage extends Element {
 
   public SourceReportCoverage(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportKind.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportKind.java
index 7856bd5bf9..76cd1a8fd1 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportKind.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportKind.java
@@ -15,7 +15,7 @@
 
 // This is a generated file.
 
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public enum SourceReportKind {
 
   /**
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportRange.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportRange.java
index 72d664e106..3eedaf183b 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportRange.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/SourceReportRange.java
@@ -22,7 +22,7 @@
  * The {@link SourceReportRange} class represents a range of executable code (function, method,
  * constructor, etc) in the running program. It is part of a SourceReport.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class SourceReportRange extends Element {
 
   public SourceReportRange(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Stack.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Stack.java
index b383cc8e08..aadba76c8b 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Stack.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Stack.java
@@ -18,7 +18,7 @@
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class Stack extends Response {
 
   public Stack(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/StepOption.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/StepOption.java
index 95dbf4ad23..719bcd57bb 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/StepOption.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/StepOption.java
@@ -18,7 +18,7 @@
 /**
  * A {@link StepOption} indicates which form of stepping is requested in a resume RPC.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public enum StepOption {
 
   Into,
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Success.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Success.java
index bd1015a8c6..48fd949be9 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Success.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Success.java
@@ -20,7 +20,7 @@
 /**
  * The {@link Success} type is used to indicate that an operation completed successfully.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class Success extends Response {
 
   public Success(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Tag.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Tag.java
deleted file mode 100644
index 27b23a4b21..0000000000
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Tag.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (c) 2015, the Dart project authors.
- *
- * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.dartlang.vm.service.element;
-
-// This is a generated file.
-
-import com.google.gson.JsonObject;
-
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
-public class Tag extends Response {
-
-  public Tag(JsonObject json) {
-    super(json);
-  }
-
-  public CodeRef getCode() {
-    return new CodeRef((JsonObject) json.get("code"));
-  }
-
-  public int getExclusiveTicks() {
-    return json.get("exclusiveTicks") == null ? -1 : json.get("exclusiveTicks").getAsInt();
-  }
-
-  public int getInclusiveTicks() {
-    return json.get("inclusiveTicks") == null ? -1 : json.get("inclusiveTicks").getAsInt();
-  }
-}
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TimelineEvent.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TimelineEvent.java
index 506cd57b65..61a29d2e70 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TimelineEvent.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TimelineEvent.java
@@ -20,7 +20,7 @@
 /**
  * An {@link TimelineEvent} is an arbitrary map that contains a Trace Event Format event.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class TimelineEvent extends Element {
 
   public TimelineEvent(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArguments.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArguments.java
index eed1fe7beb..4e6869675d 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArguments.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArguments.java
@@ -22,7 +22,7 @@
  * A {@link TypeArguments} object represents the type argument vector for some instantiated generic
  * type.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class TypeArguments extends Obj {
 
   public TypeArguments(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArgumentsRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArgumentsRef.java
index bb70d323c1..e8f65021da 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArgumentsRef.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/TypeArgumentsRef.java
@@ -20,7 +20,7 @@
 /**
  * {@link TypeArgumentsRef} is a reference to a {@link TypeArguments} object.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class TypeArgumentsRef extends ObjRef {
 
   public TypeArgumentsRef(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/UnresolvedSourceLocation.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/UnresolvedSourceLocation.java
index a2b87dfcd9..633ff1c46c 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/UnresolvedSourceLocation.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/UnresolvedSourceLocation.java
@@ -22,7 +22,7 @@
  * location. As such, it is meant to approximate the final location of the breakpoint but it is not
  * exact.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class UnresolvedSourceLocation extends Response {
 
   public UnresolvedSourceLocation(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VM.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VM.java
index e58f08ae51..a107cc483c 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VM.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VM.java
@@ -18,7 +18,7 @@
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class VM extends Response {
 
   public VM(JsonObject json) {
@@ -51,6 +51,13 @@ protected IsolateRef basicGet(JsonArray array, int index) {
     };
   }
 
+  /**
+   * A name identifying this vm. Not guaranteed to be unique.
+   */
+  public String getName() {
+    return json.get("name").getAsString();
+  }
+
   /**
    * The process id for the VM.
    */
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VMRef.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VMRef.java
index 3225902d56..6864ded034 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VMRef.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/VMRef.java
@@ -20,7 +20,7 @@
 /**
  * {@link VMRef} is a reference to a {@link VM} object.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class VMRef extends Response {
 
   public VMRef(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Version.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Version.java
index 6ae08e70c4..49c7276e0c 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Version.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/element/Version.java
@@ -20,7 +20,7 @@
 /**
  * See Versioning.
  */
-@SuppressWarnings({"WeakerAccess", "unused", "UnnecessaryInterfaceModifier"})
+@SuppressWarnings({"WeakerAccess", "unused"})
 public class Version extends Response {
 
   public Version(JsonObject json) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/BlockingRequestSink.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/BlockingRequestSink.java
index 7b0accc707..cf8cd3f795 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/BlockingRequestSink.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/BlockingRequestSink.java
@@ -56,7 +56,7 @@ public void close() {
    * @param errorResponseSink the sink to send error responses to, not {@code null}
    */
   public RequestSink toErrorSink(ResponseSink errorResponseSink, String errorResponseCode,
-      String errorResponseMessage) {
+                                 String errorResponseMessage) {
     ErrorRequestSink errorRequestSink = new ErrorRequestSink(errorResponseSink, errorResponseCode,
         errorResponseMessage);
     synchronized (queue) {
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ErrorRequestSink.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ErrorRequestSink.java
index 1844c33592..9a8fce1c29 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ErrorRequestSink.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ErrorRequestSink.java
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2015, the Dart project authors.
- * 
+ *
  * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  * or implied. See the License for the specific language governing permissions and limitations under
@@ -14,7 +14,6 @@
 package org.dartlang.vm.service.internal;
 
 import com.google.gson.JsonObject;
-
 import org.dartlang.vm.service.logging.Logging;
 
 /**
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/RequestSink.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/RequestSink.java
index dbb38c6f9e..ef2a5630c0 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/RequestSink.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/RequestSink.java
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2015, the Dart project authors.
- * 
+ *
  * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  * or implied. See the License for the specific language governing permissions and limitations under
@@ -21,7 +21,7 @@
 public interface RequestSink {
   /**
    * Put request into the sink.
-   * 
+   *
    * @param request the request to put, not {@code null}.
    */
   void add(JsonObject request);
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ResponseSink.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ResponseSink.java
index 1530732a5b..5ac1db1200 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ResponseSink.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/ResponseSink.java
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2015, the Dart project authors.
- * 
+ *
  * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  * or implied. See the License for the specific language governing permissions and limitations under
@@ -21,7 +21,7 @@
 public interface ResponseSink {
   /**
    * Put response into the sink.
-   * 
+   *
    * @param response the response to put, not {@code null}.
    */
   void add(JsonObject response) throws Exception;
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/VmServiceConst.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/VmServiceConst.java
index d0c1b377f2..3659c3c80f 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/VmServiceConst.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/VmServiceConst.java
@@ -11,50 +11,50 @@
  * or implied. See the License for the specific language governing permissions and limitations under
  * the License.
  */
- package org.dartlang.vm.service.internal;
+package org.dartlang.vm.service.internal;
 
 /**
  * JSON constants used when communicating with the VM observatory service.
  */
 public interface VmServiceConst {
-  public static final String CODE = "code";
-  public static final String ERROR = "error";
-  public static final String EVENT = "event";
-  public static final String ID = "id";
-  public static final String MESSAGE = "message";
-  public static final String METHOD = "method";
-  public static final String PARAMS = "params";
-  public static final String RESULT = "result";
-  public static final String STREAM_ID = "streamId";
-  public static final String TYPE = "type";
-  public static final String JSONRPC = "jsonrpc";
-  public static final String JSONRPC_VERSION = "2.0";
-  public static final String DATA = "data";
+  static final String CODE = "code";
+  static final String ERROR = "error";
+  static final String EVENT = "event";
+  static final String ID = "id";
+  static final String MESSAGE = "message";
+  static final String METHOD = "method";
+  static final String PARAMS = "params";
+  static final String RESULT = "result";
+  static final String STREAM_ID = "streamId";
+  static final String TYPE = "type";
+  static final String JSONRPC = "jsonrpc";
+  static final String JSONRPC_VERSION = "2.0";
+  static final String DATA = "data";
 
   /**
    * Parse error	Invalid JSON was received by the server.
    * An error occurred on the server while parsing the JSON text.
    */
-  public static final int PARSE_ERROR = -32700;
+  static final int PARSE_ERROR = -32700;
 
   /**
    * Invalid Request	The JSON sent is not a valid Request object.
    */
-  public static final int INVALID_REQUEST = -32600;
+  static final int INVALID_REQUEST = -32600;
 
   /**
    * Method not found	The method does not exist / is not available.
    */
-  public static final int METHOD_NOT_FOUND = -32601;
+  static final int METHOD_NOT_FOUND = -32601;
 
   /**
    * Invalid params	Invalid method parameter(s).
    */
-  public static final int INVALID_PARAMS = -32602;
+  static final int INVALID_PARAMS = -32602;
 
   /**
    * Server error	Reserved for implementation-defined server-errors.
    * -32000 to -32099
    */
-  public static final int SERVER_ERROR = -32000;
+  static final int SERVER_ERROR = -32000;
 }
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/WebSocketRequestSink.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/WebSocketRequestSink.java
index 63002de0e6..8a9ebfd5ac 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/WebSocketRequestSink.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/internal/WebSocketRequestSink.java
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2015, the Dart project authors.
- * 
+ *
  * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  * or implied. See the License for the specific language governing permissions and limitations under
@@ -14,11 +14,9 @@
 package org.dartlang.vm.service.internal;
 
 import com.google.gson.JsonObject;
-
-import org.dartlang.vm.service.logging.Logging;
-
 import de.roderick.weberknecht.WebSocket;
 import de.roderick.weberknecht.WebSocketException;
+import org.dartlang.vm.service.logging.Logging;
 
 /**
  * An {@link WebSocket} based implementation of {@link RequestSink}.
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logger.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logger.java
index 04fcef28a5..cf1bd03148 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logger.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logger.java
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2012, the Dart project authors.
- * 
+ *
  * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  * or implied. See the License for the specific language governing permissions and limitations under
@@ -23,7 +23,7 @@ public interface Logger {
   /**
    * Implementation of {@link Logger} that does nothing.
    */
-  public class NullLogger implements Logger {
+  class NullLogger implements Logger {
     @Override
     public void logError(String message) {
     }
@@ -41,35 +41,35 @@ public void logInformation(String message, Throwable exception) {
     }
   }
 
-  public static final Logger NULL = new NullLogger();
+  static final Logger NULL = new NullLogger();
 
   /**
    * Log the given message as an error.
-   * 
+   *
    * @param message an explanation of why the error occurred or what it means
    */
-  public void logError(String message);
+  void logError(String message);
 
   /**
    * Log the given exception as one representing an error.
-   * 
-   * @param message an explanation of why the error occurred or what it means
+   *
+   * @param message   an explanation of why the error occurred or what it means
    * @param exception the exception being logged
    */
-  public void logError(String message, Throwable exception);
+  void logError(String message, Throwable exception);
 
   /**
    * Log the given informational message.
-   * 
+   *
    * @param message an explanation of why the error occurred or what it means
    */
-  public void logInformation(String message);
+  void logInformation(String message);
 
   /**
    * Log the given exception as one representing an informational message.
-   * 
-   * @param message an explanation of why the error occurred or what it means
+   *
+   * @param message   an explanation of why the error occurred or what it means
    * @param exception the exception being logged
    */
-  public void logInformation(String message, Throwable exception);
+  void logInformation(String message, Throwable exception);
 }
diff --git a/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logging.java b/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logging.java
index d89451d6cb..867cd1e795 100644
--- a/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logging.java
+++ b/third_party/vmServiceDrivers/org/dartlang/vm/service/logging/Logging.java
@@ -1,11 +1,11 @@
 /*
  * Copyright (c) 2014, the Dart project authors.
- * 
+ *
  * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
  * in compliance with the License. You may obtain a copy of the License at
- * 
+ *
  * http://www.eclipse.org/legal/epl-v10.html
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the License
  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
  * or implied. See the License for the specific language governing permissions and limitations under
diff --git a/tool/plugin/lib/lint.dart b/tool/plugin/lib/lint.dart
index 21ddcdbe38..079a9b2e03 100644
--- a/tool/plugin/lib/lint.dart
+++ b/tool/plugin/lib/lint.dart
@@ -77,6 +77,9 @@ class LintCommand extends Command {
       'javax.annotation.Nullable',
       'org.apache.commons.lang3.StringUtils',
       'org.apache.commons.lang3.builder.HashCodeBuilder',
+      // Not technically a bad import, but not all IntelliJ platforms provide
+      // this library.
+      'org.apache.commons.io.',
     ];
 
     for (var import in proscribedImports) {
@@ -84,7 +87,7 @@ class LintCommand extends Command {
 
       final result = Process.runSync(
         'git',
-        ['grep', 'import $import;'],
+        ['grep', 'import $import'],
       );
 
       final String results = result.stdout.trim();
diff --git a/tool/plugin/lib/plugin.dart b/tool/plugin/lib/plugin.dart
index be0ff44973..370caef852 100644
--- a/tool/plugin/lib/plugin.dart
+++ b/tool/plugin/lib/plugin.dart
@@ -35,6 +35,8 @@ const Map plugins = const {
   'io.flutter.as': '10139', // Currently unused.
 };
 
+const int cloudErrorFileMaxSize = 1000; // In bytes.
+
 String rootPath;
 int pluginCount = 0;
 
@@ -97,8 +99,9 @@ List findJars(String path) {
   final dir = new Directory(path);
   return dir
       .listSync(recursive: true, followLinks: false)
-      .where((e) => e.path.endsWith('.jar'))
-      .toList();
+      .where((e) => e is File && e.path.endsWith('.jar'))
+      .toList()
+      .cast();
 }
 
 List findJavaFiles(String path) {
@@ -141,7 +144,8 @@ void genTravisYml(List specs) {
   var file = new File(p.join(rootPath, '.travis.yml'));
   var env = '';
   for (var spec in specs) {
-    env += '  - IDEA_VERSION=${spec.version}\n';
+    if (!spec.untilBuild.contains('SNAPSHOT'))
+      env += '  - IDEA_VERSION=${spec.version}\n';
   }
 
   var templateFile = new File(p.join(rootPath, '.travis_template.yml'));
@@ -273,7 +277,7 @@ String substituteTemplateVariables(String line, BuildSpec spec) {
         return spec.untilBuild;
       case 'VERSION':
         return spec.release == null
-            ? ''
+            ? 'SNAPSHOT'
             : '${spec.release}.${++pluginCount}';
       case 'CHANGELOG':
         return spec.changeLog;
@@ -313,11 +317,15 @@ Future zip(String directory, String outFile) async {
   return await exec('zip', args, cwd: p.dirname(directory));
 }
 
-void _copyFile(File file, Directory to) {
+String _convertToTar(String path) =>
+    path.replaceFirst('.zip', '.tar.gz', path.length - 5);
+
+void _copyFile(File file, Directory to, {String filename = ''}) {
   if (!to.existsSync()) {
     to.createSync(recursive: true);
   }
-  final target = new File(p.join(to.path, p.basename(file.path)));
+  if (filename == '') filename = p.basename(file.path);
+  final target = new File(p.join(to.path, filename));
   target.writeAsBytesSync(file.readAsBytesSync());
 }
 
@@ -339,6 +347,10 @@ void _copyResources(Directory from, Directory to) {
   }
 }
 
+bool _isValidDownloadArtifact(File archiveFile) =>
+    archiveFile.existsSync() &&
+    archiveFile.lengthSync() > cloudErrorFileMaxSize;
+
 String _shorten(String str) {
   return str.length < 200
       ? str
@@ -349,7 +361,7 @@ Stream _toLineStream(Stream> s, Encoding encoding) =>
     s.transform(encoding.decoder).transform(const LineSplitter());
 
 class Artifact {
-  final String file;
+  String file;
   final bool bareArchive;
   String output;
 
@@ -362,6 +374,13 @@ class Artifact {
   bool get isZip => file.endsWith('.zip');
 
   String get outPath => p.join(rootPath, 'artifacts', output);
+
+  // Historically, Android Studio has been distributed as a zip file.
+  // Recent distros are packaged as gzip'd tar files.
+  void convertToTar() {
+    if (!isZip) return;
+    file = _convertToTar(file);
+  }
 }
 
 class ArtifactManager {
@@ -391,25 +410,55 @@ class ArtifactManager {
 
     var result = 0;
     for (var artifact in artifacts) {
-      final path = 'artifacts/${artifact.file}';
-      if (FileSystemEntity.isFileSync(path)) {
+      var doDownload = true;
+
+      void alreadyDownloaded(String path) {
         log('$path exists in cache');
+        doDownload = false;
+      }
+
+      var path = 'artifacts/${artifact.file}';
+      if (FileSystemEntity.isFileSync(path)) {
+        alreadyDownloaded(path);
       } else {
-        log('downloading $path...');
-        result = await curl('$base/${artifact.file}', to: path);
-        if (result != 0) {
-          log('download failed');
-          break;
+        if (artifact.isZip) {
+          var tarPath = _convertToTar(path);
+          if (FileSystemEntity.isFileSync(tarPath)) {
+            artifact.convertToTar();
+            alreadyDownloaded(tarPath);
+          }
         }
-        var archiveFile = new File(path);
-        if (!archiveFile.existsSync() || archiveFile.lengthSync() < 200) {
-          // If the file is missing the server returns a small file containing
-          // an error message. Delete it and fail. The smallest file we store in
-          // the cloud is over 700K.
-          log('archive file not found: $base/${artifact.file}');
-          archiveFile.deleteSync();
-          result = 1;
-          break;
+        if (doDownload) {
+          log('downloading $path...');
+          result = await curl('$base/${artifact.file}', to: path);
+          if (result != 0) {
+            log('download failed');
+            break;
+          }
+          var archiveFile = new File(path);
+          if (!_isValidDownloadArtifact(archiveFile)) {
+            // If the file is missing the server returns a small file containing
+            // an error message. Delete it and try again. The smallest file we
+            // store in the cloud is over 700K.
+            log('archive file not found: $base/${artifact.file}');
+            archiveFile.deleteSync();
+            if (artifact.isZip) {
+              artifact.convertToTar();
+              path = 'artifacts/${artifact.file}';
+              result = await curl('$base/${artifact.file}', to: path);
+              if (result != 0) {
+                log('download failed');
+                break;
+              }
+              var archiveFile = new File(path);
+              if (!_isValidDownloadArtifact(archiveFile)) {
+                log('archive file not found: $base/${artifact.file}');
+                archiveFile.deleteSync();
+                result = 1;
+                break;
+              }
+            }
+          }
         }
       }
 
@@ -535,35 +584,56 @@ class BuildCommand extends ProductCommand {
       // TODO: Remove this when we no longer support AS 3.3 (IJ 2018.2.5) or AS 3.4
       var files = {};
       var processedFile, source;
-      if ((spec.version == '3.3') || (spec.version == '3.4')) {
+      if (spec.version == '3.3.2') {
+        log('spec.version: ${spec.version}');
+        processedFile = File(
+            'flutter-studio/src/io/flutter/project/FlutterProjectCreator.java');
+        source = processedFile.readAsStringSync();
+        files[processedFile] = source;
+        source = source.replaceAll('List', 'List');
+        processedFile.writeAsStringSync(source);
+      }
+      if (spec.version == '3.3.2' ||
+          spec.version == '3.4') {
         log('spec.version: ${spec.version}');
-        if (spec.version == '3.3') {
-          processedFile = File(
-              'flutter-studio/src/io/flutter/project/FlutterProjectCreator.java');
-          source = processedFile.readAsStringSync();
-          files[processedFile] = source;
-          source = source.replaceAll('List', 'List');
-          processedFile.writeAsStringSync(source);
-
-          processedFile = File(
-              'flutter-studio/src/io/flutter/profiler/FlutterStudioProfilers.java');
-          source = processedFile.readAsStringSync();
-          files[processedFile] = source;
-          source = source.replaceAll('//changed(ProfilerAspect.DEVICES);',
-              'changed(ProfilerAspect.DEVICES);');
-          processedFile.writeAsStringSync(source);
-        }
-
         processedFile = File(
-            'flutter-studio/src/io/flutter/android/AndroidModuleLibraryManager.java');
+            'flutter-studio/src/io/flutter/module/FlutterDescriptionProvider.java');
         source = processedFile.readAsStringSync();
         files[processedFile] = source;
+        source = source.replaceAll('Icon getIcon()', 'Image getIcon()');
         source = source.replaceAll(
-            'import static com.google.wireless.android.sdk.stats.GradleSyncStats.Trigger.TRIGGER_PROJECT_MODIFIED;',
-            '');
+          'return FlutterIcons.AndroidStudioNewProject;',
+          'return IconUtil.toImage(FlutterIcons.AndroidStudioNewProject);',
+        );
         source = source.replaceAll(
-            'new GradleSyncInvoker.Request(TRIGGER_PROJECT_MODIFIED);',
-            'GradleSyncInvoker.Request.projectModified();');
+          'return FlutterIcons.AndroidStudioNewPackage;',
+          'return IconUtil.toImage(FlutterIcons.AndroidStudioNewPackage);',
+        );
+        source = source.replaceAll(
+          'return FlutterIcons.AndroidStudioNewPlugin;',
+          'return IconUtil.toImage(FlutterIcons.AndroidStudioNewPlugin);',
+        );
+        source = source.replaceAll(
+          'return FlutterIcons.AndroidStudioNewModule;',
+          'return IconUtil.toImage(FlutterIcons.AndroidStudioNewModule);',
+        );
+        processedFile.writeAsStringSync(source);
+      }
+      if (spec.version != '3.6') {
+        // There is no 3.6 yet, but these edits will be needed for next canary
+        log('spec.version: ${spec.version}');
+        processedFile = File(
+            'flutter-studio/src/io/flutter/project/FlutterProjectModel.java');
+        source = processedFile.readAsStringSync();
+        files[processedFile] = source;
+        source = source.replaceAll('addListener(()', 'addListener(sender');
+        processedFile.writeAsStringSync(source);
+
+        processedFile = File(
+            'flutter-studio/src/io/flutter/project/FlutterSettingsStep.java');
+        source = processedFile.readAsStringSync();
+        files[processedFile] = source;
+        source = source.replaceAll('listen', 'receive');
         processedFile.writeAsStringSync(source);
       }
 
@@ -572,7 +642,7 @@ class BuildCommand extends ProductCommand {
       } finally {
         // Restore sources.
         files.forEach((file, src) {
-          log('Reestoring ${file.path}');
+          log('Restoring ${file.path}');
           file.writeAsStringSync(src);
         });
 
@@ -604,6 +674,11 @@ class BuildCommand extends ProductCommand {
         log('jar failed: ${result.toString()}');
         return new Future(() => result);
       }
+      if (spec.isTestTarget && !isReleaseMode) {
+        _copyFile(File('build/flutter-intellij/lib/flutter-intellij.jar'),
+            Directory(testTargetPath(spec)),
+            filename: 'io.flutter.jar');
+      }
       if (spec.isAndroidStudio) {
         result = await jar(
             'build/studio', 'build/flutter-intellij/lib/flutter-studio.jar');
@@ -619,6 +694,10 @@ class BuildCommand extends ProductCommand {
         log('zip failed: ${result.toString()}');
         return new Future(() => result);
       }
+      if (spec.copyIjVersion && !isReleaseMode) {
+        _copyFile(File(releasesFilePath(spec)), Directory(ijVersionPath(spec)),
+            filename: 'flutter-intellij.zip');
+      }
       separator('BUILT');
       log('${releasesFilePath(spec)}');
     }
@@ -704,6 +783,8 @@ class BuildSpec {
   // Build targets
   final String name;
   final String version;
+  final String ijVersion;
+  final bool isTestTarget;
   final String ideaProduct;
   final String ideaVersion;
   final String dartPluginVersion;
@@ -725,15 +806,19 @@ class BuildSpec {
       : release = releaseNum,
         name = json['name'],
         version = json['version'],
+        ijVersion = json['ijVersion'] ?? null,
         ideaProduct = json['ideaProduct'],
         ideaVersion = json['ideaVersion'],
         dartPluginVersion = json['dartPluginVersion'],
         sinceBuild = json['sinceBuild'],
         untilBuild = json['untilBuild'],
-        filesToSkip = json['filesToSkip'] ?? [] {
+        filesToSkip = json['filesToSkip'] ?? [],
+        isTestTarget = (json['isTestTarget'] ?? 'false') == 'true' {
     createArtifacts();
   }
 
+  bool get copyIjVersion => isAndroidStudio && ijVersion != null;
+
   bool get isAndroidStudio => ideaProduct.contains('android-studio');
 
   bool get isReleaseMode => release != null;
@@ -961,6 +1046,18 @@ abstract class ProductCommand extends Command {
     return filePath;
   }
 
+  String testTargetPath(BuildSpec spec) {
+    var subDir = 'release_master';
+    var filePath = p.join(rootPath, 'releases', subDir, 'test_target');
+    return filePath;
+  }
+
+  String ijVersionPath(BuildSpec spec) {
+    var subDir = 'release_master';
+    var filePath = p.join(rootPath, 'releases', subDir, spec.ijVersion);
+    return filePath;
+  }
+
   Future doit();
 
   Future run() async {
diff --git a/tool/plugin/test/plugin_test.dart b/tool/plugin/test/plugin_test.dart
index 49f27d456e..9d169491c1 100644
--- a/tool/plugin/test/plugin_test.dart
+++ b/tool/plugin/test/plugin_test.dart
@@ -38,7 +38,7 @@ void main() {
               'android-studio',
               'android-studio',
               'android-studio',
-              'ideaIC',
+              'ideaIU',
             ]));
       });
     });
@@ -54,7 +54,7 @@ void main() {
               'android-studio',
               'android-studio',
               'android-studio',
-              'ideaIC',
+              'ideaIU',
             ]));
       });
     });
@@ -70,7 +70,7 @@ void main() {
               'android-studio',
               'android-studio',
               'android-studio',
-              'ideaIC',
+              'ideaIU',
             ]));
       });
     });
@@ -147,10 +147,10 @@ void main() {
       expect(
           cmd.paths.map((p) => p.substring(p.indexOf('releases'))),
           orderedEquals([
-            'releases/release_19/3.3/flutter-intellij.zip',
+            'releases/release_19/3.3.2/flutter-intellij.zip',
             'releases/release_19/3.4/flutter-intellij.zip',
-            'releases/release_19/2018.3/flutter-intellij.zip',
-            'releases/release_19/2019.1/flutter-intellij.zip',
+            'releases/release_19/3.5/flutter-intellij.zip',
+            'releases/release_19/2019.2/flutter-intellij.zip',
           ]));
     });
   });