Dart-code: Prompt the user for the startup script when pressing F5 on a Dart project (rather than opening launch.json)

Created on 8 Feb 2018  路  33Comments  路  Source: Dart-Code/Dart-Code

Flutter works better now (no launch.json needed, just press F5) but with Dart we need to know which script to use.

I suggest when we don't have a program we either open a file open dialog or give the user a pick list of all .dart files inside the bin, test and tools folders to pick from (@devoncarew, @mit-mit any preferences?) and then ensure this gets stashed into launch.json so they don't have to do it on every F5.

If the current open file is already inside bin/test/tool maybe we should just assume it and skip the prompt.

stale

All 33 comments

If the current open file is already inside bin/test/tool maybe we should just assume it and skip the prompt.

A dialog prompt, or using the currently active file, both sounds pretty reasonable here.

Let's say the user has bin/main.dart open and hits F5 and we decide to run that one (instead of prompting). Then the user closes the main.dart file and hits F5 again; do we expect it to launch the same as last time, or prompt?

I think it should prompt (seems weird for the file we chose to be "sticky").

However, if the user had no file open and hit F5 and we gave them a picker, they selected bin/main.dart, ran it, then they press F5 again, what do we expect the behaviour to be?

For the first one, being sticky seems bad (the user was just running the active script - why did that become the "default"). But for the second one, it seems like being sticky would be right (the user already told us what to run, why would we ask again?). I'm not sure if that inconsistency seems good.

WDYT? (cc @mit-mit)

I'd err on the side of showing fewer dialogs - they tend to interrupt user flow.

I agree; I'm just worried about doing something unexpected (or bad - like running the wrong script). For ex, if we burn the select file into launch.json then this flow is a bit weird:

  1. Open bin/script1.dart and hit F5
  2. Open bin/script2.dart and hit F5

If we burned the first filename into launch.json then either:

  1. We run the wrong script in step 2
  2. We always prioritise the active file over what's in launch.json

While 2 sounds better, it could be annoying if someone has explicitly set the path in launch.json and it seems like we're ignoring it when they're hitting F5 after scanning through some other files.

I'll have a play around - maybe I can burn the "last run" script into workspace settings and use that as a fallback if don't have a bin/test/tool file active, but if something is explicitly put into launch.json then we use that as the user would expect.

After playing around with this a little more, I think the current behaviour (open up launch.json) isn't actually that bad for a "project". It avoids us having to make guesses and it's only a one-off thing for the user (and it also makes it obvious how they could add additional files if they wanted multiple entry points to switch between).

I was going to suggest that if there was no pusbpec we could just "run the current file" as a way of making it easier to write simple shell scripts (etc.), but I'm not sure that's a common scenario (how likely is it that can you run a script without a pubspec/any packages?).

I think the current behaviour (open up launch.json) isn't actually that bad for a "project"

Sounds great!

if there was no pusbpec we could just "run the current file" as a way of making it easier to write simple shell scripts (etc.), but I'm not sure that's a common scenario

This was more of a use case very early on in Dart, but at this point you really do need a pubspec file for 99.99% of apps. If they do want to run apps w/o a pubspec, I presume they could create a launch.json file for that themselves.

It'd still work just hitting F5 - it'll create and open a launch.json for them. I was trying to figure out if we could support just "open a Dart script and hit F5" to run a script (with no launch.json) but I think the advantages are minimal and the chances of getting it wrong (all the notes above about if people change script, etc.) so I think we should just go with what we have.

So, closing this as WontFix. Behaviour for v2.9 is going to be (is already):

