top | item 28549115

Bestline: Light self-contained readline alternative

95 points| cassepipe | 4 years ago |github.com | reply

48 comments

order
[+] JoshTriplett|4 years ago|reply
Looks like this doesn't support Ctrl-O (run line and retrieve next line from history). I've found that incredibly useful for re-running a series of commands: Ctrl-R to reverse search, type a few letters, Ctrl-O Ctrl-O Ctrl-O.
[+] stevekemp|4 years ago|reply
The one shortcut I use the most, in addition to the listed ones, is "Ctrl-e Ctrl-e" which opens an editor then sends the resulting content to the shell.

Using an editor is great for loops, loops with nested conditions, etc.

[+] atommclain|4 years ago|reply
This is cool, thanks for sharing! I have the following in my .inputrc which is in a similar vein (may be an anti pattern): ``` # F1, F2, F3 - calls alias1, alias2, alias3, helpful if you are executig the same ommands over and over "^[OP": "alias1\r" "^[OQ": "alias2\r" "^[OR": "alias3\r" ```
[+] riffraff|4 years ago|reply
Wow, I didn't know about this, thanks so much!
[+] swetland|4 years ago|reply
I'm a bit puzzled by "reducing binary footprint (surprisingly) by removing bloated dependencies" since linenoise has no dependencies (beyond libc) that I'm aware of.

  $ size linenoise_example 
     text    data     bss     dec     hex filename
    14718     948     184   15850    3dea linenoise_example

  $ size bestline_example 
     text    data     bss     dec     hex filename
    40830    1000    9280   51110    c7a6 bestline_example
Which seems reasonable since bestline has a bunch of unicode tables and handling for a bunch of editing features beyond linenoise's minimalist featureset, but I still don't understand the "bloated dependencies" remark...
[+] jart|4 years ago|reply
Author here. It's a question of beneath the iceberg dependencies revealed by static linking.

    $ make clean && CC="x86_64-linux-musl-gcc -s -static -DNDEBUG -Os" make
    $ ls -hal bestline_example
    -rwxr-xr-x 1 jart jart 38K Sep 19 21:41 bestline_example
Linenoise grows considerably when statically linked, since it needs functions like printf/scanf, which add footprint without adding a whole lot of value to the library.

    $ make clean && CC="x86_64-linux-musl-gcc -s -static -DNDEBUG -Os" make
    $ ls -hal linenoise_example
    -rwxr-xr-x 1 jart jart 50K Sep 19 21:41 linenoise_example
Bestline refactors the Linenoise code so the linker won't pull printf functions into the linkage. That freed up about 30kb of space, which was then used to provide UNICODE and near-feature parity with GNU Readline.

It matters because a library that's this low-level and so fundamental to the needs of nearly every program, should impose as few choices upon the user as possible, in terms of what other things they're required to support. For example, many people don't like printf() style interfaces and therefore do not want them included in their address space. The same goes for huge libraries like Curses and ICU that require you to link in megs of code/data just to read a character correctly. You want the value those things offer, but you don't want the baggage if all you care about is just reading a line comfortably.

That's where Bestline comes in. It distills the value of all those heavyweight dependencies down into a single .c file focused on reading lines and only reading lines, which everyone can agree on, that's actually tinier than the original library at the end of the day. So you get a better value as a software developer in terms of the code complexity and dependency bloat you need to take on.

[+] masklinn|4 years ago|reply
The readme mentions

> Remove heavyweight dependencies like printf/sprintf

But that’s from libc, unless they measure by statically linking everything?

