Keyboard shortcuts during triggered execution (~)

sbt 1.2.8

1. Waiting for source changes in project root... (press enter to interrupt)

In sbt 1.2.x, pressing <enter> during triggered execution returns the prompt back to where ~compile came from.

  1. If ~compile was started from sbt shell, it returns to the sbt shell.
  2. If ~compile was started as batch mode, like sbt "~compile", it returns to zsh (or whatever the system shell).

Making changes to the source code and saving the file triggers the next round with an incremented number:

2. Waiting for source changes in project root... (press enter to interrupt)

sbt 1.3.0-RC2

[info] 1. Monitoring source files for updates...
[info] Project: root
[info] Command: compile
[info] Options:
[info]   <enter>: return to the shell
[info]   'r': repeat the current command
[info]   'x': exit sbt

In sbt 1.3.0-RC2, pressing <enter> always returns to sbt shell, even if I started ~compile in batch mode.

In addition sbt 1.3.0-RC2 adds two more shortcuts:

  • 'r' reruns the current command.
  • 'x' exits sbt.

I also noticed that the instruction text is printed each time triggered execution is triggered by a file save.

[info] 2. Monitoring source files for updates...
[info] Project: root
[info] Command: compile
[info] Options:
[info]   <enter>: return to the shell
[info]   'r': repeat the current command
[info]   'x': exit sbt

discussion

I’d like to clarify the use cases around ~, and revisit the details.

enter

In my opinion, <enter> should mean “return one level if you’re waiting, otherwise do so once your current command is done”. I think that’s how sbt 1.2.8 works.

Returning one level here would be sbt shell if you started from sbt shell, and zsh if you started from zsh.

Ctrl-D

Ctrl-D if we support it, should behave exactly the same as <enter>: return one level once your current command is done. This is how Ctrl-D behaves for both Scala REPL and sbt shell.

Ctrl-C

Ctrl-C should behave similarly to how it behaves for sbt shell.

  1. When Global / cancelable is true, Ctrl-C interrupts the running command and returns one level, back to sbt shell if it was started in sbt shell.
  2. When Global / cancelable is false, Ctrl-C stops sbt, and jumps back to zsh.

?

Maybe there should be a ? support that prints out all the shortcuts, similar to Gmail etc. Ideally the text blurb should go back to one liner.

s

For rare situation where someone wants to start from batch mode, but jump to interactive mode, I’d say we can implement something like 's' as the command.

q

My preference for quitting is using using 'q' as it is available in Scala REPL as :q. Since Scala’s Predef no longer defines exit, I think sbt shell should also support quit.

I was the author of the watch changes for sbt 1.3.0 and can provide some context into my thinking:

I am in favor of shorter instructions, but it got unwieldy to have them on one line, e.g.
2. Waiting for source changes to task 'compile' in project 'root' (press enter to interrupt, 'r' to rerun the command or 'x' to exit sbt).
The command was missing from the 1.2.8 message and is quite useful if you leave a continuous build for a while and can’t remember what it was doing.

I also tend to use sbt in a narrow split screen pane and prefer to use as little horizontal space as possible which is part of why the instructions are formatted the way they are. Given that it’s fairly straightforward to change the message in a global build.sbt file, I like erring on the side of being overly verbose but clear.

The reason I changed this behavior is because many times over the years I’ve started sbt in batch mode with a continuous command and I wanted to be able to return to the shell but was unable to do so. If sbt started up faster, this wouldn’t really matter, but it’s frustrating to have to wait 15-20 seconds (or more) to restart sbt just because there was no way to get back to the shell.

With respect to the “return one level” semantics, I may not return to my sbt session for hours or even days after I started the continuous build. When that happens, I have no idea if I entered in batch mode or not. For that reason, I prefer that the consistency of the command be reflected by what it actually does rather than what it conceptually does. At the same time, I see that this makes the behavior of sbt ~compile different from sbt console. sbt 1.2.8 is also inconsistent in that the way to exit ~ is <enter> and the way to exit the REPL is Ctrl-D or :q. I would be in favor of having Ctrl-D have return one level semantics (and possibly not be listed in the options) while having <enter> always return to the shell.

