# A New Listener The basic listener is very minimalistic. There are a few reasons for this. - I don't use it often (I generally code in files or blocks, using the listener only for brief tests) - A complex listener adds surface for bugs and incompatibilites due to terminal and host configurations - It's harder to pipe data in and out of a more complex listener This can be worked around in a number of ways. Tools like `rlwrap` can be used to provide a line editor and history, and one can always write their own custom listener... But sometimes it's nice to experiment a bit. This is a modestly expanded version of the listener, adding some new functionality and providing a more flexible base to build on. # Current Features - character breaking input - suggestions on hitting TAB - show stack on hitting ESC - use `retro-describe` to get help on CTRL+K # Loading retro -i -f alternate-listener.forth new-listener # The Code The RETRO image has a 512 cell buffer between the kernel and the high level code. This is used for the Text Input Buffer, or TIB. +------------------+ | kernel | 0-1023 | input buffer | 1024-1536 | standard library | 1537+ | ... | | system buffers | EOM - ??? +------------------+ ~~~ #1024 'TIB const ~~~ The listener needs to determine what to treat as the end of the token. I define `end-of-token?` for this. ~~~ :end-of-token? (c-f) hook { ASCII:CR ASCII:LF ASCII:SPACE } a:contains? ; ~~~ It's possible to get an empty string as an input. This isn't any good, so I define `s:blank?` for this. ~~~ :s:blank? (s-sf) dup s:length n:zero? ; ~~~ There are certain keys I want to handle differently from others. The initial ones are backspace, tab, escape, and CTRL+K. I am defining handlers for these. First is backspace. I trap ASCII:BS and ASCII:DEL for this. ~~~ :handle:backspace (c-c) hook dup { ASCII:BS ASCII:DEL } a:contains? [ buffer:get buffer:get drop-pair ] if ; ~~~ Tab is used to display suggestions, based on the token input so far. ~~~ {{ :sigil? TIB fetch 'sigil:_ [ #7 + store ] sip d:lookup n:-zero? ; :hint nl TIB sigil? [ n:inc ] if d:words-beginning-with nl TIB s:put ; ---reveal--- :handle:tab (c-c) hook dup ASCII:HT eq? [ buffer:get drop hint ] if ; }} ~~~ Control + k (`ASCII:VT`) will display help for the word being typed. This assumes that `retro-describe` is in your $PATH and that the typed text is a complete word name (without a sigil). ~~~ :handle:CTRL+K (c-c) hook dup ASCII:VT eq? [ buffer:get drop TIB 'retro-describe_"%s" s:format nl unix:system TIB s:put ] if ; ~~~ The escape key will be used to display the stack. ~~~ :handle:escape (c-c) hook dup ASCII:ESC eq? [ buffer:get drop nl &dump-stack dip nl TIB s:put ] if ; ~~~ To control the checks, I define two words. The first returns an array of handlers, the second processes them. ~~~ :special-keys (-a) hook { &handle:backspace &handle:tab &handle:escape &handle:CTRL+K } ; :check (q-) hook &call a:for-each ; ~~~ And with these, I can quickly implement the `new-listener`. ~~~ {{ :guard (-) buffer:end TIB lt? [ TIB buffer:set ] if ; :c:get (-c) as{ 'liii.... i #1 d }as dup buffer:add ; :s:get (-s) [ TIB buffer:set [ guard c:get special-keys check end-of-token? ] until buffer:start s:chop ] buffer:preserve ; :forever (q-) repeat &call sip again ; ---reveal--- :new-listener (-) hook 'stty_cbreak unix:system [ s:get s:blank? &drop &interpret choose ] forever ; [ 'stty_cbreak unix:system #0 unix:exit ] &bye set-hook }} ~~~ All of the exposed words are hooks: your code can patch in and replace them as you see fit, making this much more mallable at runtime. Future things to (maybe) explore: - tab completion - line editing - input history