Lever console had to be homebrew
If you've been reading about Lever, I first moved from gnu readline to editline, and then I finally ended up to writing my own editline routines.
There was no choice to do otherwise. Readline comes with an incompatible license. Editline took more effort to adjust for my language than writing it from scratch.
Modern terminals work with ANSI TTY escape codes. A line editor utility such as readline sets the terminal from baked mode to raw. The raw mode allows code to read keyboard input directly.
Line editors have to be aware of where the cursor is in the screen. ANSI TTYs are slightly backwards and only report cursor positions in the screen-coordinates. This is useless if you want to keep the text scrollable.
The state of the art in line editors for determining the cursor location is to calculate from the text stream where the cursor ends up to and reposition it with relative motion commands.
With some care you can have complete control of the console screen without crobbering the contents. Lever's line editor comes with two fairly novel features not seen elswhere.
Asynchronous writeout
Whenever something writes into the stdout while the readline operation takes place, the terminal output leaves fragments of the line editor into the output stream as a trash. Write into output with readline on is usually avoided for this reason.
At many computer programs the readline operation halts the program until you respond. Therefore it's only a problem at multithread/multiprocess programs that something else writes into the stdout and interferes with the terminal output. This almost always ends up with trash in the output stream, fragments of the line editor prompt in middle of the text.
This is a common case in async and interactive systems. Therefore I designed my line editor utility to take full control over the TTY outputs and get the logging routed through a function, rather than directly into stderr/stdout.
Doing this requires little bit of care to avoid faulty forms of logging from forming infinite exception loops. But otherwise it should work just well.
Apropos at tab completion
Another thing Lever's editline is doing different from others is to show short description during the tab completion. Just like the apropos command in Linux. When you start a Lever console and press tab, you get this:
LEVER 0.X.0
>>
AssertionError SourceLocationLines backward
AttributeError StringBuilder bool
CallError SystemExit builtin
DocRef TTY chdir
Error TypeError chr
Event UVError clamp
Exception Uint8Array class
FrozenError Uint8Builder coerce
IOError Uint8Slice cos
Import UncatchedStopIteration cross
InstructionError Utf8Decoder decode_utf8
KeyError ValueError dict
ModuleScope abs dir
Pipe and doc
SourceLocation axisangle doc...
There are some glitches on this, but they'll be unwrinkled over time. Now if you write "ModuleScope" and then press tab again:
LEVER 0.X.0
>> ModuleScope
class ModuleScope(local:path, [parent:module_scope], [options:object])
The quick apropos is derived by inspecting the object.
Later it will be possible to also provide a slow apropos that comes directly from the offline documentation.
Short Do-it-yourself
Lever's console will be eventually accessible as a library, that's not done yet. Instead I show how you can make a console in Lever.
# This command sets the mode raw and allows to read
# the input character-by-character.
stdin.set_mode('raw')
# You need storage for the text you write.
readbuf = []
# And storage for the output.
writebuf = []
# An ordinary loop.
running = true
while running
# Writing our contents to the screen.
stdout.write(">> " ++ "".join(readbuf))
# Here we loop through each hcharacter
for ch in stdin.read()
n = ord(ch)
if ch == '\x03' # responds to CTRL+C
running = false # otherwise you cannot exit.
elif ch == '\r' # response to return: clean the readbuf.
text = "".join(readbuf)
if text == "exit" # another way to exit.
running = false
writebuf.append(text ++ "\n")
readbuf = []
elif ch == '\x7f' # response to backspace.
if readbuf.length > 0
readbuf.pop()
elif (0x20 <= n and n < 127) or n > 255
readbuf.append(ch) # writes to the readbuf.
else
# print out characters that were not understood.
writebuf.append(repr(ch) ++ "\n")
## The refresh cycle
# The libuv TTY is guaranteed to understand
# ANSI escape codes.
stdout.write('\033[0G') # moves to the left of the line.
stdout.write('\033[0J') # clears off under and right
# of the cursor.
# Write cycle into the output empties the writebuf.
stdout.write("".join(writebuf))
writebuf = []
The stdin and stdout are standard input and standard output streams. Otherwise we operate with lists and strings there. Those behave identically to their python counterparts.
Lever console started out as code like this. It eventually separated into two separate loops for the reads and writes and gained more features.