Bestline: Light self-contained readline alternative
github.com
github.com
Using an editor is great for loops, loops with nested conditions, etc.
(Be sure to set VISUAL to something nice.)
Slightly faster than ctrl-a followed by # followed by return.
(Aside: I've never seen the sense in using C to denote ctrl. How do you denote C?)
Ah apparently it's a ZSH-specific shortcut for "push-line". readline follows Emacs' (not illogical but probably less useful here) "quoted-insert" (generally used to include non-literal code e.g. C-q a does nothing as "a" is already literal, but C-q C-q should insert 0x 11 / XON / DC1).
> (Aside: I've never seen the sense in using C to denote ctrl. How do you denote C?)
c. Or C for S-c (shift-c).
a-b indicates a single keystroke, meaning a modified key, so the first character can only signify a modifier key[0]. Which "c" is not. The sequence "upper case C then lowercase c" is "C c", not "C-c".
[0] well technically you could have keystrokes of non-modifiers as modern keyboards can handle concurrent keycodes, but historical systems don't so a keystroke is only a bunch of modifiers + a single key e.g. C-S-M-c is unambiguously Control-Shift-Meta-c
To denote a capital c, use Shift-c or some variant thereof.
Ctrl is "C-", prefixed to another key without a space, so it's unambiguous.
$ 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...> Remove heavyweight dependencies like printf/sprintf
But that’s from libc, unless they measure by statically linking everything?
Not only does it seem unlikely your program would have a terminal output and standard streams without having an OS, bestline still requires a libc. Just open `bestline.c` and you'll find:
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <setjmp.h>
#include <poll.h>
#include <assert.h>
#include <signal.h>
#include <fcntl.h>
#include <limits.h>Most of the defines can have a null implementation and the core will still work. They just need to be defined.
And the message I quoted says literally nothing about µc.
$ 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.
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).
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.
> 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.
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.
https://github.com/jart/bestline/blob/master/bestline.c#L286...
This interface function is already terrible. Why doesn't it check the error code of bestlineHistoryLoad()? It smells like a sloppy C program with obscure failure modes.
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.
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.
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.
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.
- Move cursor to the first word, kill it with Alt+D (aka M-D). - Fearlessly move the cursor to another word, kill it, too. - Now move where you want to put the first word back, because it's nearby. Press Ctrl+Y, oops, it shows the second word. Never mind, press Alt+Y, and it pastes the first word on its place. - Now move elsewhere where the second word belongs. Ctrl+Y pastes (yanks) the first word again, the most recently used. Alt+Y changes it to the second word, the second most recently used.
This way you may have quite a few fragments in the ring and juggle them, repeat them, several different fragments, without the need to copy again and again.
My memory is limited so it doesn't seem worth the trouble to learn such a niche system. Still there's something intriguing about it as someone who tries to keep one's mind open about different design systems.
As a ui/ux designer what strikes me with these legacy systems is there seems to be no fluent support system in place for learning them. And even after you've learned, no visibility to support long term memorization.
Also, I suppose there is no clear visual panel to support memory as to what's actually in the ring but you kinda have to just browse it one by one with shortcuts, as I understand the above?
Do the terms kill/yank seem appropriate to others? It seems that there is a slang context I'm missing there that might make the terms more intuitive.
Now we have inherited these technologies, but there is often no reasonable way to modernize their UX in the discoverability department, because of key assumptions made decades ago. All we can use now is still manuals; `man bash`, search for the READLINE section, and read the extensive explanation of the riches hiding there. You can search by regexp, so ^READLINE will bring you right to the section, but again there is no affordance to show you that a regexp is acceptable.
And yes, I think nobody thought through the terms kill / yank; their authors just picked some metaphors 40 or more years ago, because they were building a tool for themselves and their colleagues in a computer department of a university, all engineers used to weird abstractions.
The world is very different by now.
I agree that the ancient terminology is a problem with Emacs. People who are used to Emacs don't want to change it, but it is alien to all new users. Funnily enough, in vi (a competing old text editor that predates current UI standards) "yank" means "copy".
Another example is "window" and "frame", which mean the opposite that you would think if you have used any modern windowing system. A similar problem (that is not strictly about terminology) is the way "undo" works. It is more powerful than the standard undo/redo system, but it can be very confusing when you encounter it the first time. Repeated "undo" commands work the way they do in other programs, but once you run any other command, the undo operations go in the undo history just like other commands and are themselves undoable, so the way to cancel an undo operation (which is called "redo" in many other programs) is to type some character and then run "undo" twice.
(With Emacs as my daily driver, I greatly miss its uniformity of window management even in environments that look like its spiritual successors, like VSCode.)
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
^