This raises the question of whether there should be a watchVerbose setting. If it is set to true, then we’d print something like the 7 liner above. Otherwise we’d print something like 2. Watching source files for 'root / compile'... (press '?' to print options)
I think the wording needs to be slightly different than for 1.2.8 because watch now monitors files at the task rather than project level.

Alternatively, we could print the verbose message the first time we enter watch and the compact message after the first trigger. This might be a nice sweet spot where we have good discoverability without being annoyingly verbose.

This is not rare for me. I originally added an ‘s’ option but it was redundant if you had entered the continuous build from the shell. If <enter> is changed to have return one level semantics, then I guess I could see adding ‘s’ to the options list only if ~ is entered from batch mode.

I agree with this in principle but the command to quit sbt is exit so ‘x’ was used for consistency with that command.

My ranked preferences for 1.3.0 final would probably be:

  1. Leave things as they are in 1.3.0-RC2, but add Ctrl+D has return one level semantics (probably don’t put it in the options though, much like how the scala REPL doesn’t show Ctrl+D in the options). We could also switch to a compact message after the first trigger and/or add the watchVerbose setting to toggle the compact message
  2. Add a watchConfiguration setting that allows the user to flip between 1.2.8 and 1.3.0-RC2 semantics. We could also add additional profiles that have different options/semantics.
  3. Remove the exit option entirely, add Ctrl+D and always print a compact message.

The one thing I feel strongly about is having a way to get back to the shell without exiting sbt if sbt has been started in batch mode. Its absence has been a real pain point for me due to the poor sbt startup times. I prefer the 1.3.0-RC2 <enter> semantics to a separate s option to go to the shell because I have muscle memory for using <enter> to return to the shell from sbt 1.2.8.

FWIW, because the watch options are configurable, adding the Ctrl+D has return one level semantics can be done in a build.sbt file without even changing sbt 1.3.0-RC2:

import complete.Parsers._
import complete.Parser._
ThisBuild / watchInputParser := 4.toChar ^^^ new Watch.Run("") | Watch.defaultInputParser

But also because the watch options are configurable, I am flexible on the final defaults.

I don’t think Ctrl-D should be used. It generally means EOF, and is appropriate where you’re reading arbitrary text from stdin and the user wants to signal “done.” (IIUC traditionally at least you’re accessing stdin as a file and so you read until EOF. Things like JLine might make that not literally true, but it’s still a reasonable shortcut to use.)

IMO Enter should continue its previous behavior. In particular I should be able to do sbt ~module1/compile ~module2/compile and it will keep waiting for changes in module1 and compiling them until I press Enter, at which point it will run ~module2/compile.

I think Ctrl-C should actually behave similarly, depending on Global / cancelable: When it’s enabled it cancels the running task, which in the case of ~compile, while the compiler is running should abort and return to waiting for source changes, but if pressed while waiting for source changes it should interrupt that, which would do the same thing as I want Enter to do. (If you press Enter during compilation it should go into the keyboard buffer and take effect when compile finishes.) OTOH if Global / cancelable is disabled, you can make a case either for quitting sbt, or for always breaking out of the ~ loop.

Anyway, I agree there should be a way to go to the shell from the waiting prompt, but it could just be a shortcut like S.

I don’t think there’s a need for a shortcut to exit sbt. It should print what Ctrl-C would do. If Ctrl-C would put me back in the shell I can Ctrl-C again from there to quit.

As far as the shortcut to restart the current command, it’s an excellent feature. I would just note that some utilities like entr use Space for this. I may have seen this elsewhere – I can’t recall. I have a slight preference for Space over R but it doesn’t matter.

Anyway one way or another these are great improvements!

Play’s run is another precedence for treating Ctrl-D the same as <enter> - https://github.com/playframework/playframework/blob/3ea3b2f839d20448024ff330dd29b7dc5f3c6b69/dev-mode/sbt-plugin/src/main/scala/play/sbt/PlayInteractionMode.scala#L62-L78. In both cases run would stop and return to one level up.

FWIW I don’t think Ctrl-D ever actually worked for me in Play projects…