sourcegraph / gophercon-2018-liveblog

Documents how to write a great liveblog post and how to submit your post for the GopherCon 2018 liveblog hosted by Sourcegraph at https://sourcegraph.com/gophercon
1 stars 2 forks source link

C L Eye-Catching User Interfaces #28

Closed ryan0x44 closed 6 years ago

ryan0x44 commented 6 years ago

Presenter: James Bowes

Liveblogger: Ryan D

Plug: This blog was written by @ryan0x44 from Cloudflare. If you want to write Go and help build a better Internet, Cloudflare is hiring!

James' tutorial session will teach you how to use the many features and techniques available for building interactive CLIs, from progress bars and color to mouse input and animated graphics on the command line

Summary

In this talk you'll learn how to:

...safely, for common terminals on recent versions of Mac OS, Linux, and Windows.

Slides and code examples for this talk are available here: https://bit.ly/cli-ui


img_1443

Go is awesome for CLIs

Hierarchy of User Interfaces

Part 1: Characters

Carraige Return - your new secret weapon! Though it's closely associated with line feed/newline concepts, we can use it here.

Demo 1: Progress bars

Say we want to show a progress bar like this:

demo progress: 46% |==== |

We can do this using a single-line Printf call e.g:

fmt.Printf("\rdemo progress: %3[1]d%% |%-[3]*[2]s|", percent, prog, cols)

which has the following special characters:

If we were resizing the progress bar based on the size of ther terminal, we could pass this in as a variable.

Demo 2: Unicode

You might want a progress bar with clock emoji or braille checks, e.g:

drawing spinners: ⠏ πŸ•™

To do this, we create slice literal of runes for each state:

braille = []rune{'β ‹', 'β ™', 'β Ή', 'β Έ', 'β Ό', 'β ΄', 'β ¦', 'β §', 'β ‡', '⠏'}
clock   = []rune{'πŸ•’', 'πŸ•“', 'πŸ•”', 'πŸ••', 'πŸ•–', 'πŸ•—', 'πŸ•˜', 'πŸ•™', 'πŸ•š', 'πŸ•›', 'πŸ•', 'πŸ•‘'}

The reason we use a slice rather than a string, is that it's much more difficult to index a string of unicode characters.

To make our spinner, we have a loop including a \r to move to the start of the line each time, and print the next rune in our slice:

fmt.Printf("\rdrawing spinners: %c  %c", braille[i%len(braille)], clock[i%len(clock)])

Unicode: What could go wrong?

Things to lookout for:

Part 2: Escape Codes

In-band Signalling

In-band signalling starts with \033[.

Standardized, thanks to American National Standards Institute (ANSI)!

e.g. \033[5m], \033[4m]

Note: example 4 doesn't work in all terminals, e.g. iTerm invisible is clearly visible - but in standard terminals (e.g. xterm) it works.

Text Decoration and Color

Even if terminals don't support 24 bit true color, it will usually down-sample into 256 colors (e.g. xterm). Unless you're trying to render fine art, down-sampling will probably be ok!

Multiline Output With Cursor Movement

Linear-Feedback Shift Register Screen Clearing / Fizzlefade.

Example 7 demonstrates an implemnetation of fizzlefade in Go. Note: it may break if terminal size isn't 80 characters.

So much more, e.g:

How bad can it be?

We've looked at things handled entirely by the client, but your connection might be over SSH and you don't have direct access. e.g. teapot in ReGIS - some terminals support it, but otherwise you may end up with a terminal showing gobbledygook.

How can we tell what is supported?

Environment variables:

What else can you use?

Your last option is to rely on the user;

Windows support

What's cool is we can enable the Windows subsystem for Linux,.

We do this by turning on the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag (see here) - then we can continue using the same ANSI escape codes and POSIX syscalls without worrying about the Windows API calls.

Unfortunately the console related APIs don't work across environments.

Part 3: System Calls

Out-of-band signalling through system calls.

We can thank IEEE, and Microsoft for documenting their own APIs!

Detecting Terminal Size

Great for columnar output and wrapping.

Useful e.g. to change progress bar size, or not mess up your fizzlefade demo when you have the wrong terminal size!

For Linux we use syscalls to:

  1. get the terminal kind
  2. get the terminal size

The Windows flow is similar, but the response is much more involved.

You can use the columns and lines environment variables in some shells. If you're going to be painting for a while, you might as well use syscalls - there's also a signal you can listen for when the terminal is resized.

Multi-Line Interactive Inline Inputs

For in-line interfaces (we'll cover fullscreen next).

Raw Mode

When using Raw Mode, instead of being line-based, output will be character-based and the terminal won't do any pre-processing of the output.

In our example func makeRaw is taken largely from a man page which describes what needs to be done, such as disabling certain terminal features.

Using this gives you full control, e.g. someone types a password you can control whether you echo it or not.

Part 4: Potpourri

Fullscreen interfaces

This is a combination of:

We do this so user can redirect stdout/stderr and still control what is displayed for the user.

In example 11, when the program ends, the user is back to their original terminal with the full history, etc.

Displaying graphics

There are a few ways to do this:

If you wanted to, you could use iTerm combined with SIXEL, etc.

When displaying images, note that there are a set of escape sequences that says whether the terminal should display the image, or have the terminal download the file directly.

Capturing mouse input

In our example, we receive a stream of signals that indicate what the mouse is doing - and display a Gopher image which can follow mouse around on the screen.

Appendix

A few links if you want to play around or learn more...

Great libraries:

Reading list:

ryan-blunden commented 6 years ago

Posting...

ryan-blunden commented 6 years ago

Now live:

Tweet: https://twitter.com/srcgraph/status/1034936710952439808 Post: https://about.sourcegraph.com/go/gophercon-2018-c-l-eye-catching-user-interfaces/