urbit/pkg/arvo/lib/shoe.hoon
fang 7977dcfec0
shoe: don't auto-run discontinuous inputs
Previously, up-arrowing into (or otherwise retrieving) a command from history
that automatically runs on-input would directly run the command again,
preventing the user from up-arrowing past the auto-run command into further
history.

With this change, shoe detects discontinuous inputs (sole's `%set` edit), and
refuses to auto-run the parsed command in those cases.
2020-09-26 02:08:35 +02:00

533 lines
14 KiB
Plaintext

:: shoe: console application library
::
:: /lib/sole: draw some characters
:: /lib/shoe: draw the rest of the fscking app
::
:: call +agent with a type, then call the resulting function with a core
:: of the shape described in +shoe.
:: you may produce classic gall cards and "shoe-effects", shorthands for
:: sending cli events to connected clients.
:: default implementations for the shoe-specific arms are in +default.
:: for a simple usage example, see /app/shoe.
::
/- *sole
/+ sole, auto=language-server-complete
|%
+$ state-0
$: %0
soles=(map @ta sole-share)
==
:: $card: standard gall cards plus shoe effects
::
+$ card
$% card:agent:gall
[%shoe sole-ids=(list @ta) effect=shoe-effect] :: ~ sends to all soles
==
:: $shoe-effect: easier sole-effects
::
+$ shoe-effect
$% :: %sole: raw sole-effect
::
[%sole effect=sole-effect]
:: %table: sortable, filterable data, with suggested column char widths
::
[%table head=(list dime) wide=(list @ud) rows=(list (list dime))]
:: %row: line sections with suggested char widths
::
[%row wide=(list @ud) cols=(list dime)]
==
:: +shoe: gall agent core with extra arms
::
++ shoe
|* command-type=mold
$_ ^|
|_ bowl:gall
:: +command-parser: input parser for a specific session
::
:: if the head of the result is true, instantly run the command
::
++ command-parser
|~ sole-id=@ta
|~(nail *(like [? command-type]))
:: +tab-list: autocomplete options for the session (to match +command-parser)
::
++ tab-list
|~ sole-id=@ta
:: (list [@t tank])
*(list (option:auto tank))
:: +on-command: called when a valid command is run
::
++ on-command
|~ [sole-id=@ta command=command-type]
*(quip card _^|(..on-init))
::
++ can-connect
|~ sole-id=@ta
*?
::
++ on-connect
|~ sole-id=@ta
*(quip card _^|(..on-init))
::
++ on-disconnect
|~ sole-id=@ta
*(quip card _^|(..on-init))
::
::NOTE standard gall agent arms below, though they may produce %shoe cards
::
++ on-init
*(quip card _^|(..on-init))
::
++ on-save
*vase
::
++ on-load
|~ vase
*(quip card _^|(..on-init))
::
++ on-poke
|~ [mark vase]
*(quip card _^|(..on-init))
::
++ on-watch
|~ path
*(quip card _^|(..on-init))
::
++ on-leave
|~ path
*(quip card _^|(..on-init))
::
++ on-peek
|~ path
*(unit (unit cage))
::
++ on-agent
|~ [wire sign:agent:gall]
*(quip card _^|(..on-init))
::
++ on-arvo
|~ [wire sign-arvo]
*(quip card _^|(..on-init))
::
++ on-fail
|~ [term tang]
*(quip card _^|(..on-init))
--
:: +default: bare-minimum implementations of shoe arms
::
++ default
|* [shoe=* command-type=mold]
|_ =bowl:gall
++ command-parser
|= sole-id=@ta
(easy *[? command-type])
::
++ tab-list
|= sole-id=@ta
~
::
++ on-command
|= [sole-id=@ta command=command-type]
[~ shoe]
::
++ can-connect
|= sole-id=@ta
(team:title [our src]:bowl)
::
++ on-connect
|= sole-id=@ta
[~ shoe]
::
++ on-disconnect
|= sole-id=@ta
[~ shoe]
--
:: +agent: creates wrapper core that handles sole events and calls shoe arms
::
++ agent
|* command-type=mold
|= =(shoe command-type)
=| state-0
=* state -
^- agent:gall
=>
|%
++ deal
|= cards=(list card)
%+ turn cards
|= =card
^- card:agent:gall
?. ?=(%shoe -.card) card
?- -.effect.card
%sole
=- [%give %fact - %sole-effect !>(effect.effect.card)]
%+ turn
?^ sole-ids.card sole-ids.card
~(tap in ~(key by soles))
|= sole-id=@ta
/sole/[sole-id]
::
%table
=; fez=(list sole-effect)
$(effect.card [%sole %mor fez])
=, +.effect.card
:- (row:draw & wide head)
%+ turn rows
(cury (cury row:draw |) wide)
::
%row
$(effect.card [%sole (row:draw | +.effect.card)])
==
--
::
|_ =bowl:gall
+* this .
og ~(. shoe bowl)
::
++ on-init
^- (quip card:agent:gall agent:gall)
=^ cards shoe on-init:og
[(deal cards) this]
::
++ on-save !>([%shoe-app on-save:og state])
::
++ on-load
|= old-state=vase
^- (quip card:agent:gall agent:gall)
:: we could be upgrading from a shoe-less app, in which case the vase
:: contains inner application state instead of our +on-save.
:: to distinguish between the two, we check for the presence of our own
:: +on-save tag in the vase.
::
?. ?=([%shoe-app ^] q.old-state)
=^ cards shoe (on-load:og old-state)
[(deal cards) this]
=^ old-inner state +:!<([%shoe-app vase state-0] old-state)
=^ cards shoe (on-load:og old-inner)
[(deal cards) this]
::
++ on-poke
|= [=mark =vase]
^- (quip card:agent:gall agent:gall)
?. ?=(%sole-action mark)
=^ cards shoe (on-poke:og mark vase)
[(deal cards) this]
::
=/ act !<(sole-action vase)
=* sole-id id.act
=/ cli-state=sole-share
(~(gut by soles) sole-id *sole-share)
|^ =^ [cards=(list card) =_cli-state] shoe
?- -.dat.act
%det (apply-edit +.dat.act)
%clr [[~ cli-state] shoe]
%ret try-command
%tab [(tab +.dat.act) shoe]
==
:- (deal cards)
this(soles (~(put by soles) sole-id cli-state))
::
++ effect
|= =sole-effect
^- card
[%shoe [sole-id]~ %sole sole-effect]
::
++ apply-edit
|= =sole-change
^+ [[*(list card) cli-state] shoe]
=^ inverse cli-state
(~(transceive sole cli-state) sole-change)
:: res: & for fully parsed, | for parsing failure at location
::
=/ res=(each (unit [run=? cmd=command-type]) @ud)
%+ rose (tufa buf.cli-state)
(command-parser:og sole-id)
?: ?=(%& -.res)
:: only auto-run eligible commands if they were typed out
:: (that is, not retrieved from command history)
::
?. &(?=(^ p.res) run.u.p.res !?=(%set -.ted.sole-change))
[[~ cli-state] shoe]
(run-command cmd.u.p.res)
:_ shoe
:: parsing failed
::
?. &(?=(%del -.inverse) =(+(p.inverse) (lent buf.cli-state)))
:: if edit was somewhere in the middle, let it happen anyway
::
[~ cli-state]
:: if edit was insertion at buffer tail, revert it
::
=^ undo cli-state
(~(transmit sole cli-state) inverse)
:_ cli-state
:_ ~
%+ effect %mor
:~ [%det undo] :: undo edit
[%err p.res] :: cursor to error location
==
::
++ try-command
^+ [[*(list card) cli-state] shoe]
=/ res=(unit [? cmd=command-type])
%+ rust (tufa buf.cli-state)
(command-parser:og sole-id)
?^ res (run-command cmd.u.res)
[[[(effect %bel ~)]~ cli-state] shoe]
::
++ run-command
|= cmd=command-type
^+ [[*(list card) cli-state] shoe]
=^ cards shoe (on-command:og sole-id cmd)
:: clear buffer
::
=^ clear cli-state (~(transmit sole cli-state) [%set ~])
=- [[[- cards] cli-state] shoe]
%+ effect %mor
:~ [%nex ~]
[%det clear]
==
::
++ tab
|= pos=@ud
^- (quip card _cli-state)
=+ (get-id:auto pos (tufa buf.cli-state))
=/ needle=term
(fall id %$)
:: autocomplete empty command iff user at start of command
::
=/ options=(list (option:auto tank))
(search-prefix:auto needle (tab-list:og sole-id))
=/ advance=term
(longest-match:auto options)
=/ to-send=tape
%- trip
(rsh 3 (met 3 needle) advance)
=/ send-pos=@ud
%+ add pos
(met 3 (fall forward ''))
=| cards=(list card)
:: only render the option list if we couldn't complete anything
::
=? cards &(?=(~ to-send) ?=(^ options))
[(effect %tab options) cards]
|- ^- (quip card _cli-state)
?~ to-send
[(flop cards) cli-state]
=^ char cli-state
(~(transmit sole cli-state) [%ins send-pos `@c`i.to-send])
%_ $
cards [(effect %det char) cards]
send-pos +(send-pos)
to-send t.to-send
==
--
::
++ on-watch
|= =path
^- (quip card:agent:gall agent:gall)
?. ?=([%sole @ ~] path)
=^ cards shoe
(on-watch:og path)
[(deal cards) this]
=* sole-id i.t.path
?> (can-connect:og sole-id)
=. soles (~(put by soles) sole-id *sole-share)
=^ cards shoe
(on-connect:og sole-id)
:_ this
%- deal
:_ cards
[%shoe [sole-id]~ %sole %pro & dap.bowl "> "]
::
++ on-leave
|= =path
^- (quip card:agent:gall agent:gall)
=^ cards shoe (on-leave:og path)
[(deal cards) this]
::
++ on-peek
|= =path
^- (unit (unit cage))
?. =(/x/dbug/state path) ~
``noun+(slop on-save:og !>(shoe=state))
::
++ on-agent
|= [=wire =sign:agent:gall]
^- (quip card:agent:gall agent:gall)
=^ cards shoe (on-agent:og wire sign)
[(deal cards) this]
::
++ on-arvo
|= [=wire =sign-arvo]
^- (quip card:agent:gall agent:gall)
=^ cards shoe (on-arvo:og wire sign-arvo)
[(deal cards) this]
::
++ on-fail
|= [=term =tang]
^- (quip card:agent:gall agent:gall)
=^ cards shoe (on-fail:og term tang)
[(deal cards) this]
--
::
++ draw
|%
++ row
|= [bold=? wide=(list @ud) cols=(list dime)]
^- sole-effect
:- %mor
^- (list sole-effect)
=/ cows=(list [wid=@ud col=dime])
%- head
%^ spin cols wide
|= [col=dime wiz=(list @ud)]
~| [%too-few-wide col]
?> ?=(^ wiz)
[[i.wiz col] t.wiz]
=/ cobs=(list [wid=@ud (list tape)])
(turn cows col-as-lines)
=+ [lin=0 any=|]
=| fez=(list sole-effect)
|- ^+ fez
=; out=tape
:: done when we're past the end of all columns
::
?: (levy out (cury test ' '))
(flop fez)
=; fec=sole-effect
$(lin +(lin), fez [fec fez])
?. bold txt+out
klr+[[`%br ~ ~]^[(crip out)]~]~
%+ roll cobs
|= [[wid=@ud lines=(list tape)] out=tape]
%+ weld out
%+ weld ?~(out "" " ")
=+ l=(swag [lin 1] lines)
?^(l i.l (reap wid ' '))
::
++ col-as-lines
|= [wid=@ud col=dime]
^- [@ud (list tape)]
:- wid
%+ turn
(break wid (col-as-text col) (break-sets -.col))
(cury (cury pad wid) (alignment -.col))
::
++ col-as-text
|= col=dime
^- tape
?+ p.col (scow col)
%t (trip q.col)
%tas ['%' (scow col)]
==
::
++ alignment
|= wut=@ta
^- ?(%left %right)
?: ?=(?(%t %ta %tas %da) wut)
%left
%right
::
++ break-sets
|= wut=@ta
:: for: may break directly before these characters
:: aft: may break directly after these characters
:: new: always break on these characters, consuming them
::
^- [for=(set @t) aft=(set @t) new=(set @t)]
?+ wut [(sy " ") (sy ".:-/") (sy "\0a")]
?(%p %q) [(sy "-") (sy "-") ~]
%ux [(sy ".") ~ ~]
==
::
++ break
|= [wid=@ud cot=tape brs=_*break-sets]
^- (list tape)
~| [wid cot]
?: =("" cot) ~
=; [lin=tape rem=tape]
[lin $(cot rem)]
:: take snip of max width+1, search for breakpoint on that.
:: we grab one char extra, to look-ahead for for.brs.
:: later on, we always transfer _at least_ the extra char.
::
=^ lin=tape cot
[(scag +(wid) cot) (slag +(wid) cot)]
=+ len=(lent lin)
:: find the first newline character
::
=/ new=(unit @ud)
=+ new=~(tap in new.brs)
=| las=(unit @ud)
|-
?~ new las
$(new t.new, las (hunt lth las (find [i.new]~ lin)))
:: if we found a newline, break on it
::
?^ new
:- (scag u.new lin)
(weld (slag +(u.new) lin) cot)
:: if it fits, we're done
::
?: (lte len wid)
[lin cot]
=+ nil=(flop lin)
:: search for latest aft match
::
=/ aft=(unit @ud)
:: exclude the look-ahead character from search
::
=. len (dec len)
=. nil (slag 1 nil)
=- ?~(- ~ `+(u.-))
^- (unit @ud)
=+ aft=~(tap in aft.brs)
=| las=(unit @ud)
|-
?~ aft (bind las (cury sub (dec len)))
$(aft t.aft, las (hunt lth las (find [i.aft]~ nil)))
:: search for latest for match
::
=/ for=(unit @ud)
=+ for=~(tap in for.brs)
=| las=(unit @ud)
|-
?~ for (bind las (cury sub (dec len)))
=- $(for t.for, las (hunt lth las -))
=+ (find [i.for]~ nil)
:: don't break before the first character
::
?:(=(`(dec len) -) ~ -)
:: if any result, break as late as possible
::
=+ brk=(hunt gth aft for)
?~ brk
:: lin can't break, produce it in its entirety
:: (after moving the look-ahead character back)
::
:- (scag wid lin)
(weld (slag wid lin) cot)
:- (scag u.brk lin)
=. cot (weld (slag u.brk lin) cot)
:: eat any leading whitespace the next line might have, "clean break"
::
|- ^+ cot
?~ cot ~
?. ?=(?(%' ' %'\09') i.cot)
cot
$(cot t.cot)
::
++ pad
|= [wid=@ud lyn=?(%left %right) lin=tape]
^+ lin
=+ l=(lent lin)
?: (gte l wid) lin
=+ p=(reap (sub wid l) ' ')
?- lyn
%left (weld lin p)
%right (weld p lin)
==
--
--