Flutter:

  • Pressing F5 with no launch.json just runs the project with no prompts 馃帀
  • Pressing F5 with a launch.json will use the settings in there (and this is how you'd set args like `--flavor or change the entry point if you wanted)

Dart

  • Pressing F5 with no launch.json will create and open one with the startup script set as bin/main.dart by default (the user should customise this)
  • Pressing F5 with a launch.json (including the one created above) will run the app

So, not all that different to today, except:

  • No prompts asking whether you're doing Dart or Flutter
  • launch.json is completely optional for Flutter

Here is how I'd prefer it to work:

  1. If possible to have no interactions with launch.json file
  2. When hit F5 on a file in bin/ or test/ run that file and remember it as lastRunScript
  3. When hit F5 on a *.dart file anywhere else and lastRunScript != null then run lastRunScript.

@pulyaevskiy I did like that idea earlier, but I think it's a bit too magic now. It's hard to describe to the user what's going on.

What about if, when you first hit F5, if you have a script open from bin/test/tool we pre-populate that script into the launch.json (instead of always bin/main.dart)? It would mostly work as you want, you just have to hit F5 twice the very first time, but it would expose the launch.json to you so if you need to change the script (or want to add additional configs for easier switching) you would have a better idea of its presence?

So for each executable file I'd have a separate entry in launch.json?

Say, I have 10 test files in my test/ folder. When I hit F5 on any of them for the first time I will see launch.json with a new entry for that file?

I guess it could work... but I feel like this interaction with launch.json can be handled automatically.

I just did a test on a small project. I deleted launch.json and did following.

  1. Opened example/main.dart and hit F5 (btw, I think we should add example/ folder in whitelist with bin and test)
  2. A dialog with selection "Dart Command Line" or "Flutter" showed up, I selected CLI
  3. It opened launch.json with bin/main.dart hardcoded in there.
  4. I had to modify script path to example/main.dart.
  5. I also changed name to Dart CLI: example/main.dart
  6. At this point I can run this file
  7. I opened test/some_test.dart and hit F5
  8. It runs example/main.dart :(

If I understand your version then it would look like:

  1. Open example/main.dart and hit F5
  2. A dialog with selection "Dart CLI" or "Flutter". Select "Dart CLI"
  3. launch.json pops up in the editor with new entry where program: ${workspaceRoot}/example/main.dart and name: Dart CLI: example/main.dart.
  4. Hit F5, it runs my example file
  5. Open test/some_test.dart and hit F5
  6. Repeat steps 2-4 but with new file name

If that's possible it would make me happier. :) Ideally, I'd make step 3 a background task so I don't have to see launch.json at all.

Just installed 2.9.0-beta.1 and behavior is similar except I don't get a dialog to select CLI vs Flutter (which actually made sense to me before :) ).

Say, I have 10 test files in my test/ folder. When I hit F5 on any of them for the first time I will see launch.json with a new entry for that file?

Not quite, I must've described it badly, it'd only create a launch.json the first time, then subsequent F5s will just re-run that one (which I think is kinda what you'd get with the lastRunScript idea?).

That said, I believe in launch.json you can put something like ${file} as the entry point, and then it will use the current open file (obviously this has drawbacks, but it might still work out better).

In short, my suggestion is to leave it as it is today, except:

  1. A dialog with selection "Dart Command Line" or "Flutter" showed up, I selected CLI

That step is gone in v2.9.0 (grab the beta).

I wonder if we could can add a command/button on toolbar/keybind for "run current file" that works independently of the normal launch.json.. Kinda feels like we're trying to bend Code in ways it doesn't want to go though.

I need to also do some testing of whether once launch.json is created, whether out debug provider still gets invoked to mess with things (if not, it might limit what we can do once the file exists).

Try out how it works today in v2.9.0 beta anyway, and if it seems like we might have ideas to improve it, we can re-open this and discuss more.

Just installed 2.9.0-beta.1 and behavior is similar except I don't get a dialog to select CLI vs Flutter (which actually made sense to me before :) ).

Do you mean you think you should get the dialog? We removed it because it was somewhat redundant - we already knew before it appeared whether you were debugging Dart or Flutter; but since Code didn't know it was showing all the options. Now we just have one debug "type" for Code, but dynamically set the entry script based on the project type.

Not quite, I must've described it badly, it'd only create a launch.json the first time, then subsequent F5s will just re-run that one

I normally have more than 1 executable file (including tests) in my projects so creating a launch.json with single file hardcoded in there is not very useful. I'm forced to manually create config entries for all the rest.

That said, I believe in launch.json you can put something like ${file} as the entry point,

That's what I used before and it's not working well.

That said, I realized that having separate entry in launch config for each executable actually works exactly as I'd like it to:

  1. I can select which file to run in the dropdown which is nice
  2. I can re-run currently selected file from anywhere in the workspace which is great

But:

  1. Hitting F5 does not switch launch drowdown to currently open file even if I have a config entry for it. Fixing this would be awesome
  2. I have to create config entries manually for each file. Automating this would be really awesome.

Hopefully it makes more sense now.

Hitting F5 does not switch launch drowdown to currently open file even if I have a config entry for it. Fixing this would be awesome

Interesting idea! I don't know if we can do it, but it seems like that addresses many of the issues - you get the ability to run the current file, but only when we know it's a valid target (eg. there's a launch config for it).

I have to create config entries manually for each file. Automating this would be really awesome.

This would be great, but I don't know how we could do it well. When you hit F5, we need to make a decision about whether to a) add this file to the launch.json or b) run the previously selected file.

I'll do some more digging.

When you hit F5, we need to make a decision about whether to a) add this file to the launch.json or b) run the previously selected file.

The way I see it is:

  1. User hits F5 while having file {dir}/foo.dart opened in the editor
  2. if {dir} is in [bin, test, example, tool] (any folder where executables are allowed) then
    a. if there is a launch config entry for this file, use that config and run it
    b. if there is no launch config - add entry to launch.json for this file and run it. Show a little notification "Launch config created for this file [Open launch config]"
  3. If `{dir} is not in any of the executable folders then fallback to default which is execute whatever is currently selected in the dropdown. If there is no configuration file yet, I'd prompt user to select first executable file to run.

There can also be rules like if a file is in lib/ never try to execute it to simplify detection logic.

Also specifically for tests I'd use a glob test/**_test.dart instead of folder prefix test/* because we tend to place some utilities and fixtures in test folder too.

Related idea: #416 - Add run/debug option to explorer/editor context menus

@pulyaevskiy FWIW I've been working on tests on the analysis server and I set program to ${file} in my launch.json and it's been excellent. I used to switch to the terminal then keep running tests by changing the filename (I'm working through many files), but now I just hit F5).

It's not perfect, I have to remember to Alt+Left back to the test file before running if I've jumped into a helper, but definitely not a bad experience :-)

It's not perfect, I have to remember to Alt+Left back to the test file before running

Exactly. And this is my main concern. I don't spend a lot of time in test files, most of the time I have implementation file active.

Especially when doing TDD-style development (write test first, implementation second) or debugging (breakpoints are almost never in test files). In such workflow you run a test, it fails, you open implementation, tweak it, re-run the test, it fails, you repeat. With many open files(tabs) going back to the test file as actually even more tedious than going back to the terminal (which is always Cmd+Tab away).

I've been using ${file} for a while, but recently switched to per-file config entries as it aligns a bit better with my workflow.

I have to create entries by hand but it's do-and-forget action so I can live with it.
If only F5 would allow to auto-switch active config to the current file (or prompt to create a config if not exists) it would be really nice.

v2.11 Beta 2 has some additional changes that solves some (but not all) of the things in here. If you have no launch.json and hit F5 then your current file will be invoked if it's inside test/bin/tool.

Thoughts for improving this are detecting if the script has a main method in it and if not, prompt the user:

The current file cannot be executed, would you like to run the last run script?
[ Run bin/main.dart ] [ Create launch.json ]

bin/main.dart would be replaced with the last file that was run. We can add a setting to "always run the last run script if the current file is not executable" which you can set globally, or at workspace level. It would function the same as clicking the left button on the prompt above. If there is no last run script we'll still show a prompt, but change the text somehow (maybe take a guess at the script to run) - I don't want to just create the launch.json because once it's created, you have to maintain it (and I think F5-without-launch.json is best for most people).

@pulyaevskiy What do you think?

Pretty much +1 to everything you said there. I like the logic you described around prompting and agree that if we can get away without even creating launch.json it would be the best.

Additional thoughts on detection logic.

  1. I just tried to hit F5 on a file in tool and it started my Flutter app even though it printed out Launching tool/gen_version_txt.dart on iPhone 7 Plus in debug mode...
  2. Checking for main function would definitely be useful.
  3. To fix the (1) we could also check if a script imports any package:flutter/* and if not then just use dart executable.

Also wondering if we can show a prompt as you described, does VSCode allow to add a checkbox in there so that corresponding setting is easier to discover? E.g.:

The current file cannot be executed, would you like to run the last run script?
[ Run bin/main.dart ] [ Create launch.json ]
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
[x] Always run the last script if current file is not executable

P.S.: There is also web-targeting/JS-compiled projects which would complicate this logic quite a bit more, but it'd probably be best to address separately.
P.P.S: Awesome to see this coming along!

I just tried to hit F5 on a file in tool and it started my Flutter app

I presume you don't have a launch.json?

If not, the code here should handle this and set program to your tool script:

https://github.com/Dart-Code/Dart-Code/blob/master/src/providers/debug_config_provider.ts#L40-L48

If you're able to clone master and repro this with a breakpoint in that method, I'd be curious to know what's going on.

Also wondering if we can show a prompt as you described, does VSCode allow to add a checkbox in there

Unfortunately not. My original plan was another button to "always do this", but it could be ambigious (does it always mean run this specific script that was the last one I ran, or always run the one that was "last run" at that point in time?) so I figured we could let users that want it to just opt-in with a setting (or at least, start that way - maybe we can improve on it - like prompt them with a different message when they've clicked it 5 times or something).

P.S.: There is also web-targeting/JS-compiled projects which would complicate this logic quite a bit more, but it'd probably be best to address separately.

Currently we don't have any web support so people are on their own anyway (eg. we have no F5 support). If anything here is causing new problems for anyone doing web though, then I'm happy to treat as a bug (but I think it's unlikely since we have no functionality to break).

I presume you don't have a launch.json?

I did, actually. :) Removed it, got the same result, it tries to execute flutter run.
The lines you mentioned look correct, I think the issue is in these lines:
https://github.com/Dart-Code/Dart-Code/blob/21ab261837e324b7c18730ed4e4f5b263fefcfb0/src/providers/debug_config_provider.ts#L74-L78

My project is a Flutter project, but the executable file in question is a plain Dart script. So it needs to be introspected as I mentioned above (if no package:flutter/ imports => use dart binary).

Oh, so this isn't a Flutter unit test?

Currently we don't consider running plain dart files from inside Flutter.. Can you put the details into a new issue? It might be complicated to fix.. we worked to having a single debugger that can guess what the user is running, but this is more complicated... I don't want to grep the files to try and decide which to launch (since the entry point may just reference another script that then pulls in Flutter), we may need to come up with a way for the user to override this (I did at one point have debugType on debugConfig so it round-tripped on restarts - it'd also probably have allowed overriding, though you'd probably have had to set it as an int in the launch.json... possibly we can just handle a string in here).

However, I guess then you're back to having a launch.json :(

Anyway, raise a case and let's see what we can come up with.

@pulyaevskiy FYI - it's not a complete solution for this, but #1206 adds a "Dart: Rerun Last Debug Session" command (bound to Cmd+Shift+F5/Ctrl+Shift+F5 when not already debugging) which might help a little. It re-runs the last debug session, with ${file} etc. having been resolved.

So, if you have ${file} (or nothing) in your launch.json such that F5 runs the open file, then you switch to another file and run this command, it'll re-run the same file as original. This also means if you click a Run/Debug Codelens link then end up jumping into the code being tested, you can re-run the same test (or group, or file, or whatever) from wherever you are without switching back to the test file.

Thanks for following up! I'll give it a try.

FWIW - This (the Rerun last debug session cmd) shipped yesterday, and I've found myself using Cmd+Shift+F5 tons while working on analysis server tests. When I have a failing test I'm usually working in the implementation and wanting to keep re-running the same test without navigating away :-)

(Maybe it needs a TouchBar button though? :-D)

When I have a failing test I'm usually working in the implementation and wanting to keep re-running the same test without navigating away

This is exactly why I needed this feature! :) Awesome work, thanks again!

This is exactly why i needed this feature. this is very important. very expected. thank you

Currently we don't consider running plain dart files from inside Flutter..

Why is that the case? As of now using v3.0.2 of both extensions. I have a flutter project with a launch.json as follows

{
  "version": "0.2.0",
  "configurations": [{
      "name": "Flutter",
      "request": "launch",
      "type": "dart",
      "args": [
        "--route=${input:route}",
      ],
    },
    {
      "name": "Debug feature tests",
      "program": "test_driver/app_test.dart",
      // "args": ["${input:tagExpression}"],
      "request": "launch",
      "type": "dart",
      "flutterMode": "debug"
    },
    {
      "name": "Dart",
      "type": "dart",
      "request": "launch",
      "program": "${file}",
    }

  ],
  "inputs": [{
    "type": "promptString",
    "default": "",
    "description": "Run specific tests e.g. @editor and @tasks",
    "id": "tagExpression",
  }, {
    "type": "promptString",
    "default": "/home",
    "description": "Run the specific route",
    "id": "route",
  }]
}

When I select the Dart config for debug, and Current open file is tool/my_tool.dart It prompts me for a device/emulator. Which means it's not respecting the debug config I've selected. I would like it to respect it. My use case is that I have some builders that run with build_runner build/watch and some code generation scripts. Right now I'm unable to debug those scripts.

I don't know why running of plain dart files isn't considered today. The programmer would know if that file needs to be run under flutter or not. Is this technically a complex issue to resolve or just not looked at?

Another minor issue I'm having is that the Debug feature tests config is doing something weird with args as you can see from above I have args commented out. When they are present, I do get prompted for a tagExpression but that gets supplied as part of the program file so app_test.dart <tagExpression> which doesn't run cause there's no such file. This is probably for another issue right?

@devkabiir

Currently we don't consider running plain dart files from inside Flutter..

Why is that the case?

That quote is from over a year ago. We now do assume plain Dart for things inside tool/bin folders. However, there's a TODO suggesting it might not cover all cases:

https://github.com/Dart-Code/Dart-Code/blob/c0b05720c2c903aac8a39316985c9bbd49f68fe4/src/providers/debug_config_provider.ts#L167

When I select the Dart config for debug, and Current open file is tool/my_tool.dart It prompts me for a device/emulator. Which means it's not respecting the debug config I've selected.

This isn't expected (though may be because of the TODO above, depending on your folder layout). Please open an issue for this, and include a log file (run the Dart: Capture Logs command before attempting to launch).

Another minor issue I'm having [...]. This is probably for another issue right?

Yeah, please open another issue for that. Something doesn't sound right there. Again, please include a log using the instructions above to make it easier to understand what's happening. Thanks!

It's been a long time since this issue was opened - there have been a lot of improvements to the rules for what to run when pressing F5. There are also commands for re-running the last debug session or the last test session, and also CodeLens links for tests/groups/main functions.

Are there still remaining pain points where it's not simple to run what you want to without switching files or messing with debug config?

This issue has been marked stale because it is tagged awaiting-info for 30 days with no activity. Remove the stale label or comment to prevent the issue being closed in 10 days.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mayorbyrne picture mayorbyrne  路  5Comments

partounian picture partounian  路  4Comments

rajeshjeshar picture rajeshjeshar  路  4Comments

DanTup picture DanTup  路  4Comments

jascodes picture jascodes  路  4Comments