[+] yjftsjthsd-h|4 years ago|reply
So I think (having read the readme but not the code and not being expert in the subject matter) this is a library that lets you do things like read line, but probably is not API or ABI compatible (because that seems like something that would be mentioned and I can't see anything about it)?

Also, I have to say I'm entertained at completely throwing out worrying about terminals and just declaring that everything supports vt100 so we're going to target that and be done with it. Kinda elegant, especially for the relatively limited functionality they need (not like it's curses where you're really going to exercise the terminal's capabilities).

[+] nine_k|4 years ago|reply
I suppose there are two kinds of terminals in real use today: terminal emulators (which all support vt100) and dumb / trivial consoles that use a framebuffer if not a segmented LCD display, that don't support anything standardized anyway, and you don't need much from them beside displaying characters.

So the choice to support the common case of the terminal emulator looks reasonable. Even when you attach a serial cable to a tiny controller to do something on its tiny command line, you likely do it from your laptop. Having a readline lookalike in this situation would be nice.

[+] mediocregopher|4 years ago|reply
The README mentions Antirez's coding philosophy as being an inspiration:

> This codebase aims to follow in Antirez's tradition of writing beautiful programs, that solve extremely difficult difficult technical problems in the simplest most elegant way possible.

Is something Antirez has talked about himself anywhere? I'm interested to know more, but am not fluent enough in C to understand what makes this code elegant.

[+] jks|4 years ago|reply
This library is a fork of Antirez's linenoise, so it's perhaps not only an inspiration but a quality that the code had to begin with, which the author of the fork declares that she intends to preserve.

The linenoise README mentions "sensibility for small easy to understand code". It's not really explained further in the README, but I suppose the point is that you can read the ~1200 lines of source code and get a feel for it yourself.

[+] IgorPartola|4 years ago|reply
I didn’t dive deep into this code but what I would assume makes this elegant is that it is mostly declarative/lookup style with only a little imperative code where necessary. This style doesn’t use fancy tricks/hacks and instead uses lookup tables where necessary to encapsulate logic.
[+] nrdvana|4 years ago|reply
Open this code side by side with the source code for Cyrus IMAP, or Perl core, or libssl, and then scroll through them. You will be enlightened.

In short, any code that is intended to work on a wide variety of systems is usually littered with conditional compilation directives and macros that make the code extremely difficult to follow without a extensive study of the author’s “setup”. This is because to compile/run on a wide variety of systems, you actually need wildly different C source code, and what is written in the C source file ends up being more like a template for a program than the actual program.

[+] laumars|4 years ago|reply
At this point there are already hundreds of readline alternatives across various different languages as it’s actually surprisingly easy to write a readline library.

Given the capabilities of computers these days, I think if one is stuck choosing which library to use, I’d suggest get the one that’s most complete rather than most compact. I guarantee you that there will be one of your users who’ll use an Emacs or Vi shortcut and find the lack of support really prohibiting.

[+] savolai|4 years ago|reply
Curious about the UI paradigm here and still newbie after all these years.

Is there a well written explanation about the idea / logic behind the terminology of kill line / yank somewhere for people who are used to ... well, just what modern GUIs provide, i.e copy/paste etc? Pros/cons? What functionality do these operations actually provide? Thanks.

[+] jks|4 years ago|reply
The terminology comes from Emacs:

https://www.emacswiki.org/emacs/KillingAndYanking https://www.gnu.org/software/emacs/manual/html_node/emacs/Ki...

The basic idea is that your clipboard ("kill ring") can contain many entries, so you can kill several lines (or words, sentences, functions, what have you) and then yank them back at other locations. Yanking copies the most recently killed text, but you can use another command to replace it with previous entries until you find the right one. Thus you don't have to fear losing the clipboard while performing intermediate operations.

Readline-type libraries typically implement a small subset of the Emacs kill-ring features, so you really need to try Emacs to appreciate the full range of the concept.

[+] maddyboo|4 years ago|reply
It would be cool to see a version of rlwrap [1] (blwrap in this case?) using this library.

[1]: https://man.archlinux.org/man/rlwrap.1

[+] hanslub42|4 years ago|reply
What advantages would a blwrap have over rlwrap? As I understand it, bestline's advantages over readline (fewer dependencies, license) only apply when you are using it as a library. rlwrap is a separate program that weighs in at around 200k. That could only ever be a problem on an embedded system, where rlwrap/blwrap wouldn't make sense anyway.
[+] lume|4 years ago|reply
Trying to compile it on osx. Several errors like this:

  make
  cc  -I/usr/local/opt/ruby/include  -c -o bestline.o bestline.c
  In file included from bestline.c:122:
  In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/termios.h:26:
  /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/cdefs.h:681:49: error: invalid token at start of a preprocessor expression
  #if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE == 1L
                                                  ^
[+] jart|4 years ago|reply
The issue you encountered should be fixed! I've confirmed it's building on Mac OS X. Enjoy.
[+] Miiko|4 years ago|reply
Apparently, it does not fully conforms to POSIX as README claims - AFAIK, that "_POSIX_X_SOURCE" feature test macro should be defined with positive integer value per POSIX standard, not empty as defined in bestline.c
[+] chubot|4 years ago|reply
Hm I don't see mention of vi key bindings? set -o vi is awesome in bash / Python / R, etc.