Command line UX enhancements for `swift`

Hello everyone,

I’d like to start a thread about how to improve Swift’s user experience from the command line across all platforms.

Specifically, I’d like to make the command line interface more appropriate for direct, interactive use by humans, while keeping automation use cases available. I propose to rework the CL tools in the following ways:

  • Give users direction in accomplishing their Swift tasks at the command line, providing choices for high level tasks and clarification about what to do next where possible.
  • Progressively disclose flags and options, keeping text focused on the current task.
  • Make the command line aware of context so it can infer the right action for impactful cases.
  • Continue to provide explicit invocations for all of the tasks we perform today.

Motivation

The Swift CLI has grown organically over time to include a large number of flags and options for many individual needs. In fact, you could argue the CLI was never truly “designed” to scale to the features it offers today. In order to use it successfully, you need to know which combination of options go together for a given task, many of which are designed to be driven by an IDE or build system, not a human. In fact, typing swift --help presents around 150 lines of options, with --help-hidden increasing the count to around 250. The tool would greatly benefit from a true UX design.

The command line is also currently inconsistent with how it presents options, for example:

  • Typing swift --help implies compilation with the driver and presents many build-related options, but also presents other unrelated subcommands.
  • With no arguments, swift invokes the REPL, but this isn’t particularly obvious.
  • The swift build command is for packages, but we also elevate raw code compilation with the swiftc command.

As a top goal of a redesign, the command line should always presents clear, consistent, and intuitive avenues through high level tasks. The use of sub-commands will make grouping of tasks more logical and easier to discover. This approach will also make it easier to add new functionality while keeping the commands organized, and more approachable for developers of all experience levels.

Once the top-level commands are better organized, I’d like to add features that further elevate the value of sample code and documentation, too. We can make the CL experience easier for newcomers learning the language, and for anyone investigating a new package or code base to learn it by example.

Design Sketch

The proposed idea, broken down into a few sub-goals:

1. Establish a concise, colorful look and feel.

To improve readability and differentiation of descriptions, lists, menus, or code listings printed to the terminal, I’d like to adopt terminal colors and establish a concise and spacious look and feel to the text. To give an example of a project that has some inspiring similarities, check out the GitHub CLI: https://cli.github.com.

2. Make the swift command act as a high-level router to subcommands.

With the arrival of the Swift Package Manager, we’ve already been moving in this direction. I'd like to continue with an additional focus on subcommands. For example, today the swift command will assume you mean to invoke the compiler when there isn't a subcommand, although it's not clear that is always the intended outcome, and we have swiftc for that case, as well.

The change I propose here is to just list the highest level subcommands instead, when there isn’t anything more specific requested. There are two small branches I have ready to get this started which include some improvements to the presentation, as well:

https://github.com/bitjammer/swift-driver/tree/acgarland/help-intro

3. Require the swift repl command to launch the REPL

Launching the REPL should be consistent with the other swift subcommands. The tools already recognize swift repl as a subcommand, and will become much more discoverable when listed among the top subcommands in the new UX. By not launching the REPL by default, room is opened up for other default behaviors based on context (see next section.)

4. Run certain subcommands automatically based on context

When you run a tool like swift with no parameters, the default behavior is generally chosen to either 1) encourage specific user behavior, or 2) do the thing users most often want. Preferably, something happens that feels like both. Given the user’s context, we can pick default behaviors more likely to be helpful for the user. For instance, if your shell’s current folder is within the root of a package, it may make sense to list all targets within that package if you just type swift, or provide a menu to explore and run code snippets.

Proposed new behavior: code snippets. The first contextual behavior I’d like to propose is a new way to explore a package. Imagine an interactive help experience that surfaces a package's documentation, and can even browse and run a package's bundled code snippets. Every time you download a new Swift package, you can immediately experience how that code is used in focused examples.

I have been prototyping some ideas on snippets specifically, and would love to spend some time to get feedback and go deeper on this soon in a separate pitch. The approach is designed to make it much more rewarding to document packages, and provide example code showing off the API. And if the idea catches on, the Swift community will benefit from a standard way to learn about new packages more easily.

I’ll try to post some code and a further pitch on this idea soon.

Consistency is still very important, so even if we offer contextual behaviors, every option the user may want will also need a specific command to perform that task with 100% reliability.

5. The swiftc command continues to serve as the way to automate compilation.

The swiftc tool already exists for direct control of the Swift compiler. Again, for consistency, a command such as swift compile could be added to the main swift command, and to complement the swift build command.

What this end state means is that the swift command serves as an entry point for people to perform high-level tasks with Swift, and swiftc is dedicated to compilation with the driver.

52 Likes

This looks like a long-needed and thoughtful improvement!

5 Likes

Here is a test toolchain for Linux with the the above mentioned PRs in case anyone wants to try it out. Still working on getting a macOS toolchain built.

https://ci.swift.org/job/swift-PR-toolchain-Linux/634//artifact/branch-main/swift-PR-34262-634-ubuntu16.04.tar.gz

4 Likes

Here's a macOS toolchain as well. https://ci.swift.org/job/swift-PR-toolchain-osx/1077/artifact/branch-main/swift-PR-34262-1077-osx.tar.gz

3 Likes

As promised, continuing the discussion with snippets here: Swift Snippets

2 Likes

I really love all the ideas you pitched here! Giving the CLI a redesign sounds like a great idea and will only become more relevant as it grows. Love the idea of using colors and having the default swift command be more… explanatory.

The contextual stuff is interesting. I can’t think of any other CLI I’ve used that works this way. Do you have some examples that you’re using as inspiration for this idea? I’d love to see more examples.

1 Like

Disclaimer: I thinks this comment may be tangent to your primary goal, but you touch on a number of aspects of the topic, so I'd like to chime in with my long-standing Unix CLI pipe dream in case it inspires anyone. :slight_smile:

Typical unix argument processing (getopt/getopt-long style) is generally a wonderful boon to flexibility and automation for command-line programs. However it's always difficult to build UI's around CLI tools as each one is totally unique.

It would be awesome if there was a standard command for doing an argument schema dump so that UI tools can query the tool and programmatically build reasonably useful UI experiences around the CLI tool without having specific knowledge of the CLI tool itself. (I realize there will always be edge cases where specific knowledge will be beneficial, but the intent is to minimize those cases).

In this way any interactive user interface, whether graphical or command-line like the use-case you mention above, can (ideally) be built as a generic library. Perhaps in cases where the interface is built with the application, this library can then allow explicit programming of specific edge case.

As way of example, the idea is to have the argument processing library always accept a flag like "--dump-argument-schema"...

USAGE: some_cli  [--port <port>] [...]

OPTIONS:
  -p, --port <port>       TCP Port to listen on (Default: 8000) (default: 8000)
  ...
  --dump-argument-schema   Dump argument schema -- perhaps a hidden argument?
  -h, --help                  Show help information.

Perhaps the flag should take a parameter like json, xml, etc. to specify the format to dump in. But the idea is then everything necessary to build a meaningful UI would be in the schema (grouped commands, command exclusion rules, valid values, or other value validation rules, etc.)

Having this extra layer also gives the potential for easily switching UI implementations... allowing for experimentation in the UI without breaking things for existing users.

You could start by looking at how Swift Argument Parser could be adapted for generating UI interfaces (or schema).