A Curses Application
I've now written two articles covering low-level
cursesand some higher abstractions. We know what
cursescan do at this point, but we haven't really seen how to put everything together and build a full application. Let's do that today by examining an example of moderate size.
I have written the beginnings of a command-line Twitter client using
curses. To make this easier, I developed a super simple wrapper over the raw
cursesAPI, called Rurses. Rurses, for Ruby
curses, provides more Ruby-like abstractions over the clunky C API. Here's how the Twitter client looks in action:
┌─JEG2─────────────────────────────────┐ @ │ ↑│ │Alex Harms @onealexh… 20/03/2015 14:57│ Clayton Flesher @Cal… 19/03/2015 20:52 │RT @postsecret: http://t.co/LrV0IIYgUM│ @TrevorBramble its inspired by, as in │ │ I played it once for about ten minutes │SimKlabnik 2000 @ste… 20/03/2015 14:57│ and then @JEG2 said 'go make your │the closure docs are finally flowing │ version of that'. │from my fingertips, it seems │ │ │ Mandy Moore @theruby… 19/03/2015 18:31 │ashe dryden @ashedry… 20/03/2015 14:57│ Thanks to @JEG2 I now want a MiP robot │Can anyone recommend an interestingly│ in the worst kind of way!!! │written (not dry) history of Haiti? │ │Preferably written by a Haitian. │ Sam Livingston-Gray … 19/03/2015 14:23 │ │ @avdi @JEG2 hush! Keep this up and │Jessica Lord @jllord 20/03/2015 14:57│ EVERYONE WILL KNOW │RT @PDX44: Welcome to Portland, where │ https://t.co/deJBBjoOTV │the old airport carpet is dressed up │ │as a human and named Grand Marshal of │ Josh Susser @joshsus… 19/03/2015 14:06 │a parade. http://t.co/aTCicqSzEI │ @geeksam @jeg2 @avdi POLS issue. No │ │ standard == no way to avoid surprising │Garann Means @garannm 20/03/2015 14:56│ many developers │RT @verge: Robyn launches tech │ │ ↓│ ↓ └──────────────────────────────────────┘
Curses Windows, Pads, and Panels
In the previous article, I showed how you can accomplish some terminal input and output operations using the
(n)curses(w)library. What I showed for output were mostly low-level writing routines though. Included in
cursesare some higher level abstractions that can be used to manage your program's output. This time, I want to show some of those tools.
The primary abstraction used in
cursesis the concept of a
window. We actually already used them last time, but we stuck with
stdscr(standard screen) which is just a window that fills the entire terminal. That's more for using
curseswithout thinking much about windows.
When you want them though, you can section off the terminal screen into rectangular chunks. Each chunk is a window that you can print output inside of. Here's the most basic example that I could dream up:
require "ffi-ncurses" begin stdscr = FFI::NCurses.initscr FFI::NCurses.cbreak FFI::NCurses.noecho window = FFI::NCurses.newwin(5, 15, 4, 2) # make a new window FFI::NCurses.waddstr(window, "Hello world!") FFI::NCurses.wrefresh(stdscr) # still need this FFI::NCurses.wrefresh(window) FFI::NCurses.getch ensure FFI::NCurses.endwin end
In my last article, I showed off some of the various magic commands a Unix terminal recognizes and how you can use those commands to move the cursor around, change the colors of your output, and read input in a character by character fashion. It's not very common to manipulate things manually as I did in those examples. There are libraries that wrap these mechanisms and add abstractions of their own. Using one of them can be easier and less error prone.
Probably the most famous of these higher abstraction libraries is
curses. However, your version won't be called that.
curseswas the original library for System V UNIX. These days you are far more likely to have
ncurseswhich a replacement library that emulates the original. The truth is, you probably don't have exactly that library either. Instead, you may have
ncursesw, which is the same library with wide character (read: non-ASCII) support. Also,
cursesis often discussed with several add on libraries:
form. Documentation often covers these separate units together.
Random Access Terminal
I've recently been playing around with fancy terminal output in Ruby. I've learned quite a bit about this arcane magic. I've also realized that the documentation is pretty spotty. I want to see if I can improve that with a few blog posts, so let's dive right in.
Program output typically happens in a linear order from top to bottom. For example, this code:
puts "onez" puts "twos" puts "threes"
generates this output:
onez twos threes
But what if you need to change some output? Could you replace the
zabove with an
sif you needed to? Yes, but it can get a little involved.
ANSI Escape Codes
In many cases, we just push some characters to
Kernel#putsis writing to above) and your terminal program happily shows them to the user. However, your terminal is watching these characters for special sequences that it understands. Some of those sequences of characters can cause your terminal to take actions other than just writing some output to the screen.