mirror of
https://github.com/urbit/shrub.git
synced 2024-12-19 00:13:12 +03:00
Merge pull request #5663 from urbit/m/drum-sessions
term: extended session support, other improvements
This commit is contained in:
commit
f0c70e041d
@ -10,9 +10,9 @@
|
||||
:::: :: ::::
|
||||
:: :: ::
|
||||
=> |% :: external structures
|
||||
+$ id @tasession :: session id
|
||||
+$ id sole-id :: session id
|
||||
+$ house :: all state
|
||||
$: %8
|
||||
$: %9
|
||||
egg=@u :: command count
|
||||
hoc=(map id session) :: conversations
|
||||
acl=(set ship) :: remote access whitelist
|
||||
@ -820,12 +820,23 @@
|
||||
=/ poz=vase (dy-sore p.cig)
|
||||
=/ kev=vase
|
||||
=/ kuv=(unit vase) (slew 7 som)
|
||||
?: =(~ q.cig)
|
||||
(fall kuv !>(~))
|
||||
=/ soz=(list [var=term vax=vase])
|
||||
%~ tap by
|
||||
%- ~(run by q.cig)
|
||||
|=(val=(unit dojo-source) ?~(val !>([~ ~]) (dy-vase p.u.val)))
|
||||
:: if the generator takes a named argument "drum-session",
|
||||
:: then if a value isn't already supplied, we set it to the session
|
||||
:: that this dojo instance is being run in.
|
||||
:: (dojo is, indeed, quite coupled with drum.)
|
||||
::
|
||||
=? soz
|
||||
?& ?=(^ kuv)
|
||||
(slab %both %drum-session p.u.kuv)
|
||||
!(~(has by q.cig) %drum-session)
|
||||
==
|
||||
[[%drum-session !>(ses.id)] soz] ::TODO does the who matter?
|
||||
?: =(~ soz)
|
||||
(fall kuv !>(~))
|
||||
~| keyword-arg-failure+~(key by q.cig)
|
||||
%+ slap
|
||||
(with-faces kuv+(need kuv) rep+(with-faces soz) ~)
|
||||
@ -1018,13 +1029,14 @@
|
||||
|= =card:agent:gall
|
||||
^+ +>
|
||||
=? card ?=(%pass -.card)
|
||||
card(p [id p.card])
|
||||
^- card:agent:gall
|
||||
card(p [(scot %p who.id) ses.id p.card])
|
||||
%_(+> moz [card moz])
|
||||
::
|
||||
++ he-diff :: emit update
|
||||
|= fec=sole-effect
|
||||
^+ +>
|
||||
(he-card %give %fact ~[/sole/[id]] %sole-effect !>(fec))
|
||||
(he-card %give %fact ~[(id-to-path:sole id)] %sole-effect !>(fec))
|
||||
::
|
||||
++ he-stop :: abort work
|
||||
^+ .
|
||||
@ -1532,21 +1544,47 @@
|
||||
::
|
||||
++ on-load
|
||||
|= ole=vase
|
||||
^- (quip card:agent:gall _..on-init)
|
||||
|^ =+ old=!<(house-any ole)
|
||||
=? old ?=(%5 -.old)
|
||||
^- house-any
|
||||
^- house-6
|
||||
(house-5-to-6 old)
|
||||
=? old ?=(?(%6 %7) -.old)
|
||||
(house-6-7-to-8 +.old)
|
||||
?> ?=(%8 -.old)
|
||||
`..on-init(state old)
|
||||
=^ caz old
|
||||
?. ?=(%8 -.old) [~ old]
|
||||
(house-8-to-9 old)
|
||||
?> ?=(%9 -.old)
|
||||
[caz ..on-init(state old)]
|
||||
::
|
||||
+$ house-any $%(house house-7 house-6 house-5)
|
||||
+$ house-any $%(house house-8 house-7 house-6 house-5)
|
||||
::
|
||||
+$ id-8 @tasession
|
||||
+$ house-8
|
||||
$: %8
|
||||
egg=@u
|
||||
hoc=(map id-8 session)
|
||||
acl=(set ship)
|
||||
==
|
||||
++ house-8-to-9
|
||||
|= old=house-8
|
||||
^- (quip card:agent:gall house)
|
||||
:- %+ turn ~(tap in ~(key by hoc.old))
|
||||
|= id=@ta
|
||||
^- card:agent:gall
|
||||
[%give %kick ~[/sole/[id]] ~]
|
||||
=- [%9 egg.old - acl.old]
|
||||
%- ~(gas by *(map sole-id session))
|
||||
%+ murn ~(tap by hoc.old)
|
||||
|= [id=@ta s=session]
|
||||
(bind (upgrade-id:sole id) (late s))
|
||||
::
|
||||
+$ house-7 [%7 house-6-7]
|
||||
+$ house-6 [%6 house-6-7]
|
||||
+$ house-6-7
|
||||
$: egg=@u :: command count
|
||||
hoc=(map id session-6) :: conversations
|
||||
hoc=(map id-8 session-6) :: conversations
|
||||
acl=(set ship) :: remote access whitelist
|
||||
== ::
|
||||
+$ session-6 :: per conversation
|
||||
@ -1573,9 +1611,10 @@
|
||||
old(poy ~, -.dir [our.hid %base ud+0])
|
||||
::
|
||||
+$ house-5
|
||||
[%5 egg=@u hoc=(map id session)]
|
||||
[%5 egg=@u hoc=(map id-8 session-6)]
|
||||
++ house-5-to-6
|
||||
|= old=house-5
|
||||
^- house-6
|
||||
[%6 egg.old hoc.old *(set ship)]
|
||||
--
|
||||
::
|
||||
@ -1591,7 +1630,8 @@
|
||||
he-abet:(~(he-type he hid id.act ~ (~(got by hoc) id.act)) act)
|
||||
::
|
||||
%lens-command
|
||||
=+ !<([=id =command:lens] vase)
|
||||
=+ !<([ses=@ta =command:lens] vase)
|
||||
=/ =id [our.hid ses]
|
||||
he-abet:(~(he-lens he hid id ~ (~(got by hoc) id)) command)
|
||||
::
|
||||
%allow-remote-login
|
||||
@ -1629,8 +1669,7 @@
|
||||
?> ?| (team:title our.hid src.hid)
|
||||
(~(has in acl) src.hid)
|
||||
==
|
||||
?> ?=([%sole @ ~] path)
|
||||
=/ id i.t.path
|
||||
=/ =id (need (path-to-id:sole path))
|
||||
=? hoc (~(has by hoc) id)
|
||||
~& [%dojo-peer-replaced id]
|
||||
(~(del by hoc) id)
|
||||
@ -1642,7 +1681,7 @@
|
||||
++ on-leave
|
||||
|= =path
|
||||
?> ?=([%sole *] path)
|
||||
=. hoc (~(del by hoc) t.path)
|
||||
=. hoc (~(del by hoc) (need (path-to-id:sole path)))
|
||||
[~ ..on-init]
|
||||
::
|
||||
++ on-peek
|
||||
@ -1651,13 +1690,15 @@
|
||||
::
|
||||
++ on-agent
|
||||
|= [=wire =sign:agent:gall]
|
||||
?> ?=([@ @ *] wire)
|
||||
=/ =session (~(got by hoc) i.wire)
|
||||
=/ he-full ~(. he hid i.wire ~ session)
|
||||
^- (quip card:agent:gall _..on-init)
|
||||
?> ?=([@ @ @ *] wire)
|
||||
=/ =id [(slav %p i.wire) i.t.wire]
|
||||
=/ =session (~(got by hoc) id)
|
||||
=/ he-full ~(. he hid id ~ session)
|
||||
=^ moves state
|
||||
=< he-abet
|
||||
^+ he
|
||||
?+ i.t.wire ~|([%dojo-bad-on-agent wire -.sign] !!)
|
||||
?+ i.t.t.wire ~|([%dojo-bad-on-agent wire -.sign] !!)
|
||||
%poke (he-unto:he-full t.wire sign)
|
||||
%wool (he-wool:he-full t.wire sign)
|
||||
==
|
||||
@ -1665,14 +1706,16 @@
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
?> ?=([@ *] wire)
|
||||
=/ =session (~(got by hoc) i.wire)
|
||||
=/ he-full ~(. he hid i.wire ~ session)
|
||||
^- (quip card:agent:gall _..on-init)
|
||||
?> ?=([@ @ *] wire)
|
||||
=/ =id [(slav %p i.wire) i.t.wire]
|
||||
=/ =session (~(got by hoc) id)
|
||||
=/ he-full ~(. he hid id ~ session)
|
||||
=^ moves state
|
||||
=< he-abet
|
||||
?+ +<.sign-arvo ~|([%dojo-bad-take +<.sign-arvo] !!)
|
||||
%writ (he-writ:he-full t.wire +>.sign-arvo)
|
||||
%http-response (he-http-response:he-full t.wire +>.sign-arvo)
|
||||
%writ (he-writ:he-full t.t.wire +>.sign-arvo)
|
||||
%http-response (he-http-response:he-full t.t.wire +>.sign-arvo)
|
||||
==
|
||||
[moves ..on-init]
|
||||
:: if dojo fails unexpectedly, kill whatever each session is working on
|
||||
|
@ -44,17 +44,12 @@
|
||||
~| path
|
||||
?> ?=([%session @ %view ~] path)
|
||||
=* ses i.t.path
|
||||
:~ :: subscribe to the requested session
|
||||
::
|
||||
::NOTE multiple views do not result in multiple subscriptions
|
||||
:: because they go over the same wire/duct
|
||||
::
|
||||
(pass-session ses %view ~)
|
||||
:: tell session to refresh, so new client knows what's on screen
|
||||
::TODO should client be responsible for this?
|
||||
::
|
||||
(pass-session ses %hail ~)
|
||||
==
|
||||
:: subscribe to the requested session
|
||||
::
|
||||
::NOTE multiple views do not result in multiple subscriptions
|
||||
:: because they go over the same wire/duct
|
||||
::
|
||||
[(pass-session ses %view ~)]~
|
||||
::
|
||||
++ on-arvo
|
||||
|= [=wire =sign-arvo]
|
||||
|
@ -83,7 +83,8 @@
|
||||
::
|
||||
?+ -.source.com
|
||||
:_ this(job.state (some [eyre-id com]))
|
||||
[%pass /sole %agent [our.bowl %dojo] %watch /sole/[eyre-id]]~
|
||||
=/ =path /sole/(scot %p our.bowl)/[eyre-id]
|
||||
[%pass /sole %agent [our.bowl %dojo] %watch path]~
|
||||
::
|
||||
%export
|
||||
:_ this(job.state (some [eyre-id com]))
|
||||
|
@ -43,13 +43,13 @@
|
||||
++ on-fail on-fail:def
|
||||
::
|
||||
++ command-parser
|
||||
|= sole-id=@ta
|
||||
|= =sole-id:shoe
|
||||
^+ |~(nail *(like [? command]))
|
||||
%+ stag &
|
||||
(perk %demo %row %table ~)
|
||||
::
|
||||
++ tab-list
|
||||
|= sole-id=@ta
|
||||
|= =sole-id:shoe
|
||||
^- (list [@t tank])
|
||||
:~ ['demo' leaf+"run example command"]
|
||||
['row' leaf+"print a row"]
|
||||
@ -57,7 +57,7 @@
|
||||
==
|
||||
::
|
||||
++ on-command
|
||||
|= [sole-id=@ta =command]
|
||||
|= [=sole-id:shoe =command]
|
||||
^- (quip card _this)
|
||||
=; [to=(list _sole-id) fec=shoe-effect:shoe]
|
||||
[[%shoe to fec]~ this]
|
||||
@ -87,7 +87,7 @@
|
||||
==
|
||||
::
|
||||
++ can-connect
|
||||
|= sole-id=@ta
|
||||
|= =sole-id:shoe
|
||||
^- ?
|
||||
?| =(~zod src.bowl)
|
||||
(team:title [our src]:bowl)
|
||||
|
@ -8,9 +8,11 @@
|
||||
::
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ byk=beak]
|
||||
[arg=$?([dap=term ~] [who=ship dap=term ~]) ~]
|
||||
arg=$?([dap=term ~] [who=ship dap=term ~])
|
||||
drum-session=@ta
|
||||
==
|
||||
:- %drum-link
|
||||
:- drum-session
|
||||
?~ +.arg
|
||||
[p.byk dap.arg]
|
||||
[who.arg dap.arg]
|
||||
|
@ -8,9 +8,11 @@
|
||||
::
|
||||
:- %say
|
||||
|= $: [now=@da eny=@uvJ byk=beak]
|
||||
[arg=$?([dap=term ~] [who=ship dap=term ~]) ~]
|
||||
arg=$?([dap=term ~] [who=ship dap=term ~])
|
||||
drum-session=@ta
|
||||
==
|
||||
:- %drum-unlink
|
||||
:- drum-session
|
||||
?~ +.arg
|
||||
[p.byk dap.arg]
|
||||
[who.arg dap.arg]
|
||||
|
@ -15,8 +15,7 @@
|
||||
+$ state-2 [%2 pith-2]
|
||||
::
|
||||
+$ pith-5
|
||||
$: eel=(set gill:gall) :: connect to
|
||||
bin=(map @ source) :: terminals
|
||||
$: bin=(map @ source) :: terminals
|
||||
==
|
||||
::
|
||||
+$ pith-4
|
||||
@ -75,6 +74,7 @@
|
||||
off=@ud :: window offset
|
||||
kil=kill :: kill buffer
|
||||
inx=@ud :: ring index
|
||||
eel=(set gill:gall) :: connect to
|
||||
fug=(map gill:gall (unit target)) :: connections
|
||||
mir=(pair @ud stub) :: mirrored terminal
|
||||
== ::
|
||||
@ -105,15 +105,17 @@
|
||||
:: :: ::
|
||||
|%
|
||||
++ en-gill :: gill to wire
|
||||
|= gyl=gill:gall
|
||||
|= [ses=@tas gyl=gill:gall]
|
||||
^- wire
|
||||
::TODO include session?
|
||||
[%drum %phat (scot %p p.gyl) q.gyl ~]
|
||||
[%drum %phat (scot %p p.gyl) q.gyl ?:(=(%$ ses) ~ [ses ~])]
|
||||
::
|
||||
++ de-gill :: gill from wire
|
||||
|= way=wire ^- gill:gall
|
||||
~| way
|
||||
?>(?=([@ @ *] way) [(slav %p i.way) i.t.way])
|
||||
|= way=wire
|
||||
^- [@tas gill:gall]
|
||||
~| wire=way
|
||||
?> ?=([@ @ ?(~ [@ ~])] way)
|
||||
:- ?~(t.t.way %$ i.t.t.way)
|
||||
[(slav %p i.way) i.t.way]
|
||||
--
|
||||
::
|
||||
|= [hid=bowl:gall state]
|
||||
@ -122,24 +124,29 @@
|
||||
=+ (~(gut by bin) ses *source)
|
||||
=* dev -
|
||||
=| moz=(list card:agent:gall)
|
||||
=| biz=(list dill-blit:dill)
|
||||
=| biz=(list blit:dill) ::TODO should be per-session
|
||||
|%
|
||||
++ this .
|
||||
++ klr klr:format
|
||||
+$ state ^state :: proxy
|
||||
+$ any-state ^any-state :: proxy
|
||||
++ on-init (poke-link our.hid %dojo)
|
||||
++ on-init (poke-link %$ our.hid %dojo)
|
||||
::
|
||||
++ prep
|
||||
|= s=@tas
|
||||
=. ses ses
|
||||
=. ses s
|
||||
=. dev (~(gut by bin) ses *source)
|
||||
this
|
||||
::
|
||||
++ open
|
||||
%+ cork de-gill
|
||||
|= [s=@tas g=gill:gall]
|
||||
[g (prep s)]
|
||||
::
|
||||
++ diff-sole-effect-phat :: app event
|
||||
|= [way=wire fec=sole-effect]
|
||||
=< se-abet =< se-view
|
||||
=+ gyl=(de-gill way)
|
||||
=< se-abet
|
||||
=^ gyl this (open way)
|
||||
?: (se-aint gyl) +>.$
|
||||
(se-diff gyl fec)
|
||||
::
|
||||
@ -149,7 +156,7 @@
|
||||
(prep i.t.pax)
|
||||
~| [%drum-unauthorized our+our.hid src+src.hid] :: ourself
|
||||
?> (team:title our.hid src.hid) :: or our own moon
|
||||
=< se-abet =< se-view
|
||||
=< se-abet
|
||||
(se-text "[{<src.hid>}, driving {<our.hid>}]")
|
||||
::
|
||||
++ poke-dill
|
||||
@ -158,7 +165,7 @@
|
||||
::
|
||||
++ poke-dill-belt :: terminal event
|
||||
|= bet=dill-belt:dill
|
||||
=< se-abet =< se-view
|
||||
=< se-abet
|
||||
(se-belt bet)
|
||||
::
|
||||
++ poke-dill-blit :: terminal output
|
||||
@ -166,21 +173,22 @@
|
||||
se-abet:(se-blit-sys bit)
|
||||
::
|
||||
++ poke-link :: connect app
|
||||
|= gyl=gill:gall
|
||||
=< se-abet =< se-view
|
||||
(se-link gyl)
|
||||
|= [ses=@tas gyl=gill:gall]
|
||||
=< se-abet
|
||||
(se-link:(prep ses) gyl)
|
||||
::
|
||||
++ poke-unlink :: disconnect app
|
||||
|= gyl=gill:gall
|
||||
=< se-abet =< se-view
|
||||
(se-drop:(se-pull gyl) & gyl)
|
||||
|= [ses=@ta gyl=gill:gall]
|
||||
=< se-abet
|
||||
(se-drop:(se-pull:(prep ses) gyl) & gyl)
|
||||
::
|
||||
++ poke-exit :: shutdown
|
||||
|= ~
|
||||
se-abet:(se-blit-sys `dill-blit:dill`[%qit ~])
|
||||
::
|
||||
++ poke-put :: write file
|
||||
|= [pax=path txt=@]
|
||||
|= [pax=path arg=$@(@ [@tas @])]
|
||||
=^ txt +> ?@(arg [arg +>] [+.arg (prep -.arg)])
|
||||
se-abet:(se-blit-sys [%sav pax txt]) ::
|
||||
::
|
||||
++ poke
|
||||
@ -196,16 +204,16 @@
|
||||
::
|
||||
++ on-load
|
||||
|= [hood-version=@ud old=any-state]
|
||||
=< se-abet =< se-view
|
||||
=< se-abet
|
||||
=? old ?=(%2 -.old) [%4 [eel bin]:old]
|
||||
=? old ?=(%3 -.old) [%4 [eel bin]:old]
|
||||
=? old ?=(%4 -.old)
|
||||
:+ %5 eel.old
|
||||
|^ (~(run by bin.old) source-4-to-5)
|
||||
|^ 5+(~(run by bin.old) source-4-to-5)
|
||||
++ source-4-to-5
|
||||
|= s=source-4
|
||||
|= source-4
|
||||
^- source
|
||||
s(fug (~(run by fug.s) |=(t=(unit target-4) (bind t target-4-to-5))))
|
||||
=; fug [edg off kil inx eel.old fug mir]
|
||||
(~(run by fug) |=(t=(unit target-4) (bind t target-4-to-5)))
|
||||
::
|
||||
++ target-4-to-5
|
||||
|= t=target-4
|
||||
@ -228,8 +236,8 @@
|
||||
::
|
||||
++ reap-phat :: ack connect
|
||||
|= [way=wire saw=(unit tang)]
|
||||
=< se-abet =< se-view
|
||||
=+ gyl=(de-gill way)
|
||||
=< se-abet
|
||||
=^ gyl this (open way)
|
||||
?~ saw
|
||||
(se-join gyl)
|
||||
:: Don't print stack trace because we probably just crashed to
|
||||
@ -239,9 +247,9 @@
|
||||
::
|
||||
++ take-coup-phat :: ack poke
|
||||
|= [way=wire saw=(unit tang)]
|
||||
=< se-abet =< se-view
|
||||
=< se-abet
|
||||
?~ saw +>
|
||||
=+ gyl=(de-gill way)
|
||||
=^ gyl this (open way)
|
||||
?: (se-aint gyl) +>.$
|
||||
%- se-dump:(se-drop:(se-pull gyl) & gyl)
|
||||
:_ u.saw
|
||||
@ -264,8 +272,8 @@
|
||||
::
|
||||
++ quit-phat ::
|
||||
|= way=wire
|
||||
=< se-abet =< se-view
|
||||
=+ gyl=(de-gill way)
|
||||
=< se-abet
|
||||
=^ gyl this (open way)
|
||||
~& [%drum-quit src.hid gyl]
|
||||
(se-drop %| gyl)
|
||||
:: :: ::
|
||||
@ -273,14 +281,18 @@
|
||||
:: :: ::
|
||||
++ se-abet :: resolve
|
||||
^- (quip card:agent:gall state)
|
||||
=. . se-subze:se-adze
|
||||
=. . se-view:se-subze:se-adze
|
||||
:_ sat(bin (~(put by bin) ses dev))
|
||||
^- (list card:agent:gall)
|
||||
?~ biz (flop moz)
|
||||
:_ (flop moz)
|
||||
=/ =dill-blit:dill ?~(t.biz i.biz [%mor (flop biz)])
|
||||
=/ =blit:dill ?~(t.biz i.biz [%mor (flop biz)])
|
||||
::TODO remove /drum after dill cleans up
|
||||
[%give %fact ~[/drum /dill/[ses]] %dill-blit !>(dill-blit)]
|
||||
::TODO but once we remove it, the empty trailing segment of
|
||||
:: /dill/[ses] would prevent outsiders from subscribing
|
||||
:: to the default session...
|
||||
=/ to=(list path) [/dill/[ses] ?~(ses ~[/drum] ~)]
|
||||
[%give %fact to %dill-blit !>(blit)]
|
||||
::
|
||||
++ se-adze :: update connections
|
||||
^+ .
|
||||
@ -306,7 +318,7 @@
|
||||
=< .(con +>)
|
||||
|: $:,[[ses=@tas dev=source] con=_.] ^+ con
|
||||
=+ xeno=se-subze-local:%_(con ses ses, dev dev)
|
||||
xeno(ses ses.con, dev dev.con, bin (~(put by bin) ses dev.xeno))
|
||||
xeno(ses ses.con, dev dev.con, bin (~(put by bin.xeno) ses dev.xeno))
|
||||
::
|
||||
++ se-subze-local
|
||||
^+ .
|
||||
@ -359,7 +371,7 @@
|
||||
[%cru *] (se-dump:(se-text (trip p.bet)) q.bet)
|
||||
[%hey *] +>(mir [0 ~]) :: refresh
|
||||
[%rez *] +>(edg (dec p.bet)) :: resize window
|
||||
[%yow *] ~&([%no-yow -.bet] +>)
|
||||
[%yow *] (se-link p.bet)
|
||||
==
|
||||
=+ gul=se-agon
|
||||
?: |(?=(~ gul) (se-aint u.gul))
|
||||
@ -463,7 +475,7 @@
|
||||
+>(eel (~(put in eel) gyl))
|
||||
::
|
||||
++ se-blit :: give output
|
||||
|= bil=dill-blit:dill
|
||||
|= bil=blit:dill
|
||||
+>(biz [bil biz])
|
||||
::
|
||||
++ se-blit-sys :: output to system
|
||||
@ -519,19 +531,18 @@
|
||||
::
|
||||
++ se-poke :: send a poke
|
||||
|= [gyl=gill:gall par=cage]
|
||||
(se-emit %pass (en-gill gyl) %agent gyl %poke par)
|
||||
(se-emit %pass (en-gill ses gyl) %agent gyl %poke par)
|
||||
::
|
||||
++ se-peer :: send a peer
|
||||
|= gyl=gill:gall
|
||||
~> %slog.0^leaf/"drum: link {<[p q]:gyl>}"
|
||||
::TODO include session
|
||||
=/ =path /sole/(cat 3 'drum_' (scot %p our.hid))
|
||||
=/ =path (id-to-path:sole our.hid ses)
|
||||
%- se-emit(fug (~(put by fug) gyl ~))
|
||||
[%pass (en-gill gyl) %agent gyl %watch path]
|
||||
[%pass (en-gill ses gyl) %agent gyl %watch path]
|
||||
::
|
||||
++ se-pull :: cancel subscription
|
||||
|= gyl=gill:gall
|
||||
(se-emit %pass (en-gill gyl) %agent gyl %leave ~)
|
||||
(se-emit %pass (en-gill ses gyl) %agent gyl %leave ~)
|
||||
::
|
||||
++ se-tame :: switch connection
|
||||
|= gyl=gill:gall
|
||||
@ -556,7 +567,7 @@
|
||||
^+ +>
|
||||
(ta-poke %sole-action !>(act))
|
||||
::
|
||||
++ ta-id (cat 3 'drum_' (scot %p our.hid)) :: per-ship duct id
|
||||
++ ta-id [our.hid ses] :: per-ship-session id
|
||||
::
|
||||
++ ta-aro :: hear arrow
|
||||
|= key=?(%d %l %r %u)
|
||||
@ -631,7 +642,7 @@
|
||||
%d ?^ buf.say.inp
|
||||
ta-del
|
||||
?: =([our.hid %dojo] gyl)
|
||||
+>(..ta (se-blit qit+~)) :: quit pier
|
||||
+>(..ta (se-blit-sys %qit ~)) :: quit pier
|
||||
+>(..ta (se-klin gyl)) :: unlink app
|
||||
%e +>(pos.inp (lent buf.say.inp))
|
||||
%f (ta-aro %r)
|
||||
@ -676,13 +687,12 @@
|
||||
(ta-hom %del pos.inp)
|
||||
::
|
||||
++ ta-hit :: hear click
|
||||
|= [r=@ud c=@ud]
|
||||
|= [x=@ud y=@ud]
|
||||
^+ +>
|
||||
?. =(0 r) +>
|
||||
=/ pol=@ud
|
||||
(lent-char:klr (make:klr cad.pom))
|
||||
?: (lth c pol) +>.$
|
||||
+>.$(pos.inp (min (sub c pol) (lent buf.say.inp)))
|
||||
=? x (lth x pol) pol
|
||||
+>.$(pos.inp (min (sub x pol) (lent buf.say.inp)))
|
||||
::
|
||||
++ ta-erl :: hear local error
|
||||
|= pos=@ud
|
||||
|
@ -21,7 +21,7 @@
|
||||
%mor [%a (turn p.dib |=(a=dill-blit:dill json(dib a)))]
|
||||
%hop %+ frond %hop
|
||||
?@ p.dib (numb p.dib)
|
||||
(pairs 'r'^(numb r.p.dib) 'c'^(numb c.p.dib) ~)
|
||||
(pairs 'x'^(numb x.p.dib) 'y'^(numb y.p.dib) ~)
|
||||
%put (frond -.dib (tape (tufa p.dib)))
|
||||
?(%bel %clr) (frond %act %s -.dib)
|
||||
==
|
||||
|
@ -1,8 +1,7 @@
|
||||
:: %drum-put: download into host system
|
||||
::
|
||||
:::: /hoon/do-claim/womb/mar
|
||||
::
|
||||
/? 310
|
||||
|_ [path @]
|
||||
|_ [path $@(@ [@ta @])]
|
||||
::
|
||||
++ grad %noun
|
||||
++ grow
|
||||
@ -11,6 +10,6 @@
|
||||
--
|
||||
++ grab :: convert from
|
||||
|%
|
||||
+$ noun [path @] :: clam from %noun
|
||||
+$ noun [path $@(@ [@ta @])] :: clam from %noun
|
||||
--
|
||||
--
|
||||
|
@ -1 +1 @@
|
||||
[%zuse 417]
|
||||
[%zuse 416]
|
||||
|
@ -3,7 +3,7 @@
|
||||
!:
|
||||
=> ..part
|
||||
|%
|
||||
++ lull %328
|
||||
++ lull %327
|
||||
:: :: ::
|
||||
:::: :: :: (1) models
|
||||
:: :: ::
|
||||
@ -1209,16 +1209,17 @@
|
||||
$% [%aro p=?(%d %l %r %u)] :: arrow key
|
||||
[%bac ~] :: true backspace
|
||||
[%del ~] :: true delete
|
||||
[%hit r=@ud c=@ud] :: mouse click
|
||||
[%hit x=@ud y=@ud] :: mouse click
|
||||
[%ret ~] :: return
|
||||
== ::
|
||||
+$ blit :: client output
|
||||
$% [%bel ~] :: make a noise
|
||||
[%clr ~] :: clear the screen
|
||||
[%hop p=$@(@ud [r=@ud c=@ud])] :: set cursor col/pos
|
||||
[%hop p=$@(@ud [x=@ud y=@ud])] :: set cursor col/pos
|
||||
[%klr p=stub] :: put styled
|
||||
[%put p=(list @c)] :: put text at cursor
|
||||
[%mor p=(list blit)] :: multiple blits
|
||||
[%nel ~] :: newline
|
||||
[%put p=(list @c)] :: put text at cursor
|
||||
[%sag p=path q=*] :: save to jamfile
|
||||
[%sav p=path q=@] :: save to file
|
||||
[%url p=@t] :: activate url
|
||||
@ -1233,7 +1234,6 @@
|
||||
== ::
|
||||
+$ dill-blit :: arvo output
|
||||
$% blit :: client output
|
||||
[%mor p=(list dill-blit)] :: multiple blits
|
||||
[%qit ~] :: close console
|
||||
== ::
|
||||
+$ flog :: sent to %dill
|
||||
|
@ -176,13 +176,10 @@
|
||||
++ from :: receive blit
|
||||
|= bit=dill-blit
|
||||
^+ +>
|
||||
?: ?=(%mor -.bit)
|
||||
|- ^+ +>.^$
|
||||
?~ p.bit +>.^$
|
||||
$(p.bit t.p.bit, +>.^$ ^$(bit i.p.bit))
|
||||
?: ?=(%qit -.bit)
|
||||
(dump %logo ~)
|
||||
(done %blit [bit ~])
|
||||
::TODO so why is this a (list blit) again?
|
||||
(done %blit bit ~)
|
||||
::
|
||||
++ sponsor
|
||||
^- ship
|
||||
@ -212,16 +209,12 @@
|
||||
++ open
|
||||
|= gyl=(list gill)
|
||||
::TODO should allow handlers from non-base desks
|
||||
=. +> (pass / %g %jolt %base ram)
|
||||
::TODO maybe ensure :ram is running?
|
||||
=. +> peer
|
||||
%+ roll gyl
|
||||
|= [g=gill _..open]
|
||||
(send [%yow g])
|
||||
::
|
||||
++ shut
|
||||
::TODO send a %bye blit?
|
||||
pull(eye.all (~(del by eye.all) ses))
|
||||
::
|
||||
++ send :: send action
|
||||
|= bet=dill-belt
|
||||
^+ +>
|
||||
@ -375,7 +368,12 @@
|
||||
=/ nus
|
||||
~| [%no-session ses]
|
||||
(need (ax hen ses))
|
||||
=^ moz all abet:shut:nus
|
||||
::NOTE we do deletion from state outside of the core,
|
||||
:: because +abet would re-insert.
|
||||
::TODO send a %bye blit? xx
|
||||
=^ moz all abet:pull:nus
|
||||
=. dug.all (~(del by dug.all) ses)
|
||||
=. eye.all (~(del by eye.all) ses)
|
||||
[moz ..^$]
|
||||
:: %view opens a subscription to the target session, on the current duct
|
||||
::
|
||||
|
@ -4,7 +4,7 @@
|
||||
=> ..lull
|
||||
~% %zuse ..part ~
|
||||
|%
|
||||
++ zuse %417
|
||||
++ zuse %416
|
||||
:: :: ::
|
||||
:::: :: :: (2) engines
|
||||
:: :: ::
|
||||
@ -3902,29 +3902,24 @@
|
||||
++ slag :: slag stub
|
||||
|= [a=@ b=stub]
|
||||
^- stub
|
||||
=+ c=(lnts-char b)
|
||||
=+ i=(brek a c)
|
||||
?~ i ~
|
||||
=+ r=(^slag +(p.u.i) b)
|
||||
?: =(a q.u.i)
|
||||
r
|
||||
=+ n=(snag p.u.i b)
|
||||
:_ r :- p.n
|
||||
(^slag (sub (snag p.u.i c) (sub q.u.i a)) q.n)
|
||||
?: =(0 a) b
|
||||
?~ b ~
|
||||
=+ c=(lent q.i.b)
|
||||
?: =(c a) t.b
|
||||
?: (gth c a)
|
||||
[[p.i.b (^slag a q.i.b)] t.b]
|
||||
$(a (sub a c), b t.b)
|
||||
::
|
||||
++ scag :: scag stub
|
||||
|= [a=@ b=stub]
|
||||
^- stub
|
||||
=+ c=(lnts-char b)
|
||||
=+ i=(brek a c)
|
||||
?~ i b
|
||||
?: =(a q.u.i)
|
||||
(^scag +(p.u.i) b)
|
||||
%+ welp
|
||||
(^scag p.u.i b)
|
||||
=+ n=(snag p.u.i b)
|
||||
:_ ~ :- p.n
|
||||
(^scag (sub (snag p.u.i c) (sub q.u.i a)) q.n)
|
||||
?: =(0 a) ~
|
||||
?~ b ~
|
||||
=+ c=(lent q.i.b)
|
||||
?: (gth c a)
|
||||
[p.i.b (^scag a q.i.b)]~
|
||||
:- i.b
|
||||
$(a (sub a c), b t.b)
|
||||
::
|
||||
++ swag :: swag stub
|
||||
|= [[a=@ b=@] c=stub]
|
||||
|
@ -13,11 +13,12 @@
|
||||
%bel b+&
|
||||
%clr b+&
|
||||
%hop ?@ p.blit (numb p.blit)
|
||||
(pairs 'r'^(numb r.p.blit) 'c'^(numb c.p.blit) ~)
|
||||
(pairs 'x'^(numb x.p.blit) 'y'^(numb y.p.blit) ~)
|
||||
%put a+(turn p.blit |=(c=@c s+(tuft c)))
|
||||
%nel b+&
|
||||
%url s+p.blit
|
||||
%wyp b+&
|
||||
%mor a+(turn p.blit ^blit)
|
||||
::
|
||||
%sag
|
||||
%- pairs
|
||||
@ -73,7 +74,7 @@
|
||||
:~ aro+(su (perk %d %l %r %u ~))
|
||||
bac+ul
|
||||
del+ul
|
||||
hit+(ot 'r'^ni 'c'^ni ~)
|
||||
hit+(ot 'x'^ni 'y'^ni ~)
|
||||
ret+ul
|
||||
==
|
||||
::
|
||||
|
@ -13,15 +13,15 @@
|
||||
/- *sole
|
||||
/+ sole, auto=language-server-complete
|
||||
|%
|
||||
+$ state-0
|
||||
$: %0
|
||||
soles=(map @ta sole-share)
|
||||
+$ state-1
|
||||
$: %1
|
||||
soles=(map sole-id 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 sole-ids=(list sole-id) effect=shoe-effect] :: ~ sends to all
|
||||
==
|
||||
:: $shoe-effect: easier sole-effects
|
||||
::
|
||||
@ -47,30 +47,30 @@
|
||||
:: if the head of the result is true, instantly run the command
|
||||
::
|
||||
++ command-parser
|
||||
|~ sole-id=@ta
|
||||
|~ =sole-id
|
||||
|~(nail *(like [? command-type]))
|
||||
:: +tab-list: autocomplete options for the session (to match +command-parser)
|
||||
::
|
||||
++ tab-list
|
||||
|~ sole-id=@ta
|
||||
|~ =sole-id
|
||||
:: (list [@t tank])
|
||||
*(list (option:auto tank))
|
||||
:: +on-command: called when a valid command is run
|
||||
::
|
||||
++ on-command
|
||||
|~ [sole-id=@ta command=command-type]
|
||||
|~ [=sole-id command=command-type]
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ can-connect
|
||||
|~ sole-id=@ta
|
||||
|~ =sole-id
|
||||
*?
|
||||
::
|
||||
++ on-connect
|
||||
|~ sole-id=@ta
|
||||
|~ =sole-id
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
++ on-disconnect
|
||||
|~ sole-id=@ta
|
||||
|~ =sole-id
|
||||
*(quip card _^|(..on-init))
|
||||
::
|
||||
::NOTE standard gall agent arms below, though they may produce %shoe cards
|
||||
@ -119,27 +119,27 @@
|
||||
|* [shoe=* command-type=mold]
|
||||
|_ =bowl:gall
|
||||
++ command-parser
|
||||
|= sole-id=@ta
|
||||
|= =sole-id
|
||||
(easy *[? command-type])
|
||||
::
|
||||
++ tab-list
|
||||
|= sole-id=@ta
|
||||
|= =sole-id
|
||||
~
|
||||
::
|
||||
++ on-command
|
||||
|= [sole-id=@ta command=command-type]
|
||||
|= [=sole-id command=command-type]
|
||||
[~ shoe]
|
||||
::
|
||||
++ can-connect
|
||||
|= sole-id=@ta
|
||||
|= =sole-id
|
||||
(team:title [our src]:bowl)
|
||||
::
|
||||
++ on-connect
|
||||
|= sole-id=@ta
|
||||
|= =sole-id
|
||||
[~ shoe]
|
||||
::
|
||||
++ on-disconnect
|
||||
|= sole-id=@ta
|
||||
|= =sole-id
|
||||
[~ shoe]
|
||||
--
|
||||
:: +agent: creates wrapper core that handles sole events and calls shoe arms
|
||||
@ -147,7 +147,7 @@
|
||||
++ agent
|
||||
|* command-type=mold
|
||||
|= =(shoe command-type)
|
||||
=| state-0
|
||||
=| state-1
|
||||
=* state -
|
||||
^- agent:gall
|
||||
=>
|
||||
@ -164,8 +164,7 @@
|
||||
%+ turn
|
||||
?^ sole-ids.card sole-ids.card
|
||||
~(tap in ~(key by soles))
|
||||
|= sole-id=@ta
|
||||
/sole/[sole-id]
|
||||
id-to-path:sole
|
||||
::
|
||||
%table
|
||||
=; fez=(list sole-effect)
|
||||
@ -202,9 +201,36 @@
|
||||
?. ?=([%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]
|
||||
|^ =| old-outer=state-any
|
||||
=^ old-inner old-outer
|
||||
+:!<([%shoe-app vase state-any] old-state)
|
||||
:: ~! q.old-state
|
||||
:: ?+ +>.q.old-state !!
|
||||
:: [%0 *] +:!<([%shoe-app vase state-0] old-state)
|
||||
:: [%1 *] +:!<([%shoe-app vase state-1] old-state)
|
||||
:: ==
|
||||
=^ caz shoe (on-load:og old-inner)
|
||||
=^ cuz old-outer
|
||||
?. ?=(%0 -.old-outer) [~ old-outer]
|
||||
(state-0-to-1 old-outer)
|
||||
?> ?=(%1 -.old-outer)
|
||||
[(weld cuz (deal caz)) this(state old-outer)]
|
||||
::
|
||||
+$ state-any $%(state-1 state-0)
|
||||
+$ state-0 [%0 soles=(map @ta sole-share)]
|
||||
++ state-0-to-1
|
||||
|= old=state-0
|
||||
^- (quip card:agent:gall state-1)
|
||||
:- %+ turn ~(tap in ~(key by soles.old))
|
||||
|= id=@ta
|
||||
^- card:agent:gall
|
||||
[%give %kick ~[/sole/[id]] ~]
|
||||
:- %1
|
||||
%- ~(gas by *(map sole-id sole-share))
|
||||
%+ murn ~(tap by soles.old)
|
||||
|= [id=@ta s=sole-share]
|
||||
(bind (upgrade-id:sole id) (late s))
|
||||
--
|
||||
::
|
||||
++ on-poke
|
||||
|= [=mark =vase]
|
||||
@ -326,19 +352,18 @@
|
||||
++ on-watch
|
||||
|= =path
|
||||
^- (quip card:agent:gall agent:gall)
|
||||
?. ?=([%sole @ ~] path)
|
||||
?~ sole-id=(path-to-id: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)
|
||||
?> (can-connect:og u.sole-id)
|
||||
=. soles (~(put by soles) u.sole-id *sole-share)
|
||||
=^ cards shoe
|
||||
(on-connect:og sole-id)
|
||||
(on-connect:og u.sole-id)
|
||||
:_ this
|
||||
%- deal
|
||||
:_ cards
|
||||
[%shoe [sole-id]~ %sole %pro & dap.bowl "> "]
|
||||
[%shoe [u.sole-id]~ %sole %pro & dap.bowl "> "]
|
||||
::
|
||||
++ on-leave
|
||||
|= =path
|
||||
|
@ -136,4 +136,28 @@
|
||||
=+ dat=(transmute [%mor leg] [%ins pos `@c`0])
|
||||
?> ?=(%ins -.dat)
|
||||
p.dat
|
||||
::
|
||||
::
|
||||
++ path-to-id
|
||||
|= =path
|
||||
^- (unit sole-id)
|
||||
?. ?=([%sole @ ?(~ [@ ~])] path) ~
|
||||
?~ who=(slaw %p i.t.path) ~
|
||||
`[u.who ?~(t.t.path %$ i.t.t.path)]
|
||||
::
|
||||
++ id-to-path
|
||||
|= sole-id
|
||||
^- path
|
||||
::TODO this whole "no empty path ending" business feels icky.
|
||||
:: do we want default session to be ~.~ ?
|
||||
:: concern here is that outsiders cannot subscribe to the default
|
||||
:: session, because /sole/~zod/ isn't a valid path...
|
||||
[%sole (scot %p who) ?~(ses ~ /[ses])]
|
||||
::
|
||||
++ upgrade-id
|
||||
|= old=@ta
|
||||
^- (unit sole-id)
|
||||
%+ rush old
|
||||
%+ cook (late %$)
|
||||
;~(pfix (jest 'drum_~') fed:ag)
|
||||
--
|
||||
|
@ -20,7 +20,9 @@
|
||||
|= jon=^json ^- sole-action
|
||||
%- need %. jon
|
||||
=> [dejs-soft:format ..sole-action]
|
||||
|^ (ot id+so dat+(fo %ret (of det+change tab+ni ~)) ~)
|
||||
|^ (ot id+id dat+(fo %ret (of det+change tab+ni ~)) ~)
|
||||
++ id
|
||||
(ot who+(su ;~(pfix sig fed:ag)) ses+so ~)
|
||||
++ fo
|
||||
|* [a=term b=fist]
|
||||
|=(c=json ?.(=([%s a] c) (b c) (some [a ~])))
|
||||
|
@ -3,8 +3,9 @@
|
||||
::
|
||||
^?
|
||||
|%
|
||||
+$ sole-id [who=@p ses=@ta]
|
||||
+$ sole-action :: sole to app
|
||||
$: id=@ta :: duct id
|
||||
$: id=sole-id :: session id
|
||||
$= dat
|
||||
$% :: [%abo ~] :: reset interaction
|
||||
[%det sole-change] :: command line edit
|
||||
|
@ -1 +1 @@
|
||||
[%zuse 417]
|
||||
[%zuse 416]
|
||||
|
@ -1 +1 @@
|
||||
[%zuse 417]
|
||||
[%zuse 416]
|
||||
|
23
pkg/interface/webterm/.eslintrc.js
Normal file
23
pkg/interface/webterm/.eslintrc.js
Normal file
@ -0,0 +1,23 @@
|
||||
module.exports = exports = {
|
||||
"rules": {
|
||||
"spaced-comment": 0,
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:import/errors",
|
||||
"plugin:react/recommended",
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
},
|
||||
"import/resolver": {
|
||||
typescript: {} // this loads <rootdir>/tsconfig.json to eslint
|
||||
},
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es6": true
|
||||
},
|
||||
"plugins": ["import", "react-hooks"]
|
||||
}
|
1
pkg/interface/webterm/.nvmrc
Normal file
1
pkg/interface/webterm/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
16.14.0
|
94
pkg/interface/webterm/App.tsx
Normal file
94
pkg/interface/webterm/App.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import React, {
|
||||
useCallback, useEffect
|
||||
} from 'react';
|
||||
|
||||
import useTermState from './state';
|
||||
import { useDark } from './lib/useDark';
|
||||
import api from './api';
|
||||
|
||||
import { _dark, _light } from '@tlon/indigo-react';
|
||||
|
||||
import 'xterm/css/xterm.css';
|
||||
|
||||
import {
|
||||
scrySessions
|
||||
} from '@urbit/api/term';
|
||||
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { Tabs } from './Tabs';
|
||||
import Buffer from './Buffer';
|
||||
import { DEFAULT_SESSION } from './constants';
|
||||
import { showSlog } from './lib/blit';
|
||||
import { InfoButton } from './InfoButton';
|
||||
|
||||
const initSessions = async () => {
|
||||
const response = await api.scry(scrySessions());
|
||||
|
||||
useTermState.getState().set((state) => {
|
||||
state.names = response.sort();
|
||||
});
|
||||
};
|
||||
|
||||
export default function TermApp() {
|
||||
const { names, selected } = useTermState();
|
||||
const dark = useDark();
|
||||
|
||||
const setupSlog = useCallback(() => {
|
||||
console.log('slog: setting up...');
|
||||
let available = false;
|
||||
const slog = new EventSource('/~_~/slog', { withCredentials: true });
|
||||
|
||||
slog.onopen = () => {
|
||||
console.log('slog: opened stream');
|
||||
available = true;
|
||||
};
|
||||
|
||||
slog.onmessage = (e) => {
|
||||
const session = useTermState.getState().sessions[DEFAULT_SESSION];
|
||||
if (!session) {
|
||||
console.log('slog: default session mia!', 'msg:', e.data);
|
||||
console.log(Object.keys(useTermState.getState().sessions), session);
|
||||
return;
|
||||
}
|
||||
showSlog(session.term, e.data);
|
||||
};
|
||||
|
||||
slog.onerror = (e) => {
|
||||
console.error('slog: eventsource error:', e);
|
||||
if (available) {
|
||||
window.setTimeout(() => {
|
||||
if (slog.readyState !== EventSource.CLOSED) {
|
||||
return;
|
||||
}
|
||||
console.log('slog: reconnecting...');
|
||||
setupSlog();
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
|
||||
useTermState.getState().set((state) => {
|
||||
state.slogstream = slog;
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
initSessions();
|
||||
setupSlog();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={dark ? _dark : _light}>
|
||||
<div className="header">
|
||||
<Tabs />
|
||||
<InfoButton />
|
||||
</div>
|
||||
<div className="buffer-container">
|
||||
{names.map((name) => {
|
||||
return <Buffer key={name} name={name} selected={name === selected} dark={dark} />;
|
||||
})}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
350
pkg/interface/webterm/Buffer.tsx
Normal file
350
pkg/interface/webterm/Buffer.tsx
Normal file
@ -0,0 +1,350 @@
|
||||
import { Terminal, ITerminalOptions } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { debounce } from 'lodash';
|
||||
import bel from './lib/bel';
|
||||
import api from './api';
|
||||
|
||||
import {
|
||||
Belt, pokeTask, pokeBelt
|
||||
} from '@urbit/api/term';
|
||||
import { Session } from './state';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import useTermState from './state';
|
||||
import React from 'react';
|
||||
import { Box, Col } from '@tlon/indigo-react';
|
||||
import { makeTheme } from './lib/theme';
|
||||
import { showBlit, csi, hasBell } from './lib/blit';
|
||||
import { DEFAULT_SESSION, RESIZE_DEBOUNCE_MS, RESIZE_THRESHOLD_PX } from './constants';
|
||||
import { retry } from './lib/retry';
|
||||
|
||||
const termConfig: ITerminalOptions = {
|
||||
logLevel: 'warn',
|
||||
//
|
||||
convertEol: true,
|
||||
//
|
||||
rows: 24,
|
||||
cols: 80,
|
||||
scrollback: 10000,
|
||||
//
|
||||
fontFamily: '"Source Code Pro", "Roboto mono", "Courier New", monospace',
|
||||
fontWeight: 400,
|
||||
// NOTE theme colors configured dynamically
|
||||
//
|
||||
bellStyle: 'sound',
|
||||
bellSound: bel,
|
||||
//
|
||||
// allows text selection by holding modifier (option, or shift)
|
||||
macOptionClickForcesSelection: true,
|
||||
// prevent insertion of simulated arrow keys on-altclick
|
||||
altClickMovesCursor: false
|
||||
};
|
||||
|
||||
const readInput = (term: Terminal, e: string): Belt[] => {
|
||||
const belts: Belt[] = [];
|
||||
let strap = '';
|
||||
|
||||
while (e.length > 0) {
|
||||
let c = e.charCodeAt(0);
|
||||
|
||||
// text input
|
||||
//
|
||||
if (c >= 32 && c !== 127) {
|
||||
strap += e[0];
|
||||
e = e.slice(1); //TODO revisit wrt (list @c) & unicode characters
|
||||
continue;
|
||||
} else if ('' !== strap) {
|
||||
belts.push({ txt: strap.split('') });
|
||||
strap = '';
|
||||
}
|
||||
|
||||
// special keys/characters
|
||||
//
|
||||
if (0 === c) {
|
||||
term.write('\x07'); // bel
|
||||
} else if (8 === c || 127 === c) {
|
||||
belts.push({ bac: null });
|
||||
} else if (13 === c) {
|
||||
belts.push({ ret: null });
|
||||
} else if (c <= 26) {
|
||||
const k = String.fromCharCode(96 + c);
|
||||
//NOTE prevent remote shut-downs
|
||||
if ('d' !== k) {
|
||||
belts.push({ mod: { mod: 'ctl', key: k } });
|
||||
}
|
||||
}
|
||||
|
||||
// escape sequences
|
||||
//
|
||||
if (27 === c) { // ESC
|
||||
e = e.slice(1);
|
||||
c = e.charCodeAt(0);
|
||||
if (91 === c || 79 === c) { // [ or O
|
||||
e = e.slice(1);
|
||||
c = e.charCodeAt(0);
|
||||
/* eslint-disable max-statements-per-line */
|
||||
switch (c) {
|
||||
case 65: belts.push({ aro: 'u' }); break;
|
||||
case 66: belts.push({ aro: 'd' }); break;
|
||||
case 67: belts.push({ aro: 'r' }); break;
|
||||
case 68: belts.push({ aro: 'l' }); break;
|
||||
//
|
||||
case 77: {
|
||||
const m = e.charCodeAt(1) - 31;
|
||||
if (1 === m) {
|
||||
const c = e.charCodeAt(2) - 32;
|
||||
const r = e.charCodeAt(3) - 32;
|
||||
belts.push({ hit: { y: r - 1, x: c - 1 } });
|
||||
}
|
||||
e = e.slice(3);
|
||||
break;
|
||||
}
|
||||
//
|
||||
default: term.write('\x07'); break; // bel
|
||||
}
|
||||
} else if (c >= 97 && c <= 122) { // a <= c <= z
|
||||
belts.push({ mod: { mod: 'met', key: e[0] } });
|
||||
} else if (c === 46) { // .
|
||||
belts.push({ mod: { mod: 'met', key: '.' } });
|
||||
} else if (c === 8 || c === 127) {
|
||||
belts.push({ mod: { mod: 'met', key: { bac: null } } });
|
||||
} else {
|
||||
term.write('\x07'); break; // bel
|
||||
}
|
||||
}
|
||||
|
||||
e = e.slice(1);
|
||||
}
|
||||
if ('' !== strap) {
|
||||
if (1 === strap.length) {
|
||||
belts.push(strap);
|
||||
} else {
|
||||
belts.push({ txt: strap.split('') });
|
||||
}
|
||||
strap = '';
|
||||
}
|
||||
return belts;
|
||||
};
|
||||
|
||||
const onResize = async (name: string, session: Session) => {
|
||||
if (session) {
|
||||
session.fit.fit();
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name].pending++;
|
||||
});
|
||||
api.poke(pokeTask(name, { blew: { w: session.term.cols, h: session.term.rows } })).then(() => {
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name].pending--;
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onInput = (name: string, session: Session, e: string) => {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
const term = session.term;
|
||||
const belts = readInput(term, e);
|
||||
belts.forEach((b) => {
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name].pending++;
|
||||
});
|
||||
api.poke(pokeBelt(name, b)).then(() => {
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name].pending--;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
interface BufferProps {
|
||||
name: string,
|
||||
selected: boolean,
|
||||
dark: boolean,
|
||||
}
|
||||
|
||||
export default function Buffer({ name, selected, dark }: BufferProps) {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const session: Session = useTermState(s => s.sessions[name]);
|
||||
|
||||
const initSession = useCallback(async (name: string, dark: boolean) => {
|
||||
console.log('setting up', name === DEFAULT_SESSION ? 'default' : name);
|
||||
|
||||
// set up xterm terminal
|
||||
//
|
||||
const term = new Terminal(termConfig);
|
||||
term.options.theme = makeTheme(dark);
|
||||
const fit = new FitAddon();
|
||||
term.loadAddon(fit);
|
||||
fit.fit();
|
||||
term.focus();
|
||||
|
||||
// start mouse reporting
|
||||
//
|
||||
term.write(csi('?9h'));
|
||||
|
||||
const ses: Session = {
|
||||
term,
|
||||
fit,
|
||||
hasBell: false,
|
||||
pending: 0,
|
||||
subscriptionId: null
|
||||
};
|
||||
|
||||
// set up event handlers
|
||||
//
|
||||
term.attachCustomKeyEventHandler((e: KeyboardEvent) => {
|
||||
//NOTE ctrl+shift keypresses never make it into term.onData somehow,
|
||||
// so we handle them specially ourselves.
|
||||
// we may be able to remove this once xterm.js fixes #3382 & co.
|
||||
if (e.shiftKey
|
||||
&& e.ctrlKey
|
||||
&& e.type === 'keydown'
|
||||
&& e.key.length === 1
|
||||
) {
|
||||
api.poke(pokeBelt(name, { mod: { mod: 'ctl', key: e.key } }));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
term.onData(e => onInput(name, ses, e));
|
||||
term.onBinary(e => onInput(name, ses, e));
|
||||
|
||||
// open subscription
|
||||
//
|
||||
const initSubscription = async () => {
|
||||
const subscriptionId = await api.subscribe({
|
||||
app: 'herm', path: '/session/' + name + '/view',
|
||||
event: (e) => {
|
||||
showBlit(ses.term, e);
|
||||
//NOTE getting selected from state because selected prop is stale
|
||||
if (hasBell(e) && (useTermState.getState().selected !== name)) {
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name].hasBell = true;
|
||||
});
|
||||
}
|
||||
//TODO should handle %bye on this higher level though, for deletion
|
||||
},
|
||||
err: (e, id) => {
|
||||
console.log(`subscription error, id ${id}:`, e);
|
||||
},
|
||||
quit: async () => { // quit
|
||||
console.error('quit, reconnecting...');
|
||||
try {
|
||||
const newSubscriptionId = await retry(initSubscription, () => {
|
||||
console.log('attempting to reconnect ...');
|
||||
}, 5);
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name].subscriptionId = newSubscriptionId;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('unable to reconnect', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return subscriptionId;
|
||||
};
|
||||
|
||||
ses.subscriptionId = await initSubscription();
|
||||
|
||||
useTermState.getState().set((state) => {
|
||||
state.sessions[name] = ses;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const shouldResize = useCallback(() => {
|
||||
if(!session) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const containerHeight = document.querySelector('.buffer-container')?.clientHeight || Infinity;
|
||||
const terminalHeight = session.term.element?.clientHeight || 0;
|
||||
|
||||
return (containerHeight - terminalHeight) >= RESIZE_THRESHOLD_PX;
|
||||
}, [session]);
|
||||
|
||||
const onSelect = useCallback(async () => {
|
||||
if (session && selected && shouldResize()) {
|
||||
session.fit.fit();
|
||||
await api.poke(pokeTask(name, { blew: { w: session.term.cols, h: session.term.rows } }));
|
||||
session.term.focus();
|
||||
}
|
||||
}, [session?.term, selected]);
|
||||
|
||||
// Effects
|
||||
// init session
|
||||
useEffect(() => {
|
||||
if(session) {
|
||||
return;
|
||||
}
|
||||
|
||||
initSession(name, dark);
|
||||
}, [name]);
|
||||
|
||||
// attach to DOM when ref is available
|
||||
useEffect(() => {
|
||||
if(session && containerRef.current && !session.term.element) {
|
||||
session.term.open(containerRef.current);
|
||||
}
|
||||
}, [session, containerRef]);
|
||||
|
||||
// initialize resize listeners
|
||||
//
|
||||
useEffect(() => {
|
||||
if(!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: use ResizeObserver for improved performance?
|
||||
const debouncedResize = debounce(() => onResize(name, session), RESIZE_DEBOUNCE_MS);
|
||||
window.addEventListener('resize', debouncedResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', debouncedResize);
|
||||
};
|
||||
}, [session]);
|
||||
|
||||
// on dark mode change, change terminals' theme
|
||||
//
|
||||
useEffect(() => {
|
||||
const theme = makeTheme(dark);
|
||||
if (session) {
|
||||
session.term.options.theme = theme;
|
||||
}
|
||||
if (containerRef.current) {
|
||||
containerRef.current.style.backgroundColor = theme.background || '';
|
||||
}
|
||||
}, [session, dark]);
|
||||
|
||||
// On select, resize, focus, and poke herm with updated cols and rows
|
||||
useEffect(() => {
|
||||
onSelect();
|
||||
}, [onSelect]);
|
||||
|
||||
return (
|
||||
!session && !selected ?
|
||||
<p>Loading...</p>
|
||||
:
|
||||
<Box
|
||||
width='100%'
|
||||
height='100%'
|
||||
bg='white'
|
||||
fontFamily='mono'
|
||||
overflow='hidden'
|
||||
className="terminal-container"
|
||||
style={selected ? { zIndex: 999 } : {}}
|
||||
>
|
||||
<Col
|
||||
width='100%'
|
||||
height='100%'
|
||||
minHeight='0'
|
||||
px={['0', '2']}
|
||||
pb={['0', '2']}
|
||||
ref={containerRef}
|
||||
>
|
||||
</Col>
|
||||
</Box>
|
||||
);
|
||||
}
|
24
pkg/interface/webterm/InfoButton.tsx
Normal file
24
pkg/interface/webterm/InfoButton.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Icon } from '@tlon/indigo-react';
|
||||
import { useDetectOS } from './lib/useDetectOS';
|
||||
|
||||
export const InfoButton = () => {
|
||||
const { isMacOS } = useDetectOS();
|
||||
|
||||
const onInfoClick = useCallback(() => {
|
||||
const key = isMacOS ? 'alt' : 'shift';
|
||||
|
||||
alert(`To select text in the terminal, hold down the ${key} key.`);
|
||||
}, [isMacOS]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button className="info-btn" onClick={onInfoClick}>
|
||||
<Icon
|
||||
icon="Info"
|
||||
size="18px"
|
||||
/>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
};
|
55
pkg/interface/webterm/Spinner.tsx
Normal file
55
pkg/interface/webterm/Spinner.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import useIsMounted from './lib/useIsMounted';
|
||||
import React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const DELAY_MS = 1000;
|
||||
const FRAME_MS = 250;
|
||||
const CHARS = '|/-\\';
|
||||
|
||||
const Spinner = () => {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [intervalTimer, setIntervalTimer] = useState<ReturnType<typeof setInterval> | undefined>();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
useEffect(() => {
|
||||
setIntervalTimer(
|
||||
setInterval(() => {
|
||||
if (isMounted()) {
|
||||
setIndex(idx => idx === CHARS.length - 1 ? 0 : idx + 1);
|
||||
}
|
||||
}, FRAME_MS)
|
||||
);
|
||||
|
||||
return () => {
|
||||
if (intervalTimer) {
|
||||
clearInterval(intervalTimer);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <span> {CHARS[index]}</span>;
|
||||
};
|
||||
|
||||
export const DelayedSpinner = () => {
|
||||
const [showSpinner, setShowSpinner] = useState(false);
|
||||
const [delayTimer, setDelayTimer] = useState<ReturnType<typeof setTimeout> | undefined>();
|
||||
const isMounted = useIsMounted();
|
||||
|
||||
useEffect(() => {
|
||||
setDelayTimer(
|
||||
setTimeout(() => {
|
||||
if (isMounted()) {
|
||||
setShowSpinner(true);
|
||||
}
|
||||
}, DELAY_MS)
|
||||
);
|
||||
|
||||
return () => {
|
||||
if (delayTimer) {
|
||||
clearTimeout(delayTimer);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return showSpinner ? <Spinner /> : null;
|
||||
};
|
55
pkg/interface/webterm/Tab.tsx
Normal file
55
pkg/interface/webterm/Tab.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import { DEFAULT_SESSION } from './constants';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import useTermState, { Session } from './state';
|
||||
import api from './api';
|
||||
import { pokeTask } from '@urbit/api/term';
|
||||
import { DelayedSpinner as Spinner } from './Spinner';
|
||||
|
||||
interface TabProps {
|
||||
session: Session;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const Tab = ( { session, name }: TabProps ) => {
|
||||
const isSelected = useTermState().selected === name;
|
||||
|
||||
const onClick = () => {
|
||||
useTermState.getState().set((state) => {
|
||||
state.selected = name;
|
||||
state.sessions[name].hasBell = false;
|
||||
});
|
||||
};
|
||||
|
||||
const onDelete = useCallback(async (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
// clean up subscription
|
||||
if(session && session.subscriptionId) {
|
||||
await api.unsubscribe(session.subscriptionId);
|
||||
}
|
||||
|
||||
// DELETE
|
||||
await api.poke(pokeTask(name, { shut: null }));
|
||||
|
||||
// remove from zustand
|
||||
useTermState.getState().set((state) => {
|
||||
if (state.selected === name) {
|
||||
state.selected = DEFAULT_SESSION;
|
||||
}
|
||||
state.names = state.names.filter(n => n !== name);
|
||||
delete state.sessions[name];
|
||||
});
|
||||
}, [session]);
|
||||
|
||||
return (
|
||||
<div className={'tab ' + (isSelected ? 'selected' : '')} onClick={onClick}>
|
||||
<a className='session-name'>
|
||||
{session?.hasBell ? '🔔 ' : ''}
|
||||
{name === DEFAULT_SESSION ? 'default' : name}
|
||||
{session && session.pending > 0 ? <Spinner /> : null}
|
||||
{' '}
|
||||
</a>
|
||||
{name === DEFAULT_SESSION ? null : <a className="delete-session" onClick={onDelete}>x</a>}
|
||||
</div>
|
||||
);
|
||||
};
|
26
pkg/interface/webterm/Tabs.tsx
Normal file
26
pkg/interface/webterm/Tabs.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import useTermState from './state';
|
||||
import { Tab } from './Tab';
|
||||
import { useAddSession } from './lib/useAddSession';
|
||||
import { Icon } from '@tlon/indigo-react';
|
||||
|
||||
export const Tabs = () => {
|
||||
const { sessions, names } = useTermState();
|
||||
const addSession = useAddSession();
|
||||
|
||||
return (
|
||||
<div className="tabs">
|
||||
{names.map((n, i) => {
|
||||
return (
|
||||
<Tab session={sessions[n]} name={n} key={i} />
|
||||
);
|
||||
})}
|
||||
<button className="tab" onClick={addSession}>
|
||||
<Icon
|
||||
icon="Plus"
|
||||
size="18px"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,454 +0,0 @@
|
||||
/* eslint-disable max-lines */
|
||||
import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
useCallback
|
||||
} from 'react';
|
||||
|
||||
import useTermState from './state';
|
||||
import { useDark } from './join';
|
||||
import api from './api';
|
||||
|
||||
import { Terminal, ITerminalOptions, ITheme } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
import { Box, Col, Reset, _dark, _light } from '@tlon/indigo-react';
|
||||
|
||||
import 'xterm/css/xterm.css';
|
||||
|
||||
import {
|
||||
Belt, Blit, Stye, Stub, Tint, Deco,
|
||||
pokeTask, pokeBelt
|
||||
} from '@urbit/api/term';
|
||||
|
||||
import bel from './lib/bel';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
type TermAppProps = {
|
||||
ship: string;
|
||||
}
|
||||
|
||||
const makeTheme = (dark: boolean): ITheme => {
|
||||
let fg, bg: string;
|
||||
if (dark) {
|
||||
fg = 'white';
|
||||
bg = 'rgb(26,26,26)';
|
||||
} else {
|
||||
fg = 'black';
|
||||
bg = 'white';
|
||||
}
|
||||
// TODO indigo colors.
|
||||
// we can't pluck these from ThemeContext because they have transparency.
|
||||
// technically xterm supports transparency, but it degrades performance.
|
||||
return {
|
||||
foreground: fg,
|
||||
background: bg,
|
||||
brightBlack: '#7f7f7f', // NOTE slogs
|
||||
cursor: fg,
|
||||
cursorAccent: bg,
|
||||
selection: fg
|
||||
};
|
||||
};
|
||||
|
||||
const termConfig: ITerminalOptions = {
|
||||
logLevel: 'warn',
|
||||
//
|
||||
convertEol: true,
|
||||
//
|
||||
rows: 24,
|
||||
cols: 80,
|
||||
scrollback: 10000,
|
||||
//
|
||||
fontFamily: '"Source Code Pro", "Roboto mono", "Courier New", monospace',
|
||||
fontWeight: 400,
|
||||
// NOTE theme colors configured dynamically
|
||||
//
|
||||
bellStyle: 'sound',
|
||||
bellSound: bel,
|
||||
//
|
||||
// allows text selection by holding modifier (option, or shift)
|
||||
macOptionClickForcesSelection: true,
|
||||
// prevent insertion of simulated arrow keys on-altclick
|
||||
altClickMovesCursor: false
|
||||
};
|
||||
|
||||
const csi = (cmd: string, ...args: number[]) => {
|
||||
return '\x1b[' + args.join(';') + cmd;
|
||||
};
|
||||
|
||||
const tint = (t: Tint) => {
|
||||
switch (t) {
|
||||
case null: return '9';
|
||||
case 'k': return '0';
|
||||
case 'r': return '1';
|
||||
case 'g': return '2';
|
||||
case 'y': return '3';
|
||||
case 'b': return '4';
|
||||
case 'm': return '5';
|
||||
case 'c': return '6';
|
||||
case 'w': return '7';
|
||||
default: return `8;2;${t.r%256};${t.g%256};${t.b%256}`;
|
||||
}
|
||||
};
|
||||
|
||||
const stye = (s: Stye) => {
|
||||
let out = '';
|
||||
|
||||
// text decorations
|
||||
//
|
||||
if (s.deco.length > 0) {
|
||||
out += s.deco.reduce((decs: number[], deco: Deco) => {
|
||||
/* eslint-disable max-statements-per-line */
|
||||
switch (deco) {
|
||||
case null: decs.push(0); return decs;
|
||||
case 'br': decs.push(1); return decs;
|
||||
case 'un': decs.push(4); return decs;
|
||||
case 'bl': decs.push(5); return decs;
|
||||
default: console.log('weird deco', deco); return decs;
|
||||
}
|
||||
}, []).join(';');
|
||||
}
|
||||
|
||||
// background color
|
||||
//
|
||||
if (s.back !== null) {
|
||||
if (out !== '') {
|
||||
out += ';';
|
||||
}
|
||||
out += '4';
|
||||
out += tint(s.back);
|
||||
}
|
||||
|
||||
// foreground color
|
||||
//
|
||||
if (s.fore !== null) {
|
||||
if (out !== '') {
|
||||
out += ';';
|
||||
}
|
||||
out += '3';
|
||||
out += tint(s.fore);
|
||||
}
|
||||
|
||||
if (out === '') {
|
||||
return out;
|
||||
}
|
||||
return '\x1b[' + out + 'm';
|
||||
};
|
||||
|
||||
const showBlit = (term: Terminal, blit: Blit) => {
|
||||
let out = '';
|
||||
|
||||
if ('bel' in blit) {
|
||||
out += '\x07';
|
||||
} else if ('clr' in blit) {
|
||||
term.clear();
|
||||
out += csi('u');
|
||||
} else if ('hop' in blit) {
|
||||
if (typeof blit.hop === 'number') {
|
||||
out += csi('H', term.rows, blit.hop + 1);
|
||||
} else {
|
||||
out += csi('H', term.rows - blit.hop.r, blit.hop.c + 1);
|
||||
}
|
||||
out += csi('s'); // save cursor position
|
||||
} else if ('put' in blit) {
|
||||
out += blit.put.join('');
|
||||
out += csi('u');
|
||||
} else if ('klr' in blit) {
|
||||
out += blit.klr.reduce((lin: string, p: Stub) => {
|
||||
lin += stye(p.stye);
|
||||
lin += p.text.join('');
|
||||
lin += csi('m', 0);
|
||||
return lin;
|
||||
}, '');
|
||||
out += csi('u');
|
||||
} else if ('nel' in blit) {
|
||||
out += '\n';
|
||||
} else if ('sag' in blit || 'sav' in blit) {
|
||||
const sav = ('sag' in blit) ? blit.sag : blit.sav;
|
||||
const name = sav.path.split('/').slice(-2).join('.');
|
||||
const buff = Buffer.from(sav.file, 'base64');
|
||||
const blob = new Blob([buff], { type: 'application/octet-stream' });
|
||||
saveAs(blob, name);
|
||||
} else if ('url' in blit) {
|
||||
window.open(blit.url);
|
||||
} else if ('wyp' in blit) {
|
||||
out += '\r' + csi('K');
|
||||
out += csi('u');
|
||||
//
|
||||
} else {
|
||||
console.log('weird blit', blit);
|
||||
}
|
||||
|
||||
term.write(out);
|
||||
};
|
||||
|
||||
// NOTE should generally only be passed the default terminal session
|
||||
const showSlog = (term: Terminal, slog: string) => {
|
||||
// set scroll region to exclude the bottom line,
|
||||
// scroll up one line,
|
||||
// move cursor to start of the newly created whitespace,
|
||||
// set text to grey,
|
||||
// print the slog,
|
||||
// restore color, scroll region, and cursor.
|
||||
//
|
||||
term.write(csi('r', 1, term.rows - 1)
|
||||
+ csi('S', 1)
|
||||
+ csi('H', term.rows - 1, 1)
|
||||
+ csi('m', 90)
|
||||
+ slog
|
||||
+ csi('m', 0)
|
||||
+ csi('r')
|
||||
+ csi('u'));
|
||||
};
|
||||
|
||||
const readInput = (term: Terminal, e: string): Belt[] => {
|
||||
const belts: Belt[] = [];
|
||||
let strap = '';
|
||||
|
||||
while (e.length > 0) {
|
||||
let c = e.charCodeAt(0);
|
||||
|
||||
// text input
|
||||
//
|
||||
if (c >= 32 && c !== 127) {
|
||||
strap += e[0];
|
||||
e = e.slice(1);
|
||||
continue;
|
||||
} else if ('' !== strap) {
|
||||
belts.push({ txt: strap.split('') });
|
||||
strap = '';
|
||||
}
|
||||
|
||||
// special keys/characters
|
||||
//
|
||||
if (0 === c) {
|
||||
term.write('\x07'); // bel
|
||||
} else if (8 === c || 127 === c) {
|
||||
belts.push({ bac: null });
|
||||
} else if (13 === c) {
|
||||
belts.push({ ret: null });
|
||||
} else if (c <= 26) {
|
||||
let k = String.fromCharCode(96 + c);
|
||||
//NOTE prevent remote shut-downs
|
||||
if ('d' !== k) {
|
||||
belts.push({ mod: { mod: 'ctl', key: k } });
|
||||
}
|
||||
}
|
||||
|
||||
// escape sequences
|
||||
//
|
||||
if (27 === c) { // ESC
|
||||
e = e.slice(1);
|
||||
c = e.charCodeAt(0);
|
||||
if (91 === c || 79 === c) { // [ or O
|
||||
e = e.slice(1);
|
||||
c = e.charCodeAt(0);
|
||||
/* eslint-disable max-statements-per-line */
|
||||
switch (c) {
|
||||
case 65: belts.push({ aro: 'u' }); break;
|
||||
case 66: belts.push({ aro: 'd' }); break;
|
||||
case 67: belts.push({ aro: 'r' }); break;
|
||||
case 68: belts.push({ aro: 'l' }); break;
|
||||
//
|
||||
case 77: {
|
||||
const m = e.charCodeAt(1) - 31;
|
||||
if (1 === m) {
|
||||
const c = e.charCodeAt(2) - 32;
|
||||
const r = e.charCodeAt(3) - 32;
|
||||
belts.push({ hit: { r: term.rows - r, c: c - 1 } });
|
||||
}
|
||||
e = e.slice(3);
|
||||
break;
|
||||
}
|
||||
//
|
||||
default: term.write('\x07'); break; // bel
|
||||
}
|
||||
} else if (c >= 97 && c <= 122) { // a <= c <= z
|
||||
belts.push({ mod: { mod: 'met', key: e[0] } });
|
||||
} else if (c === 46) { // .
|
||||
belts.push({ mod: { mod: 'met', key: '.' } });
|
||||
} else if (c === 8 || c === 127) {
|
||||
belts.push({ mod: { mod: 'met', key: { bac: null } } });
|
||||
} else {
|
||||
term.write('\x07'); break; // bel
|
||||
}
|
||||
}
|
||||
|
||||
e = e.slice(1);
|
||||
}
|
||||
if ('' !== strap) {
|
||||
belts.push({ txt: strap.split('') });
|
||||
strap = '';
|
||||
}
|
||||
return belts;
|
||||
};
|
||||
|
||||
export default function TermApp(props: TermAppProps) {
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
// TODO allow switching of selected
|
||||
const { sessions, selected, slogstream, set } = useTermState();
|
||||
const session = sessions[selected];
|
||||
const dark = useDark();
|
||||
|
||||
const setupSlog = useCallback(() => {
|
||||
console.log('slog: setting up...');
|
||||
let available = false;
|
||||
const slog = new EventSource('/~_~/slog', { withCredentials: true });
|
||||
|
||||
slog.onopen = (e) => {
|
||||
console.log('slog: opened stream');
|
||||
available = true;
|
||||
};
|
||||
|
||||
slog.onmessage = (e) => {
|
||||
const session = useTermState.getState().sessions[''];
|
||||
if (!session) {
|
||||
console.log('default session mia!', 'slog:', slog);
|
||||
return;
|
||||
}
|
||||
showSlog(session.term, e.data);
|
||||
};
|
||||
|
||||
slog.onerror = (e) => {
|
||||
console.error('slog: eventsource error:', e);
|
||||
if (available) {
|
||||
window.setTimeout(() => {
|
||||
if (slog.readyState !== EventSource.CLOSED) {
|
||||
return;
|
||||
}
|
||||
console.log('slog: reconnecting...');
|
||||
setupSlog();
|
||||
}, 10000);
|
||||
}
|
||||
};
|
||||
|
||||
set((state) => {
|
||||
state.slogstream = slog;
|
||||
});
|
||||
}, [sessions]);
|
||||
|
||||
const onInput = useCallback((ses: string, e: string) => {
|
||||
const term = useTermState.getState().sessions[ses].term;
|
||||
const belts = readInput(term, e);
|
||||
belts.map((b) => {
|
||||
api.poke(pokeBelt(ses, b));
|
||||
});
|
||||
}, [sessions]);
|
||||
|
||||
const onResize = useCallback(() => {
|
||||
// TODO debounce, if it ever becomes a problem
|
||||
session?.fit.fit();
|
||||
}, [session]);
|
||||
|
||||
// on-init, open slogstream
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!slogstream) {
|
||||
setupSlog();
|
||||
}
|
||||
window.addEventListener('resize', onResize);
|
||||
return () => {
|
||||
// TODO clean up subs?
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
}, [onResize, setupSlog]);
|
||||
|
||||
// on dark mode change, change terminals' theme
|
||||
//
|
||||
useEffect(() => {
|
||||
const theme = makeTheme(dark);
|
||||
for (const ses in sessions) {
|
||||
sessions[ses].term.setOption('theme', theme);
|
||||
}
|
||||
if (container.current) {
|
||||
container.current.style.backgroundColor = theme.background || '';
|
||||
}
|
||||
}, [dark, sessions]);
|
||||
|
||||
// on selected change, maybe setup the term, or put it into the container
|
||||
//
|
||||
useEffect(() => {
|
||||
let ses = session;
|
||||
// initialize terminal
|
||||
//
|
||||
if (!ses) {
|
||||
// set up terminal
|
||||
//
|
||||
const term = new Terminal(termConfig);
|
||||
term.setOption('theme', makeTheme(dark));
|
||||
const fit = new FitAddon();
|
||||
term.loadAddon(fit);
|
||||
|
||||
// start mouse reporting
|
||||
//
|
||||
term.write(csi('?9h'));
|
||||
|
||||
// set up event handlers
|
||||
//
|
||||
term.onData(e => onInput(selected, e));
|
||||
term.onBinary(e => onInput(selected, e));
|
||||
term.onResize((e) => {
|
||||
api.poke(pokeTask(selected, { blew: { w: e.cols, h: e.rows } }));
|
||||
});
|
||||
|
||||
ses = { term, fit };
|
||||
|
||||
// open subscription
|
||||
//
|
||||
api.subscribe({ app: 'herm', path: '/session/'+selected+'/view',
|
||||
event: (e) => {
|
||||
const ses = useTermState.getState().sessions[selected];
|
||||
if (!ses) {
|
||||
console.log('on blit: no such session', selected, sessions, useTermState.getState().sessions);
|
||||
return;
|
||||
}
|
||||
showBlit(ses.term, e);
|
||||
},
|
||||
quit: () => { // quit
|
||||
// TODO show user a message
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (container.current && !container.current.contains(ses.term.element || null)) {
|
||||
ses.term.open(container.current);
|
||||
ses.fit.fit();
|
||||
ses.term.focus();
|
||||
}
|
||||
|
||||
set((state) => {
|
||||
state.sessions[selected] = ses;
|
||||
});
|
||||
|
||||
return () => {
|
||||
// TODO unload term from container
|
||||
// but term.dispose is too powerful? maybe just empty the container?
|
||||
};
|
||||
}, [set, session, container]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={dark ? _dark : _light}>
|
||||
<Reset />
|
||||
<Box
|
||||
width='100%'
|
||||
height='100%'
|
||||
bg='white'
|
||||
fontFamily='mono'
|
||||
overflow='hidden'
|
||||
>
|
||||
<Col
|
||||
width='100%'
|
||||
height='100%'
|
||||
minHeight='0'
|
||||
px={['0','2']}
|
||||
pb={['0','2']}
|
||||
ref={container}
|
||||
>
|
||||
</Col>
|
||||
</Box>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
28
pkg/interface/webterm/constants.ts
Normal file
28
pkg/interface/webterm/constants.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export const DEFAULT_SESSION = '';
|
||||
export const DEFAULT_HANDLER = 'hood';
|
||||
export const RESIZE_DEBOUNCE_MS = 100;
|
||||
export const RESIZE_THRESHOLD_PX = 15;
|
||||
|
||||
/**
|
||||
* Session ID validity:
|
||||
*
|
||||
* - must start with an alphabetical
|
||||
* - can be composed of alphanumerics with hyphens
|
||||
* - can be length 1 or longer
|
||||
*/
|
||||
export const SESSION_ID_REGEX = /(^[a-z]{1}[a-z\d-]*$)/;
|
||||
|
||||
/**
|
||||
* Open a session with a given agent using `[agent]![session_name]`
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* book!my-session
|
||||
* ```
|
||||
*
|
||||
* This will create a new session in webterm for the `%book` agent.
|
||||
*
|
||||
* Note that the second capture group after the ! is composed of the session ID
|
||||
* regex above.
|
||||
*/
|
||||
export const AGENT_SESSION_REGEX = /^([a-z]{1}[a-z\d-]*)!([a-z]{1}[a-z\d-]*$)/;
|
@ -23,10 +23,113 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Source+Code+Pro&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body, #root {
|
||||
height: 100vh;
|
||||
height: 99vh; /* prevent scrollbar on outer frame */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.buffer-container {
|
||||
height: calc(100% - 40px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
div.header {
|
||||
display: grid;
|
||||
grid-template-areas: "tabs info";
|
||||
grid-template-columns: auto min-content;
|
||||
grid-template-rows: auto;
|
||||
}
|
||||
|
||||
div.info {
|
||||
grid-area: info;
|
||||
}
|
||||
|
||||
div.tabs {
|
||||
grid-area: tabs;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-start;
|
||||
padding: 5px 5px 0 5px;
|
||||
border-bottom: 1px solid black;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
div.tabs > * {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
border: solid 1px black;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.tab, button.tab {
|
||||
margin-bottom: -1px; /** To overlay the selected tab on the tabs container bottom border */
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
font-family: monospace;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
div.tabs > div.selected {
|
||||
border-bottom: white solid 1px;
|
||||
}
|
||||
|
||||
div.tabs > div.selected > a.session-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div.tabs > a.delete-session {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
button.info-btn {
|
||||
border: none;
|
||||
border-bottom: solid black 1px;
|
||||
background: transparent;
|
||||
padding: 10px;
|
||||
line-height: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
background-color: rgb(26,26,26);
|
||||
}
|
||||
|
||||
div.tabs {
|
||||
background-color: rgb(26, 26, 26);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
border-bottom-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
div.tab {
|
||||
background-color: rgb(26, 26, 26);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
border-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
button.tab {
|
||||
background-color: rgb(42, 42, 42);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
border-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
div.tabs > div.selected {
|
||||
border-bottom: rgb(26,26,26) solid 1px;
|
||||
}
|
||||
|
||||
button.info-btn {
|
||||
border-bottom: solid rgba(255, 255, 255, 0.9) 1px;
|
||||
background: rgb(26, 26, 26);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
129
pkg/interface/webterm/lib/blit.ts
Normal file
129
pkg/interface/webterm/lib/blit.ts
Normal file
@ -0,0 +1,129 @@
|
||||
import { Terminal } from 'xterm';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { Blit, Stub, Stye } from '@urbit/api/term';
|
||||
import { stye } from '../lib/stye';
|
||||
|
||||
export const csi = (cmd: string, ...args: number[]) => {
|
||||
return '\x1b[' + args.join(';') + cmd;
|
||||
};
|
||||
|
||||
export const showBlit = (term: Terminal, blit: Blit) => {
|
||||
let out = '';
|
||||
|
||||
if ('mor' in blit) {
|
||||
return blit.mor.map(b => showBlit(term, b));
|
||||
} else if ('bel' in blit) {
|
||||
out += '\x07';
|
||||
} else if ('clr' in blit) {
|
||||
term.clear();
|
||||
out += csi('u');
|
||||
} else if ('hop' in blit) {
|
||||
if (typeof blit.hop === 'number') {
|
||||
out += csi('H', term.rows, blit.hop + 1);
|
||||
} else {
|
||||
out += csi('H', blit.hop.y + 1, blit.hop.x + 1);
|
||||
}
|
||||
out += csi('s'); // save cursor position
|
||||
} else if ('put' in blit) {
|
||||
out += blit.put.join('');
|
||||
out += csi('u');
|
||||
} else if ('klr' in blit) {
|
||||
out += blit.klr.reduce((lin: string, p: Stub) => {
|
||||
lin += stye(p.stye);
|
||||
lin += p.text.join('');
|
||||
lin += csi('m', 0);
|
||||
return lin;
|
||||
}, '');
|
||||
out += csi('u');
|
||||
} else if ('nel' in blit) {
|
||||
out += '\n';
|
||||
} else if ('sag' in blit || 'sav' in blit) {
|
||||
const sav = ('sag' in blit) ? blit.sag : blit.sav;
|
||||
const name = sav.path.split('/').slice(-2).join('.');
|
||||
const buff = Buffer.from(sav.file, 'base64');
|
||||
const blob = new Blob([buff], { type: 'application/octet-stream' });
|
||||
saveAs(blob, name);
|
||||
} else if ('url' in blit) {
|
||||
window.open(blit.url);
|
||||
} else if ('wyp' in blit) {
|
||||
out += '\r' + csi('K');
|
||||
out += csi('u');
|
||||
//
|
||||
} else {
|
||||
console.log('weird blit', blit);
|
||||
}
|
||||
|
||||
term.write(out);
|
||||
};
|
||||
|
||||
export const showSlog = (term: Terminal, slog: string) => {
|
||||
// set scroll region to exclude the bottom line,
|
||||
// scroll up one line,
|
||||
// move cursor to start of the newly created whitespace,
|
||||
// set text to grey,
|
||||
// print the slog,
|
||||
// restore color, scroll region, and cursor.
|
||||
//
|
||||
term.write(csi('r', 1, term.rows - 1)
|
||||
+ csi('S', 1)
|
||||
+ csi('H', term.rows - 1, 1)
|
||||
+ csi('m', 90)
|
||||
+ slog
|
||||
+ csi('m', 0)
|
||||
+ csi('r')
|
||||
+ csi('u'));
|
||||
};
|
||||
|
||||
export const hasBell = (blit: Blit) => {
|
||||
if ('bel' in blit) {
|
||||
return true;
|
||||
} else if ('mor' in blit) {
|
||||
return blit.mor.some(hasBell);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// debug rendering
|
||||
//NOTE doesn't behave nicely in the presence of eob %nel blits,
|
||||
// because those aren't idempotent
|
||||
|
||||
const blotStye: Stye = { deco: [], back: { r: 255, g: 0, b: 255 }, fore: 'k' };
|
||||
const blitToBlot = (blit: Blit): Blit => {
|
||||
if ('mor' in blit) {
|
||||
return { mor: blit.mor.map(blitToBlot) };
|
||||
} else if ('put' in blit) {
|
||||
return { klr: [{ text: blit.put, stye: blotStye }] };
|
||||
} else if ('klr' in blit) {
|
||||
return { klr: blit.klr.map((s: Stub) => {
|
||||
return { text: s.text, stye: blotStye };
|
||||
}) };
|
||||
} else {
|
||||
return blit;
|
||||
}
|
||||
};
|
||||
|
||||
const queue: {term: Terminal, blit: Blit}[] = [];
|
||||
const renderFromQueue = () => {
|
||||
const next = queue.shift();
|
||||
if (!next) {
|
||||
return;
|
||||
}
|
||||
showBlit(next.term, next.blit);
|
||||
if (0 === queue.length) {
|
||||
return;
|
||||
}
|
||||
setTimeout(renderFromQueue, 200);
|
||||
};
|
||||
|
||||
export const showBlitDebug = (term: Terminal, blit: Blit) => {
|
||||
const blot = blitToBlot(blit);
|
||||
if (0 === queue.length) {
|
||||
showBlit(term, blot);
|
||||
queue.push({ term, blit });
|
||||
setTimeout(renderFromQueue, 200);
|
||||
} else {
|
||||
queue.push({ term, blit: blot });
|
||||
queue.push({ term, blit });
|
||||
}
|
||||
};
|
39
pkg/interface/webterm/lib/retry.ts
Normal file
39
pkg/interface/webterm/lib/retry.ts
Normal file
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Wait for the given milliseconds
|
||||
* @param {number} milliseconds The given time to wait
|
||||
* @returns {Promise} A fulfiled promise after the given time has passed
|
||||
*/
|
||||
function waitFor(milliseconds) {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a promise and retry with exponential backoff
|
||||
* based on the maximum retry attempts it can perform
|
||||
* @param {Promise} promise promise to be executed
|
||||
* @param {function} onRetry callback executed on every retry
|
||||
* @param {number} maxRetries The maximum number of retries to be attempted
|
||||
* @returns {Promise} The result of the given promise passed in
|
||||
*/
|
||||
export function retry(promise, onRetry, maxRetries) {
|
||||
async function retryWithBackoff(retries) {
|
||||
try {
|
||||
if (retries > 0) {
|
||||
const timeToWait = 2 ** retries * 100;
|
||||
console.log(`waiting for ${timeToWait}ms...`);
|
||||
await waitFor(timeToWait);
|
||||
}
|
||||
return await promise();
|
||||
} catch (e) {
|
||||
if (retries < maxRetries) {
|
||||
onRetry();
|
||||
return retryWithBackoff(retries + 1);
|
||||
} else {
|
||||
console.warn('Max retries reached. Bubbling the error up');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retryWithBackoff(0);
|
||||
}
|
60
pkg/interface/webterm/lib/stye.ts
Normal file
60
pkg/interface/webterm/lib/stye.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { Deco, Stye, Tint } from '@urbit/api/term';
|
||||
|
||||
const tint = (t: Tint) => {
|
||||
switch (t) {
|
||||
case null: return '9';
|
||||
case 'k': return '0';
|
||||
case 'r': return '1';
|
||||
case 'g': return '2';
|
||||
case 'y': return '3';
|
||||
case 'b': return '4';
|
||||
case 'm': return '5';
|
||||
case 'c': return '6';
|
||||
case 'w': return '7';
|
||||
default: return `8;2;${t.r%256};${t.g%256};${t.b%256}`;
|
||||
}
|
||||
};
|
||||
|
||||
export const stye = (s: Stye) => {
|
||||
let out = '';
|
||||
|
||||
// text decorations
|
||||
//
|
||||
if (s.deco.length > 0) {
|
||||
out += s.deco.reduce((decs: number[], deco: Deco) => {
|
||||
/* eslint-disable max-statements-per-line */
|
||||
switch (deco) {
|
||||
case null: decs.push(0); return decs;
|
||||
case 'br': decs.push(1); return decs;
|
||||
case 'un': decs.push(4); return decs;
|
||||
case 'bl': decs.push(5); return decs;
|
||||
default: console.log('weird deco', deco); return decs;
|
||||
}
|
||||
}, []).join(';');
|
||||
}
|
||||
|
||||
// background color
|
||||
//
|
||||
if (s.back !== null) {
|
||||
if (out !== '') {
|
||||
out += ';';
|
||||
}
|
||||
out += '4';
|
||||
out += tint(s.back);
|
||||
}
|
||||
|
||||
// foreground color
|
||||
//
|
||||
if (s.fore !== null) {
|
||||
if (out !== '') {
|
||||
out += ';';
|
||||
}
|
||||
out += '3';
|
||||
out += tint(s.fore);
|
||||
}
|
||||
|
||||
if (out === '') {
|
||||
return out;
|
||||
}
|
||||
return '\x1b[' + out + 'm';
|
||||
};
|
23
pkg/interface/webterm/lib/theme.ts
Normal file
23
pkg/interface/webterm/lib/theme.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ITheme } from 'xterm';
|
||||
|
||||
export const makeTheme = (dark: boolean): ITheme => {
|
||||
let fg, bg: string;
|
||||
if (dark) {
|
||||
fg = 'white';
|
||||
bg = 'rgb(26,26,26)';
|
||||
} else {
|
||||
fg = 'black';
|
||||
bg = 'white';
|
||||
}
|
||||
// TODO indigo colors.
|
||||
// we can't pluck these from ThemeContext because they have transparency.
|
||||
// technically xterm supports transparency, but it degrades performance.
|
||||
return {
|
||||
foreground: fg,
|
||||
background: bg,
|
||||
brightBlack: '#7f7f7f', // NOTE slogs
|
||||
cursor: fg,
|
||||
cursorAccent: bg,
|
||||
selection: fg
|
||||
};
|
||||
};
|
66
pkg/interface/webterm/lib/useAddSession.ts
Normal file
66
pkg/interface/webterm/lib/useAddSession.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
DEFAULT_HANDLER,
|
||||
AGENT_SESSION_REGEX,
|
||||
SESSION_ID_REGEX
|
||||
} from '../constants';
|
||||
import useTermState from '../state';
|
||||
import api from '../api';
|
||||
import { pokeTask } from '@urbit/api/term';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useAddSession = () => {
|
||||
const { names } = useTermState();
|
||||
|
||||
const addSession = useCallback(async () => {
|
||||
let agent = DEFAULT_HANDLER;
|
||||
let sessionName: string;
|
||||
|
||||
const userInput = prompt('Please enter an alpha-numeric session name.');
|
||||
// user canceled or did not enter a value
|
||||
if (null === userInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for custom agent session syntax
|
||||
if (AGENT_SESSION_REGEX.test(userInput)) {
|
||||
const match = AGENT_SESSION_REGEX.exec(userInput);
|
||||
if (!match) {
|
||||
alert('Invalid format. Valid syntax: agent!session-name');
|
||||
return;
|
||||
}
|
||||
agent = match[1];
|
||||
sessionName = match[2];
|
||||
// else, use the default session creation regex
|
||||
} else if (SESSION_ID_REGEX.test(userInput)) {
|
||||
const match = SESSION_ID_REGEX.exec(userInput);
|
||||
if (!match) {
|
||||
alert('Invalid format. Valid syntax: session-name');
|
||||
return;
|
||||
}
|
||||
sessionName = match[1];
|
||||
} else {
|
||||
alert('Invalid format. Valid syntax: session-name');
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent duplicate sessions
|
||||
if(names.includes(sessionName)) {
|
||||
alert(`Session name must be unique ("${sessionName}" already in use)`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//TODO eventually, customizable app pre-linking?
|
||||
await api.poke(pokeTask(sessionName, { open: { term: agent, apps: [{ who: '~' + (window as any).ship, app: 'dojo' }] } }));
|
||||
useTermState.getState().set((state) => {
|
||||
state.names = [sessionName, ...state.names].sort();
|
||||
state.selected = sessionName;
|
||||
state.sessions[sessionName] = null;
|
||||
});
|
||||
} catch (error) {
|
||||
console.log('unable to create session:', error);
|
||||
}
|
||||
}, [names]);
|
||||
|
||||
return addSession;
|
||||
};
|
@ -1,6 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTheme } from './settings';
|
||||
import useTermState from './state';
|
||||
import useTermState from '../state';
|
||||
|
||||
export function useDark() {
|
||||
const [osDark, setOsDark] = useState(false);
|
||||
@ -11,12 +10,11 @@ export function useDark() {
|
||||
setOsDark(e.matches);
|
||||
};
|
||||
setOsDark(themeWatcher.matches);
|
||||
themeWatcher.addListener(update);
|
||||
themeWatcher.addEventListener('change', update);
|
||||
|
||||
return () => {
|
||||
themeWatcher.removeListener(update);
|
||||
}
|
||||
|
||||
themeWatcher.removeEventListener('change', update);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const theme = useTermState(s => s.theme);
|
44
pkg/interface/webterm/lib/useDetectOS.ts
Normal file
44
pkg/interface/webterm/lib/useDetectOS.ts
Normal file
@ -0,0 +1,44 @@
|
||||
/* eslint-disable no-useless-escape */
|
||||
// Regex patterns inspired by:
|
||||
// https://github.com/faisalman/ua-parser-js/blob/master/src/ua-parser.js
|
||||
const LINUX = [
|
||||
/\b(joli|palm)\b ?(?:os)?\/?([\w\.]*)/i,
|
||||
/(mint)[\/\(\) ]?(\w*)/i,
|
||||
/(mageia|vectorlinux)[; ]/i,
|
||||
/([kxln]?ubuntu|debian|suse|opensuse|gentoo|arch(?= linux)|slackware|fedora|mandriva|centos|pclinuxos|red ?hat|zenwalk|linpus|raspbian|plan 9|minix|risc os|contiki|deepin|manjaro|elementary os|sabayon|linspire)(?: gnu\/linux)?(?: enterprise)?(?:[- ]linux)?(?:-gnu)?[-\/ ]?(?!chrom|package)([-\w\.]*)/i,
|
||||
/(hurd|linux) ?([\w\.]*)/i,
|
||||
/(gnu) ?([\w\.]*)/i,
|
||||
/\b([-frentopcghs]{0,5}bsd|dragonfly)[\/ ]?(?!amd|[ix346]{1,2}86)([\w\.]*)/i,
|
||||
/(haiku) (\w+)/i,
|
||||
/(sunos) ?([\w\.\d]*)/i,
|
||||
/((?:open)?solaris)[-\/ ]?([\w\.]*)/i,
|
||||
/(aix) ((\d)(?=\.|\)| )[\w\.])*/i,
|
||||
/\b(beos|os\/2|amigaos|morphos|openvms|fuchsia|hp-ux)/i,
|
||||
/(unix) ?([\w\.]*)/i
|
||||
];
|
||||
|
||||
const MAC_OS = [
|
||||
/(mac os x) ?([\w\. ]*)/i,
|
||||
/(macintosh|mac_powerpc\b)(?!.+haiku)/i
|
||||
];
|
||||
|
||||
const WINDOWS = [
|
||||
/microsoft (windows) (vista|xp)/i,
|
||||
/(windows) nt 6\.2; (arm)/i,
|
||||
/(windows (?:phone(?: os)?|mobile))[\/ ]?([\d\.\w ]*)/i,
|
||||
/(windows)[\/ ]?([ntce\d\. ]+\w)(?!.+xbox)/i
|
||||
];
|
||||
|
||||
export const useDetectOS = () => {
|
||||
const userAgent = navigator.userAgent;
|
||||
|
||||
const isLinux = LINUX.some(regex => regex.test(userAgent));
|
||||
const isMacOS = MAC_OS.some(regex => regex.test(userAgent));
|
||||
const isWindows = WINDOWS.some(regex => regex.test(userAgent));
|
||||
|
||||
return {
|
||||
isLinux,
|
||||
isMacOS,
|
||||
isWindows
|
||||
};
|
||||
};
|
17
pkg/interface/webterm/lib/useIsMounted.ts
Normal file
17
pkg/interface/webterm/lib/useIsMounted.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
function useIsMounted() {
|
||||
const isMounted = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return useCallback(() => isMounted.current, []);
|
||||
}
|
||||
|
||||
export default useIsMounted;
|
716
pkg/interface/webterm/package-lock.json
generated
716
pkg/interface/webterm/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@
|
||||
"@urbit/api": "^1.1.1",
|
||||
"@urbit/http-api": "^1.2.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^16.14.0",
|
||||
"react-dom": "^16.14.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
@ -36,7 +37,7 @@
|
||||
"@types/styled-system": "^5.1.10",
|
||||
"@typescript-eslint/eslint-plugin": "^4.15.0",
|
||||
"@typescript-eslint/parser": "^4.24.0",
|
||||
"@urbit/eslint-config": "^1.0.0",
|
||||
"@urbit/eslint-config": "^1.0.3",
|
||||
"@welldone-software/why-did-you-render": "^6.1.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
@ -45,7 +46,9 @@
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^4.5.1",
|
||||
"husky": "^6.0.0",
|
||||
@ -58,7 +61,8 @@
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint ./src/**/*.{ts,tsx}",
|
||||
"lint": "eslint ./**/*.{ts,tsx}",
|
||||
"lint-fix": "eslint --fix ./**/*.{ts,tsx}",
|
||||
"lint-file": "eslint",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
|
@ -3,21 +3,33 @@ import { FitAddon } from 'xterm-addon-fit';
|
||||
import create from 'zustand';
|
||||
import produce from 'immer';
|
||||
|
||||
type Session = { term: Terminal, fit: FitAddon };
|
||||
type Sessions = { [id: string]: Session; }
|
||||
export type Session = {
|
||||
term: Terminal,
|
||||
fit: FitAddon,
|
||||
hasBell: boolean,
|
||||
pending: number,
|
||||
subscriptionId: number | null,
|
||||
} | null;
|
||||
export type Sessions = { [id: string]: Session; }
|
||||
|
||||
export interface TermState {
|
||||
sessions: Sessions,
|
||||
names: string[],
|
||||
selected: string,
|
||||
slogstream: null | EventSource,
|
||||
theme: 'auto' | 'light' | 'dark'
|
||||
};
|
||||
theme: 'auto' | 'light' | 'dark',
|
||||
//TODO: figure out the type
|
||||
set: any,
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const useTermState = create<TermState>((set, get) => ({
|
||||
sessions: {} as Sessions,
|
||||
names: [''],
|
||||
selected: '', // empty string is default session
|
||||
slogstream: null,
|
||||
theme: 'auto',
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
set: (f: (draft: TermState) => void) => {
|
||||
set(produce(f));
|
||||
}
|
||||
|
26
pkg/interface/webterm/tsconfig.json
Normal file
26
pkg/interface/webterm/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUnusedParameters": false,
|
||||
"noImplicitReturns": false,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"noUnusedLocals": false,
|
||||
"noImplicitAny": false,
|
||||
"noEmit": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"strict": false,
|
||||
"strictNullChecks": true,
|
||||
"jsx": "react",
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": [
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
@ -16,15 +16,15 @@
|
||||
+$ card card:shoe
|
||||
::
|
||||
+$ versioned-state
|
||||
$% state-3
|
||||
$% state-4
|
||||
state-3
|
||||
state-2
|
||||
state-1
|
||||
state-0
|
||||
==
|
||||
::
|
||||
+$ state-3
|
||||
$: %3
|
||||
::TODO support multiple sessions
|
||||
+$ state-4
|
||||
$: %4
|
||||
sessions=(map sole-id session) :: sole sessions
|
||||
bound=(map resource glyph) :: bound resource glyphs
|
||||
binds=(jug glyph resource) :: resource glyph lookup
|
||||
@ -33,7 +33,17 @@
|
||||
timez=(pair ? @ud) :: timezone adjustment
|
||||
==
|
||||
::
|
||||
+$ sole-id @ta
|
||||
+$ state-3
|
||||
$: %3
|
||||
sessions=(map @ta session)
|
||||
bound=(map resource glyph)
|
||||
binds=(jug glyph resource)
|
||||
settings=(set term)
|
||||
width=@ud
|
||||
timez=(pair ? @ud)
|
||||
==
|
||||
::
|
||||
+$ sole-id sole-id:shoe
|
||||
+$ session
|
||||
$: viewing=(set resource) :: connected graphs
|
||||
history=(list uid:post) :: scrollback pointers
|
||||
@ -115,7 +125,7 @@
|
||||
== ::
|
||||
::
|
||||
--
|
||||
=| state-3
|
||||
=| state-4
|
||||
=* state -
|
||||
::
|
||||
%- agent:dbug
|
||||
@ -258,14 +268,14 @@
|
||||
settings width timez
|
||||
==
|
||||
::
|
||||
=^ cards u.old
|
||||
=^ cards-1 u.old
|
||||
?. ?=(%2 -.u.old) [~ u.old]
|
||||
:- :~ [%pass /chat-store %agent [our-self %chat-store] %leave ~]
|
||||
[%pass /invites %agent [our.bowl %invite-store] %leave ~]
|
||||
==
|
||||
^- state-3
|
||||
:- %3
|
||||
:* %+ ~(put in *(map sole-id session))
|
||||
:* %+ ~(put in *(map @ta session))
|
||||
(cat 3 'drum_' (scot %p our.bowl))
|
||||
:* ~ ~ 0
|
||||
::
|
||||
@ -290,14 +300,29 @@
|
||||
timez.u.old
|
||||
==
|
||||
::
|
||||
?> ?=(%3 -.u.old)
|
||||
=^ cards-2 u.old
|
||||
?. ?=(%3 -.u.old) [~ u.old]
|
||||
:- %+ turn ~(tap in ~(key by sessions.u.old))
|
||||
|= id=@ta
|
||||
^- card:agent:gall
|
||||
[%give %kick ~[/sole/[id]] ~]
|
||||
=- u.old(- %4, sessions -)
|
||||
%- ~(gas by *(map sole-id session))
|
||||
%+ murn ~(tap by sessions.u.old)
|
||||
|= [id=@ta s=session]
|
||||
(bind (upgrade-id:sole:shoe id) (late s))
|
||||
::
|
||||
?> ?=(%4 -.u.old)
|
||||
:_ u.old
|
||||
%+ welp
|
||||
cards
|
||||
?: %- ~(has by wex.bowl)
|
||||
[/graph-store our-self %graph-store]
|
||||
~
|
||||
~[connect]
|
||||
;: welp
|
||||
cards-1
|
||||
cards-2
|
||||
::
|
||||
?: %- ~(has by wex.bowl)
|
||||
[/graph-store our-self %graph-store]
|
||||
~
|
||||
~[connect]
|
||||
==
|
||||
:: +connect: connect to the graph-store
|
||||
::
|
||||
++ connect
|
||||
|
@ -1 +1 @@
|
||||
[%zuse 417]
|
||||
[%zuse 416]
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Poke } from '../lib'
|
||||
import { Scry } from '../../http-api/src'
|
||||
import { Poke } from '../../http-api/src/types';
|
||||
import { Belt, Task, SessionTask } from './types';
|
||||
|
||||
export const pokeTask = (session: string, task: Task): Poke<SessionTask> => ({
|
||||
|
@ -25,10 +25,11 @@ export type Stub = {
|
||||
export type Blit =
|
||||
| { bel: null } // make a noise
|
||||
| { clr: null } // clear the screen
|
||||
| { hop: number | { r: number, c: number } } // set cursor col/pos
|
||||
| { hop: number | { x: number, y: number } } // set cursor col/pos
|
||||
| { klr: Stub[] } // put styled
|
||||
| { put: string[] } // put text at cursor
|
||||
| { mor: Blit[] } // multiple blits
|
||||
| { nel: null } // newline
|
||||
| { put: string[] } // put text at cursor
|
||||
| { sag: { path: string, file: string } } // save to jamfile
|
||||
| { sav: { path: string, file: string } } // save to file
|
||||
| { url: string } // activate url
|
||||
@ -42,7 +43,7 @@ export type Bolt =
|
||||
| { aro: 'd' | 'l' | 'r' | 'u' }
|
||||
| { bac: null }
|
||||
| { del: null }
|
||||
| { hit: { r: number, c: number } }
|
||||
| { hit: { x: number, y: number } }
|
||||
| { ret: null }
|
||||
|
||||
export type Belt =
|
||||
@ -53,8 +54,8 @@ export type Belt =
|
||||
export type Task =
|
||||
| { belt: Belt }
|
||||
| { blew: { w: number, h: number } }
|
||||
| { flow: { term: string, apps: Array<{ who: string, app: string }> } }
|
||||
| { hail: null }
|
||||
| { hook: null }
|
||||
| { open: { term: string, apps: Array<{ who: string, app: string }> } }
|
||||
| { shut: null }
|
||||
|
||||
export type SessionTask = { session: string } & Task
|
||||
|
@ -572,7 +572,7 @@ _setup_cert_store()
|
||||
{
|
||||
BIO* cbio = BIO_new_mem_buf(include_ca_bundle_crt, include_ca_bundle_crt_len);
|
||||
if ( !cbio || !(_cert_store = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL)) ) {
|
||||
u3l_log("boot: failed to decode embedded CA certificates\r\n");
|
||||
u3l_log("boot: failed to decode embedded CA certificates");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -1267,7 +1267,7 @@ _cw_eval(c3_i argc, c3_c* argv[])
|
||||
}
|
||||
u3s_cue_xeno_done(sil_u);
|
||||
if ( c3n == u3v_boot_lite(pil) ) {
|
||||
u3l_log("lite: boot failed\r\n");
|
||||
u3l_log("lite: boot failed");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@ -1858,7 +1858,7 @@ _cw_vere(c3_i argc, c3_c* argv[])
|
||||
// initialize curl
|
||||
//
|
||||
if ( 0 != curl_global_init(CURL_GLOBAL_DEFAULT) ) {
|
||||
u3l_log("boot: curl initialization failed\r\n");
|
||||
u3l_log("boot: curl initialization failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -1888,11 +1888,11 @@ _cw_vere(c3_i argc, c3_c* argv[])
|
||||
|
||||
|
||||
if ( u3_king_vere(pac_c, ver_c, arc_c, dir_c, 0) ) {
|
||||
u3l_log("vere: download failed\r\n");
|
||||
u3l_log("vere: download failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
u3l_log("vere: download succeeded\r\n");
|
||||
u3l_log("vere: download succeeded");
|
||||
}
|
||||
|
||||
/* _cw_vile(): generatoe/print keyfile
|
||||
@ -2230,7 +2230,7 @@ main(c3_i argc,
|
||||
{
|
||||
SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE};
|
||||
if ( NULL == (u3_Host.cev_u = CreateEvent(&sa, FALSE, FALSE, NULL)) ) {
|
||||
u3l_log("boot: failed to create Ctrl-C event: %d\r\n", GetLastError());
|
||||
u3l_log("boot: failed to create Ctrl-C event: %d", GetLastError());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
@ -156,12 +156,14 @@
|
||||
c3_o mou; // M (for mouse event) received
|
||||
c3_y ton_y; // mouse button
|
||||
c3_y col_y; // column coordinate
|
||||
c3_y seq_y; // vt sequence
|
||||
} esc;
|
||||
|
||||
struct {
|
||||
c3_y syb_y[5]; // utf8 code buffer
|
||||
c3_w len_w; // present length
|
||||
c3_w wid_w; // total width
|
||||
struct { // input buffering
|
||||
c3_y syb_y[5]; // utf8 code buffer
|
||||
c3_w len_w; // present length
|
||||
c3_w wid_w; // total width
|
||||
u3_noun imp; // %txt input buffer
|
||||
} fut;
|
||||
|
||||
struct {
|
||||
|
@ -1174,13 +1174,8 @@ u3e_live(c3_o nuu_o, c3_c* dir_c)
|
||||
|
||||
/* If the images were empty, we are logically booting.
|
||||
*/
|
||||
<<<<<<< HEAD
|
||||
if ( !nor_w && !sou_w ) {
|
||||
u3l_log("live: logical boot");
|
||||
=======
|
||||
if ( (0 == u3P.nor_u.pgs_w) && (0 == u3P.sou_u.pgs_w) ) {
|
||||
u3l_log("live: logical boot\r\n");
|
||||
>>>>>>> next/arvo
|
||||
u3l_log("live: logical boot");
|
||||
nuu_o = c3y;
|
||||
}
|
||||
else {
|
||||
|
@ -33,7 +33,7 @@ u3_weak
|
||||
u3l_punt(const char* name, u3_weak pro)
|
||||
{
|
||||
if ( u3_none == pro ) {
|
||||
u3l_log("%s-punt\r\n", name);
|
||||
u3l_log("%s-punt", name);
|
||||
}
|
||||
return pro;
|
||||
}
|
||||
|
@ -1772,7 +1772,7 @@ _cm_crypto()
|
||||
if ( 0 == CRYPTO_set_mem_functions(&_cm_malloc_ssl,
|
||||
&_cm_realloc_ssl,
|
||||
&_cm_free_ssl) ) {
|
||||
u3l_log("%s\r\n", "openssl initialization failed");
|
||||
u3l_log("%s", "openssl initialization failed");
|
||||
abort();
|
||||
}
|
||||
|
||||
@ -1815,7 +1815,7 @@ u3m_init(size_t len_i)
|
||||
|| (len_i < (1 << (u3a_page + 2)))
|
||||
|| (len_i > u3a_bytes) )
|
||||
{
|
||||
u3l_log("loom: bad size: %zu\r\n", len_i);
|
||||
u3l_log("loom: bad size: %zu", len_i);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,7 @@ u3v_lily(u3_noun fot, u3_noun txt, c3_l* tid_l)
|
||||
(c3n == u3r_safe_word(q_uco, &wad_w)) ||
|
||||
(wad_w & 0x80000000) )
|
||||
{
|
||||
u3l_log("strange lily %s\n", u3r_string(txt));
|
||||
u3l_log("strange lily %s", u3r_string(txt));
|
||||
u3z(txt); u3z(uco); return c3n;
|
||||
}
|
||||
else {
|
||||
|
@ -610,7 +610,7 @@ u3_disk_acquire(c3_c* pax_c)
|
||||
|
||||
if ( NULL != (loq_u = c3_fopen(paf_c, "r")) ) {
|
||||
if ( 1 != fscanf(loq_u, "%" SCNu32, &pid_w) ) {
|
||||
u3l_log("lockfile %s is corrupt!\n", paf_c);
|
||||
u3l_log("lockfile %s is corrupt!", paf_c);
|
||||
kill(getpid(), SIGTERM);
|
||||
sleep(1); c3_assert(0);
|
||||
}
|
||||
@ -620,13 +620,13 @@ u3_disk_acquire(c3_c* pax_c)
|
||||
int ret = kill(pid_w, SIGTERM);
|
||||
|
||||
if ( -1 == ret && errno == EPERM ) {
|
||||
u3l_log("disk: permission denied when trying to kill process %d!\n", pid_w);
|
||||
u3l_log("disk: permission denied when trying to kill process %d!", pid_w);
|
||||
kill(getpid(), SIGTERM);
|
||||
sleep(1); c3_assert(0);
|
||||
}
|
||||
|
||||
if ( -1 != ret ) {
|
||||
u3l_log("disk: stopping process %d, live in %s...\n",
|
||||
u3l_log("disk: stopping process %d, live in %s...",
|
||||
pid_w, pax_c);
|
||||
|
||||
for ( i_w = 0; i_w < 16; i_w++ ) {
|
||||
@ -644,10 +644,10 @@ u3_disk_acquire(c3_c* pax_c)
|
||||
}
|
||||
}
|
||||
if ( 16 == i_w ) {
|
||||
u3l_log("disk: process %d seems unkillable!\n", pid_w);
|
||||
u3l_log("disk: process %d seems unkillable!", pid_w);
|
||||
c3_assert(0);
|
||||
}
|
||||
u3l_log("disk: stopped old process %u\n", pid_w);
|
||||
u3l_log("disk: stopped old process %u", pid_w);
|
||||
}
|
||||
}
|
||||
fclose(loq_u);
|
||||
@ -655,7 +655,7 @@ u3_disk_acquire(c3_c* pax_c)
|
||||
}
|
||||
|
||||
if ( NULL == (loq_u = c3_fopen(paf_c, "w")) ) {
|
||||
u3l_log("disk: unable to open %s\n", paf_c);
|
||||
u3l_log("disk: unable to open %s", paf_c);
|
||||
c3_assert(0);
|
||||
}
|
||||
|
||||
@ -703,7 +703,7 @@ u3_disk_exit(u3_disk* log_u)
|
||||
if ( (c3y == log_u->ted_o)
|
||||
&& (0 > uv_cancel(&log_u->req_u)) )
|
||||
{
|
||||
// u3l_log("disk: unable to cleanup\r\n");
|
||||
// u3l_log("disk: unable to cleanup");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -726,7 +726,7 @@ _ames_ef_send(u3_ames* sam_u, u3_noun lan, u3_noun pac)
|
||||
//
|
||||
else if ( 0 == lan_u.por_s ) {
|
||||
if ( u3C.wag_w & u3o_verbose ) {
|
||||
u3l_log("ames: inscrutable lane\n");
|
||||
u3l_log("ames: inscrutable lane");
|
||||
}
|
||||
_ames_pact_free(pac_u);
|
||||
}
|
||||
|
@ -333,7 +333,7 @@ _conn_moor_bail(void* ptr_v, ssize_t err_i, const c3_c* err_c)
|
||||
u3_shan* san_u = can_u->san_u;
|
||||
|
||||
if ( err_i != UV_EOF ) {
|
||||
u3l_log("conn: moor bail %zd %s\n", err_i, err_c);
|
||||
u3l_log("conn: moor bail %zd %s", err_i, err_c);
|
||||
if ( _(can_u->liv_o) ) {
|
||||
_conn_send_noun(can_u, u3nq(0, c3__bail, u3i_word(err_i),
|
||||
u3i_string(err_c)));
|
||||
@ -573,7 +573,7 @@ _conn_moor_poke(void* ptr_v, c3_d len_d, c3_y* byt_y)
|
||||
rud = u3dc("scot", c3__uv, u3k(rid));
|
||||
tag_c = u3r_string(tag);
|
||||
rid_c = u3r_string(rud);
|
||||
u3l_log("conn: %s %s\n", tag_c, rid_c);
|
||||
u3l_log("conn: %s %s", tag_c, rid_c);
|
||||
c3_free(tag_c); c3_free(rid_c);
|
||||
|
||||
switch (tag) {
|
||||
@ -690,7 +690,7 @@ _conn_init_sock(u3_shan* san_u)
|
||||
c3_assert(!ret_i);
|
||||
ret_i = uv_listen((uv_stream_t*)&san_u->pyp_u, 0, _conn_sock_cb);
|
||||
c3_assert(!ret_i);
|
||||
u3l_log("conn: listening on %s\n", pip_c);
|
||||
u3l_log("conn: listening on %s", pip_c);
|
||||
|
||||
#else // _WIN32
|
||||
// the full socket path is limited to about 108 characters,
|
||||
@ -702,46 +702,46 @@ _conn_init_sock(u3_shan* san_u)
|
||||
c3_i err_i;
|
||||
|
||||
if ( NULL == getcwd(pax_c, sizeof(pax_c)) ) {
|
||||
u3l_log("conn: getcwd: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: getcwd: %s", uv_strerror(errno));
|
||||
u3_king_bail();
|
||||
}
|
||||
if ( 0 != chdir(u3_Host.dir_c) ) {
|
||||
u3l_log("conn: chdir: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: chdir: %s", uv_strerror(errno));
|
||||
u3_king_bail();
|
||||
}
|
||||
if ( 0 != unlink(URB_SOCK_PATH) && errno != ENOENT ) {
|
||||
u3l_log("conn: unlink: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: unlink: %s", uv_strerror(errno));
|
||||
goto _conn_sock_err_chdir;
|
||||
}
|
||||
if ( 0 != (err_i = uv_pipe_init(u3L, &san_u->pyp_u, 0)) ) {
|
||||
u3l_log("conn: uv_pipe_init: %s\n", uv_strerror(err_i));
|
||||
u3l_log("conn: uv_pipe_init: %s", uv_strerror(err_i));
|
||||
goto _conn_sock_err_chdir;
|
||||
}
|
||||
if ( 0 != (err_i = uv_pipe_bind(&san_u->pyp_u, URB_SOCK_PATH)) ) {
|
||||
u3l_log("conn: uv_pipe_bind: %s\n", uv_strerror(err_i));
|
||||
u3l_log("conn: uv_pipe_bind: %s", uv_strerror(err_i));
|
||||
goto _conn_sock_err_chdir;
|
||||
}
|
||||
if ( 0 != (err_i = uv_listen((uv_stream_t*)&san_u->pyp_u, 0,
|
||||
_conn_sock_cb)) ) {
|
||||
u3l_log("conn: uv_listen: %s\n", uv_strerror(err_i));
|
||||
u3l_log("conn: uv_listen: %s", uv_strerror(err_i));
|
||||
goto _conn_sock_err_unlink;
|
||||
}
|
||||
if ( 0 != chdir(pax_c) ) {
|
||||
u3l_log("conn: chdir: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: chdir: %s", uv_strerror(errno));
|
||||
goto _conn_sock_err_close;
|
||||
}
|
||||
u3l_log("conn: listening on %s/%s\n", u3_Host.dir_c, URB_SOCK_PATH);
|
||||
u3l_log("conn: listening on %s/%s", u3_Host.dir_c, URB_SOCK_PATH);
|
||||
return;
|
||||
|
||||
_conn_sock_err_close:
|
||||
uv_close((uv_handle_t*)&san_u->pyp_u, _conn_close_cb);
|
||||
_conn_sock_err_unlink:
|
||||
if ( 0 != unlink(URB_SOCK_PATH) ) {
|
||||
u3l_log("conn: unlink: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: unlink: %s", uv_strerror(errno));
|
||||
}
|
||||
_conn_sock_err_chdir:
|
||||
if ( 0 != chdir(pax_c) ) {
|
||||
u3l_log("conn: chdir: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: chdir: %s", uv_strerror(errno));
|
||||
}
|
||||
u3_king_bail();
|
||||
#endif // _WIN32
|
||||
@ -764,7 +764,7 @@ _conn_born_news(u3_ovum* egg_u, u3_ovum_news new_e)
|
||||
static void
|
||||
_conn_born_bail(u3_ovum* egg_u, u3_noun lud)
|
||||
{
|
||||
u3l_log("conn: %%born failure; %%fyrd not supported\n");
|
||||
u3l_log("conn: %%born failure; %%fyrd not supported");
|
||||
u3z(lud);
|
||||
u3_ovum_free(egg_u);
|
||||
}
|
||||
@ -820,7 +820,7 @@ _conn_ef_handle(u3_conn* con_u,
|
||||
}
|
||||
}
|
||||
else {
|
||||
u3l_log("conn: handle-no-coq %" PRIx32 " %" PRIu32 "\n",
|
||||
u3l_log("conn: handle-no-coq %" PRIx32 " %" PRIu32,
|
||||
sev_l, coq_l);
|
||||
}
|
||||
u3z(rid); u3z(tag); u3z(dat);
|
||||
@ -866,11 +866,11 @@ _conn_io_exit(u3_auto* car_u)
|
||||
|
||||
if ( 0 != unlink(paf_c) ) {
|
||||
if ( ENOENT != errno ) {
|
||||
u3l_log("conn: failed to unlink socket: %s\n", uv_strerror(errno));
|
||||
u3l_log("conn: failed to unlink socket: %s", uv_strerror(errno));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// u3l_log("conn: unlinked %s\n", paf_c);
|
||||
// u3l_log("conn: unlinked %s", paf_c);
|
||||
}
|
||||
c3_free(paf_c);
|
||||
|
||||
|
@ -147,6 +147,7 @@ u3_term_log_init(void)
|
||||
|
||||
uty_u->tat_u.fut.len_w = 0;
|
||||
uty_u->tat_u.fut.wid_w = 0;
|
||||
uty_u->tat_u.fut.imp = u3_nul;
|
||||
}
|
||||
|
||||
// default size
|
||||
@ -384,7 +385,7 @@ _term_it_clear_line(u3_utty* uty_u)
|
||||
|
||||
// if we're clearing the bottom line, clear our mirror of it too
|
||||
//
|
||||
if ( 0 == uty_u->tat_u.mir.rus_w ) {
|
||||
if ( uty_u->tat_u.siz.row_l - 1 == uty_u->tat_u.mir.rus_w ) {
|
||||
_term_it_free_line(uty_u);
|
||||
}
|
||||
}
|
||||
@ -400,19 +401,19 @@ _term_it_show_blank(u3_utty* uty_u)
|
||||
|
||||
/* _term_it_move_cursor(): move cursor to row & column
|
||||
*
|
||||
* row 0 is at the bottom, col 0 is to the left.
|
||||
* row 0 is at the top, col 0 is to the left.
|
||||
* if the given position exceeds the known window size,
|
||||
* it is clipped to stay within the window.
|
||||
*/
|
||||
static void
|
||||
_term_it_move_cursor(u3_utty* uty_u, c3_w row_w, c3_w col_w)
|
||||
_term_it_move_cursor(u3_utty* uty_u, c3_w col_w, c3_w row_w)
|
||||
{
|
||||
c3_l row_l = uty_u->tat_u.siz.row_l;
|
||||
c3_l col_l = uty_u->tat_u.siz.col_l;
|
||||
if ( row_w >= row_l ) { row_w = row_l - 1; }
|
||||
if ( col_w >= col_l ) { col_w = col_l - 1; }
|
||||
|
||||
_term_it_send_csi(uty_u, 'H', 2, row_l - row_w, col_w + 1);
|
||||
_term_it_send_csi(uty_u, 'H', 2, row_w + 1, col_w + 1);
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.suc_u);
|
||||
|
||||
uty_u->tat_u.mir.rus_w = row_w;
|
||||
@ -472,7 +473,7 @@ _term_it_restore_line(u3_utty* uty_u)
|
||||
{
|
||||
u3_utat* tat_u = &uty_u->tat_u;
|
||||
|
||||
_term_it_send_csi(uty_u, 'H', 2, tat_u->siz.row_l, 0);
|
||||
_term_it_send_csi(uty_u, 'H', 2, tat_u->siz.row_l, 1);
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.cel_u);
|
||||
_term_it_send_stub(uty_u, u3k(tat_u->mir.lin));
|
||||
//NOTE send_stub restores cursor position
|
||||
@ -489,7 +490,8 @@ _term_it_save_stub(u3_utty* uty_u, u3_noun tub)
|
||||
// keep track of changes to bottom-most line, to aid spinner drawing logic.
|
||||
// -t mode doesn't need this logic, because it doesn't render the spinner.
|
||||
//
|
||||
if ( (0 == tat_u->mir.rus_w) && (c3n == u3_Host.ops_u.tem)) {
|
||||
if ( ( tat_u->siz.row_l - 1 == tat_u->mir.rus_w ) &&
|
||||
( c3n == u3_Host.ops_u.tem ) ) {
|
||||
lin = u3dq("wail:klr:format", lin, tat_u->mir.cus_w, u3k(tub), ' ');
|
||||
lin = u3do("pact:klr:format", lin);
|
||||
}
|
||||
@ -512,8 +514,8 @@ _term_it_show_nel(u3_utty* uty_u)
|
||||
}
|
||||
|
||||
uty_u->tat_u.mir.cus_w = 0;
|
||||
if ( uty_u->tat_u.mir.rus_w > 0 ) {
|
||||
uty_u->tat_u.mir.rus_w--;
|
||||
if ( uty_u->tat_u.mir.rus_w < uty_u->tat_u.siz.row_l - 1 ) {
|
||||
uty_u->tat_u.mir.rus_w++;
|
||||
}
|
||||
else {
|
||||
// newline at bottom of screen, so bottom line is now empty
|
||||
@ -617,9 +619,28 @@ _term_io_belt(u3_utty* uty_u, u3_noun blb)
|
||||
}
|
||||
}
|
||||
|
||||
/* _term_io_suck_char(): process a single character.
|
||||
/* _term_io_spit(): input the buffer (if any), then input the belt (if any)
|
||||
*/
|
||||
static void
|
||||
_term_io_spit(u3_utty* uty_u, u3_noun bet) {
|
||||
u3_utat* tat_u = &uty_u->tat_u;
|
||||
if (u3_nul != tat_u->fut.imp) {
|
||||
_term_io_belt(uty_u, u3nc(c3__txt, u3kb_flop(tat_u->fut.imp)));
|
||||
tat_u->fut.imp = u3_nul;
|
||||
}
|
||||
if (u3_none != bet) {
|
||||
_term_io_belt(uty_u, bet);
|
||||
}
|
||||
}
|
||||
|
||||
/* _term_io_suck_char(): process a single character.
|
||||
*
|
||||
* Note that this accumulates simple inputs in a buffer, and is not
|
||||
* guaranteed to flush it fully. After a call (or sequence of calls)
|
||||
* to this function, please call _term_io_spit(uty_u, u3_none) to
|
||||
* flush any remainder.
|
||||
*/
|
||||
static void
|
||||
_term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
|
||||
{
|
||||
u3_utat* tat_u = &uty_u->tat_u;
|
||||
@ -628,19 +649,39 @@ _term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
|
||||
//
|
||||
if ( c3y == tat_u->esc.ape ) {
|
||||
if ( c3y == tat_u->esc.bra ) {
|
||||
switch ( cay_y ) {
|
||||
default: {
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.bel_u);
|
||||
break;
|
||||
}
|
||||
case 'A': _term_io_belt(uty_u, u3nc(c3__aro, 'u')); break;
|
||||
case 'B': _term_io_belt(uty_u, u3nc(c3__aro, 'd')); break;
|
||||
case 'C': _term_io_belt(uty_u, u3nc(c3__aro, 'r')); break;
|
||||
case 'D': _term_io_belt(uty_u, u3nc(c3__aro, 'l')); break;
|
||||
// vt sequence
|
||||
//
|
||||
case 'M': tat_u->esc.mou = c3y; break;
|
||||
if ( cay_y == '~' ) {
|
||||
switch ( tat_u->esc.seq_y ) {
|
||||
default: {
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.bel_u);
|
||||
break;
|
||||
}
|
||||
case '3': _term_io_spit(uty_u, u3nc(c3__del, u3_nul)); break;
|
||||
}
|
||||
tat_u->esc.ape = tat_u->esc.bra = c3n;
|
||||
tat_u->esc.seq_y = 0;
|
||||
}
|
||||
else if ( cay_y <= '9' ) {
|
||||
tat_u->esc.seq_y = cay_y;
|
||||
}
|
||||
// xterm sequence
|
||||
//
|
||||
else {
|
||||
switch ( cay_y ) {
|
||||
default: {
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.bel_u);
|
||||
break;
|
||||
}
|
||||
case 'A': _term_io_spit(uty_u, u3nc(c3__aro, 'u')); break;
|
||||
case 'B': _term_io_spit(uty_u, u3nc(c3__aro, 'd')); break;
|
||||
case 'C': _term_io_spit(uty_u, u3nc(c3__aro, 'r')); break;
|
||||
case 'D': _term_io_spit(uty_u, u3nc(c3__aro, 'l')); break;
|
||||
//
|
||||
case 'M': tat_u->esc.mou = c3y; break;
|
||||
}
|
||||
tat_u->esc.ape = tat_u->esc.bra = c3n;
|
||||
}
|
||||
tat_u->esc.ape = tat_u->esc.bra = c3n;
|
||||
}
|
||||
else {
|
||||
if ( (cay_y >= 'a') && (cay_y <= 'z') ) {
|
||||
@ -648,13 +689,13 @@ _term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
|
||||
// XX for backwards compatibility, check kelvin version
|
||||
// and fallback to [%met @c]
|
||||
//
|
||||
_term_io_belt(uty_u, u3nt(c3__mod, c3__met, cay_y));
|
||||
_term_io_spit(uty_u, u3nt(c3__mod, c3__met, cay_y));
|
||||
}
|
||||
else if ( 8 == cay_y || 127 == cay_y ) {
|
||||
tat_u->esc.ape = c3n;
|
||||
// XX backwards compatibility [%met @c]
|
||||
//
|
||||
_term_io_belt(uty_u, u3nq(c3__mod, c3__met, c3__bac, u3_nul));
|
||||
_term_io_spit(uty_u, u3nq(c3__mod, c3__met, c3__bac, u3_nul));
|
||||
}
|
||||
else if ( ('[' == cay_y) || ('O' == cay_y) ) {
|
||||
tat_u->esc.bra = c3y;
|
||||
@ -677,9 +718,9 @@ _term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
|
||||
}
|
||||
else {
|
||||
c3_y row_y = cay_y - 32;
|
||||
// only acknowledge button 1 presses within our window
|
||||
// only acknowledge button 1 presses within our known window
|
||||
if ( 1 != tat_u->esc.ton_y && row_y <= tat_u->siz.row_l ) {
|
||||
_term_io_belt(uty_u, u3nt(c3__hit, tat_u->siz.row_l - row_y, tat_u->esc.col_y - 1));
|
||||
_term_io_spit(uty_u, u3nt(c3__hit, tat_u->esc.col_y - 1, row_y - 1));
|
||||
}
|
||||
tat_u->esc.mou = c3n;
|
||||
tat_u->esc.ton_y = tat_u->esc.col_y = 0;
|
||||
@ -699,28 +740,28 @@ _term_io_suck_char(u3_utty* uty_u, c3_y cay_y)
|
||||
wug = u3do("taft", huv);
|
||||
|
||||
tat_u->fut.len_w = tat_u->fut.wid_w = 0;
|
||||
_term_io_belt(uty_u, u3nt(c3__txt, wug, u3_nul));
|
||||
tat_u->fut.imp = u3nc(wug, tat_u->fut.imp);
|
||||
}
|
||||
}
|
||||
// individual characters
|
||||
//
|
||||
else {
|
||||
if ( (cay_y >= 32) && (cay_y < 127) ) { // visual ascii
|
||||
_term_io_belt(uty_u, u3nt(c3__txt, cay_y, u3_nul));
|
||||
tat_u->fut.imp = u3nc(cay_y, tat_u->fut.imp);
|
||||
}
|
||||
else if ( 0 == cay_y ) { // null
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.bel_u);
|
||||
}
|
||||
else if ( 8 == cay_y || 127 == cay_y ) { // backspace & delete
|
||||
_term_io_belt(uty_u, u3nc(c3__bac, u3_nul));
|
||||
_term_io_spit(uty_u, u3nc(c3__bac, u3_nul));
|
||||
}
|
||||
else if ( 10 == cay_y || 13 == cay_y ) { // newline & carriage return
|
||||
_term_io_belt(uty_u, u3nc(c3__ret, u3_nul));
|
||||
_term_io_spit(uty_u, u3nc(c3__ret, u3_nul));
|
||||
}
|
||||
else if ( cay_y <= 26 ) {
|
||||
// XX backwards compatibility [%ctl @c]
|
||||
//
|
||||
_term_io_belt(uty_u, u3nt(c3__mod, c3__ctl, ('a' + (cay_y - 1))));
|
||||
_term_io_spit(uty_u, u3nt(c3__mod, c3__ctl, ('a' + (cay_y - 1))));
|
||||
}
|
||||
else if ( 27 == cay_y ) {
|
||||
tat_u->esc.ape = c3y;
|
||||
@ -778,6 +819,7 @@ _term_suck(u3_utty* uty_u, const c3_y* buf, ssize_t siz_i)
|
||||
for ( i=0; i < siz_i; i++ ) {
|
||||
_term_io_suck_char(uty_u, buf[i]);
|
||||
}
|
||||
_term_io_spit(uty_u, u3_none);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -865,8 +907,8 @@ _term_spin_step(u3_utty* uty_u)
|
||||
// if we know where the bottom line is, and the cursor is not on it,
|
||||
// move it to the bottom left
|
||||
//
|
||||
if ( tat_u->siz.row_l && tat_u->mir.rus_w > 0 ) {
|
||||
_term_it_send_csi(uty_u, 'H', 2, tat_u->siz.row_l, 0);
|
||||
if ( tat_u->siz.row_l && tat_u->mir.rus_w < tat_u->siz.row_l - 1 ) {
|
||||
_term_it_send_csi(uty_u, 'H', 2, tat_u->siz.row_l, 1);
|
||||
}
|
||||
|
||||
c3_w i_w;
|
||||
@ -1313,7 +1355,7 @@ _term_ef_blit(u3_utty* uty_u,
|
||||
case c3__hop: {
|
||||
u3_noun pos = u3t(blt);
|
||||
if ( c3y == u3r_ud(pos) ) {
|
||||
_term_it_move_cursor(uty_u, 0, pos);
|
||||
_term_it_move_cursor(uty_u, pos, uty_u->tat_u.siz.row_l - 1);
|
||||
}
|
||||
else {
|
||||
_term_it_move_cursor(uty_u, u3h(pos), u3t(pos));
|
||||
@ -1325,14 +1367,24 @@ _term_ef_blit(u3_utty* uty_u,
|
||||
} break;
|
||||
|
||||
case c3__lin: { //TMP backwards compatibility
|
||||
_term_it_move_cursor(uty_u, 0, 0);
|
||||
_term_it_move_cursor(uty_u, 0, uty_u->tat_u.siz.row_l - 1);
|
||||
_term_it_clear_line(uty_u);
|
||||
} //
|
||||
case c3__put: {
|
||||
_term_it_show_tour(uty_u, u3k(u3t(blt)));
|
||||
} break;
|
||||
|
||||
case c3__mor: //TMP backwards compatibility
|
||||
case c3__mor: {
|
||||
if (u3_nul != u3t(blt)) {
|
||||
u3_noun bis = u3t(blt);
|
||||
while (u3_nul != bis) {
|
||||
_term_ef_blit(uty_u, u3k(u3h(bis)));
|
||||
bis = u3t(bis);
|
||||
}
|
||||
break;
|
||||
}
|
||||
//TMP fall through to nel for backwards compatibility
|
||||
}
|
||||
case c3__nel: {
|
||||
_term_it_show_nel(uty_u);
|
||||
} break;
|
||||
@ -1709,6 +1761,10 @@ _term_io_exit(u3_auto* car_u)
|
||||
//
|
||||
_term_it_dump_buf(uty_u, &uty_u->ufo_u.mof_u);
|
||||
|
||||
// move cursor to the end
|
||||
//
|
||||
_term_it_move_cursor(uty_u, 0, uty_u->tat_u.siz.row_l - 1);
|
||||
|
||||
// NB, closed in u3_term_log_exit()
|
||||
//
|
||||
uv_read_stop((uv_stream_t*)&(uty_u->pin_u));
|
||||
|
@ -293,7 +293,7 @@ _unix_mkdirp(c3_c* pax_c)
|
||||
while ( fas_c ) {
|
||||
*fas_c = 0;
|
||||
if ( 0 != mkdir(pax_c, 0777) && EEXIST != errno ) {
|
||||
u3l_log("unix: mkdir %s: %s\n", pax_c, strerror(errno));
|
||||
u3l_log("unix: mkdir %s: %s", pax_c, strerror(errno));
|
||||
u3m_bail(c3__fail);
|
||||
}
|
||||
*fas_c++ = '/';
|
||||
@ -318,7 +318,7 @@ u3_unix_save(c3_c* pax_c, u3_atom pad)
|
||||
c3_c* ful_c;
|
||||
|
||||
if ( !u3_unix_cane(pax_c) ) {
|
||||
u3l_log("%s: non-canonical path\n", pax_c);
|
||||
u3l_log("%s: non-canonical path", pax_c);
|
||||
u3z(pad); u3m_bail(c3__fail);
|
||||
}
|
||||
if ( '/' == *pax_c) {
|
||||
@ -333,7 +333,7 @@ u3_unix_save(c3_c* pax_c, u3_atom pad)
|
||||
_unix_mkdirp(ful_c);
|
||||
fid_i = c3_open(ful_c, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
if ( fid_i < 0 ) {
|
||||
u3l_log("%s: %s\n", ful_c, strerror(errno));
|
||||
u3l_log("%s: %s", ful_c, strerror(errno));
|
||||
c3_free(ful_c);
|
||||
u3z(pad); u3m_bail(c3__fail);
|
||||
}
|
||||
@ -347,7 +347,7 @@ u3_unix_save(c3_c* pax_c, u3_atom pad)
|
||||
c3_free(pad_y);
|
||||
|
||||
if ( rit_w != fln_w ) {
|
||||
u3l_log("%s: %s\n", ful_c, strerror(errno));
|
||||
u3l_log("%s: %s", ful_c, strerror(errno));
|
||||
c3_free(ful_c);
|
||||
u3m_bail(c3__fail);
|
||||
}
|
||||
|
@ -576,13 +576,8 @@ _boothack_doom(void)
|
||||
u3_noun whu = u3dc("slaw", 'p', u3k(fak));
|
||||
|
||||
if ( u3_nul == whu ) {
|
||||
<<<<<<< HEAD
|
||||
u3l_log("boot: malformed -F ship %s", u3_Host.ops_u.fak_c);
|
||||
exit(1);
|
||||
=======
|
||||
u3l_log("boot: malformed -F ship %s\r\n", u3_Host.ops_u.fak_c);
|
||||
u3_king_bail();
|
||||
>>>>>>> next/arvo
|
||||
}
|
||||
|
||||
bot = u3nc(c3__fake, u3k(u3t(whu)));
|
||||
@ -979,7 +974,7 @@ _king_save_file(c3_c* url_c, FILE* fil_u)
|
||||
long cod_i;
|
||||
|
||||
if ( !(cul_u = curl_easy_init()) ) {
|
||||
u3l_log("failed to initialize libcurl\n");
|
||||
u3l_log("failed to initialize libcurl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@ -993,11 +988,11 @@ _king_save_file(c3_c* url_c, FILE* fil_u)
|
||||
// XX retry?
|
||||
//
|
||||
if ( CURLE_OK != res_i ) {
|
||||
u3l_log("curl: failed %s: %s\n", url_c, curl_easy_strerror(res_i));
|
||||
u3l_log("curl: failed %s: %s", url_c, curl_easy_strerror(res_i));
|
||||
ret_i = -1;
|
||||
}
|
||||
if ( 300 <= cod_i ) {
|
||||
u3l_log("curl: error %s: HTTP %ld\n", url_c, cod_i);
|
||||
u3l_log("curl: error %s: HTTP %ld", url_c, cod_i);
|
||||
ret_i = -2;
|
||||
}
|
||||
|
||||
@ -1063,14 +1058,14 @@ _king_init_pace(c3_c* pac_c)
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
u3l_log("dock: init pace (%s): open %s\n", pac_c, strerror(errno));
|
||||
u3l_log("dock: init pace (%s): open %s", pac_c, strerror(errno));
|
||||
c3_free(bin_c);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( _king_write_raw(fid_i, (c3_y*)pac_c, strlen(pac_c)) ) {
|
||||
u3l_log("dock: init pace (%s): write %s\n", pac_c, strerror(errno));
|
||||
u3l_log("dock: init pace (%s): write %s", pac_c, strerror(errno));
|
||||
close(fid_i);
|
||||
c3_free(bin_c);
|
||||
return -1;
|
||||
@ -1078,12 +1073,12 @@ _king_init_pace(c3_c* pac_c)
|
||||
// XX sync first?
|
||||
//
|
||||
else if ( close(fid_i) ) {
|
||||
u3l_log("dock: init pace (%s): close %s\n", pac_c, strerror(errno));
|
||||
u3l_log("dock: init pace (%s): close %s", pac_c, strerror(errno));
|
||||
c3_free(bin_c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
u3l_log("dock: pace (%s): configured at %s/.bin/pace\r\n",
|
||||
u3l_log("dock: pace (%s): configured at %s/.bin/pace",
|
||||
pac_c, u3_Host.dir_c);
|
||||
|
||||
return 0;
|
||||
@ -1143,12 +1138,12 @@ u3_king_vere(c3_c* pac_c, // pace
|
||||
|| !(fil_u = fdopen(fid_i, "wb")) )
|
||||
{
|
||||
if ( EEXIST == errno ) {
|
||||
u3l_log("already installed\n");
|
||||
u3l_log("already installed");
|
||||
c3_free(bin_c);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
u3l_log("unable to open %s: %s\r\n", bin_c, strerror(errno));
|
||||
u3l_log("unable to open %s: %s", bin_c, strerror(errno));
|
||||
c3_free(bin_c);
|
||||
return -1;
|
||||
}
|
||||
@ -1159,7 +1154,7 @@ u3_king_vere(c3_c* pac_c, // pace
|
||||
c3_assert( ret_i > 0 );
|
||||
|
||||
if ( (ret_i = _king_save_file(url_c, fil_u)) ) {
|
||||
u3l_log("unable to save %s to %s: %d\r\n", url_c, bin_c, ret_i);
|
||||
u3l_log("unable to save %s to %s: %d", url_c, bin_c, ret_i);
|
||||
c3_free(url_c);
|
||||
fclose(fil_u);
|
||||
unlink(bin_c);
|
||||
@ -1192,7 +1187,7 @@ u3_king_vere(c3_c* pac_c, // pace
|
||||
}
|
||||
}
|
||||
|
||||
u3l_log("vere: saved to %s\n", bin_c);
|
||||
u3l_log("vere: saved to %s", bin_c);
|
||||
|
||||
c3_free(url_c);
|
||||
c3_free(bin_c);
|
||||
@ -1215,13 +1210,13 @@ _king_do_upgrade(c3_c* pac_c, c3_c* ver_c)
|
||||
arc_c = u3_Host.arc_c;
|
||||
}
|
||||
else {
|
||||
u3l_log("vere: --arch required\r\n");
|
||||
u3l_log("vere: --arch required");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if ( _king_make_pace(pac_c) ) {
|
||||
u3l_log("vere: unable to make pace (%s) directory in pier\n", pac_c);
|
||||
u3l_log("vere: unable to make pace (%s) directory in pier", pac_c);
|
||||
u3_king_bail();
|
||||
exit(1);
|
||||
}
|
||||
@ -1234,13 +1229,13 @@ _king_do_upgrade(c3_c* pac_c, c3_c* ver_c)
|
||||
// XX get link option
|
||||
//
|
||||
if ( u3_king_vere(pac_c, ver_c, arc_c, dir_c, 1) ) {
|
||||
u3l_log("vere: upgrade failed\r\n");
|
||||
u3l_log("vere: upgrade failed");
|
||||
u3_king_bail();
|
||||
exit(1);
|
||||
}
|
||||
|
||||
c3_free(dir_c);
|
||||
u3l_log("vere: upgrade succeeded\r\n");
|
||||
u3l_log("vere: upgrade succeeded");
|
||||
// XX print restart instructions
|
||||
}
|
||||
|
||||
@ -1476,7 +1471,7 @@ u3_king_dock(c3_c* pac_c)
|
||||
// XX get link option
|
||||
//
|
||||
if ( _king_copy_vere(pac_c, URBIT_VERSION, arc_c, 1) ) {
|
||||
u3l_log("vere: binary copy failed\r\n");
|
||||
u3l_log("vere: binary copy failed");
|
||||
u3_king_bail();
|
||||
exit(1);
|
||||
}
|
||||
@ -1484,7 +1479,7 @@ u3_king_dock(c3_c* pac_c)
|
||||
// NB: failure ignored
|
||||
//
|
||||
_king_init_pace(pac_c);
|
||||
u3l_log("vere: binary copy succeeded\r\n");
|
||||
u3l_log("vere: binary copy succeeded");
|
||||
// XX print restart instructions
|
||||
}
|
||||
}
|
||||
@ -1514,10 +1509,10 @@ u3_king_done(void)
|
||||
|
||||
if ( u3_Host.xit_i ) {
|
||||
if ( c3y == u3_Host.nex_o ) {
|
||||
u3l_log("vere: upgrade failed\r\n");
|
||||
u3l_log("vere: upgrade failed");
|
||||
}
|
||||
else if ( c3y == u3_Host.pep_o ) {
|
||||
u3l_log("vere: prep for upgrade failed\r\n");
|
||||
u3l_log("vere: prep for upgrade failed");
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -1535,15 +1530,15 @@ u3_king_done(void)
|
||||
|
||||
switch ( u3_king_next(pac_c, &ver_c) ) {
|
||||
case -2: {
|
||||
u3l_log("vere: unable to check for next version\n");
|
||||
u3l_log("vere: unable to check for next version");
|
||||
} break;
|
||||
|
||||
case -1: {
|
||||
u3l_log("vere: up to date\n");
|
||||
u3l_log("vere: up to date");
|
||||
} break;
|
||||
|
||||
case 0: {
|
||||
u3l_log("vere: next (%%%s): %s\n", pac_c, ver_c);
|
||||
u3l_log("vere: next (%%%s): %s", pac_c, ver_c);
|
||||
_king_do_upgrade(pac_c, ver_c);
|
||||
c3_free(ver_c);
|
||||
} break;
|
||||
@ -1554,7 +1549,7 @@ u3_king_done(void)
|
||||
c3_free(pac_c);
|
||||
}
|
||||
else if ( c3y == u3_Host.pep_o ) {
|
||||
u3l_log("vere: ready for upgrade\n");
|
||||
u3l_log("vere: ready for upgrade");
|
||||
}
|
||||
|
||||
// copy binary into pier on boot
|
||||
|
@ -1056,7 +1056,7 @@ _lord_on_serf_err_cb(uv_stream_t* pyp_u,
|
||||
uv_read_stop(pyp_u);
|
||||
|
||||
if ( siz_i != UV_EOF ) {
|
||||
u3l_log("lord: serf stderr: %s\r\n", uv_strerror(siz_i));
|
||||
u3l_log("lord: serf stderr: %s", uv_strerror(siz_i));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,7 +504,7 @@ _pier_on_scry_done(void* ptr_v, u3_noun nun)
|
||||
ext_c = "txt";
|
||||
}
|
||||
else {
|
||||
u3l_log("pier: cannot export cell as %s\n", u3_Host.ops_u.puf_c);
|
||||
u3l_log("pier: cannot export cell as %s", u3_Host.ops_u.puf_c);
|
||||
out = u3_none;
|
||||
}
|
||||
u3z(puf);
|
||||
@ -516,13 +516,8 @@ _pier_on_scry_done(void* ptr_v, u3_noun nun)
|
||||
c3_c fil_c[256];
|
||||
snprintf(fil_c, 256, "%s.%s", pac_c + 1, ext_c);
|
||||
|
||||
<<<<<<< HEAD
|
||||
u3_walk_save(fil_c, 0, out, pir_u->pax_c, pad);
|
||||
u3l_log("pier: scry result in %s", fil_c);
|
||||
=======
|
||||
u3_unix_save(fil_c, out);
|
||||
u3l_log("pier: scry result in %s/.urb/put/%s\n", u3_Host.dir_c, fil_c);
|
||||
>>>>>>> next/arvo
|
||||
u3l_log("pier: scry result in %s/.urb/put/%s", u3_Host.dir_c, fil_c);
|
||||
}
|
||||
}
|
||||
|
||||
@ -678,8 +673,8 @@ _pier_wyrd_fail(u3_pier* pir_u, u3_ovum* egg_u, u3_noun lud)
|
||||
// XX organizing version constants
|
||||
//
|
||||
#define VERE_NAME "vere"
|
||||
#define VERE_ZUSE 417
|
||||
#define VERE_LULL 328
|
||||
#define VERE_ZUSE 416
|
||||
#define VERE_LULL 327
|
||||
|
||||
/* _pier_wyrd_aver(): check for %wend effect and version downgrade. RETAIN
|
||||
*/
|
||||
|
@ -1,326 +0,0 @@
|
||||
/* vere/walk.c
|
||||
**
|
||||
*/
|
||||
#include "all.h"
|
||||
#include "vere/vere.h"
|
||||
|
||||
/* |%
|
||||
** ++ arch :: fs node
|
||||
** $% [& p=@uvI q=*] :: file, hash/data
|
||||
** [| p=(map ,@ta arch)] :: directory
|
||||
** == ::
|
||||
** --
|
||||
*/
|
||||
|
||||
#if 0
|
||||
static u3_noun
|
||||
_walk_ok(u3_noun nod)
|
||||
{
|
||||
u3_noun don = u3n_mung(u3k(u2A->toy.arch), u3k(nod));
|
||||
|
||||
if ( c3n == u3_sing(nod, don) ) {
|
||||
c3_assert(0);
|
||||
}
|
||||
u3z(don);
|
||||
return nod;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* u3_walk_safe(): load file or 0.
|
||||
*/
|
||||
u3_noun
|
||||
u3_walk_safe(c3_c* pas_c)
|
||||
{
|
||||
struct stat buf_b;
|
||||
c3_i fid_i = open(pas_c, O_RDONLY, 0644);
|
||||
c3_w fln_w, red_w;
|
||||
c3_y* pad_y;
|
||||
|
||||
if ( (fid_i < 0) || (fstat(fid_i, &buf_b) < 0) ) {
|
||||
// u3l_log("%s: %s", pas_c, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
fln_w = buf_b.st_size;
|
||||
pad_y = c3_malloc(buf_b.st_size);
|
||||
|
||||
red_w = read(fid_i, pad_y, fln_w);
|
||||
close(fid_i);
|
||||
|
||||
if ( fln_w != red_w ) {
|
||||
c3_free(pad_y);
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
u3_noun pad = u3i_bytes(fln_w, (c3_y *)pad_y);
|
||||
c3_free(pad_y);
|
||||
|
||||
return pad;
|
||||
}
|
||||
}
|
||||
|
||||
/* u3_walk_load(): load file or bail.
|
||||
*/
|
||||
u3_noun
|
||||
u3_walk_load(c3_c* pas_c)
|
||||
{
|
||||
struct stat buf_b;
|
||||
c3_i fid_i = open(pas_c, O_RDONLY, 0644);
|
||||
c3_w fln_w, red_w;
|
||||
c3_y* pad_y;
|
||||
|
||||
if ( (fid_i < 0) || (fstat(fid_i, &buf_b) < 0) ) {
|
||||
u3l_log("%s: %s", pas_c, strerror(errno));
|
||||
return u3m_bail(c3__fail);
|
||||
}
|
||||
fln_w = buf_b.st_size;
|
||||
pad_y = c3_malloc(buf_b.st_size);
|
||||
|
||||
red_w = read(fid_i, pad_y, fln_w);
|
||||
close(fid_i);
|
||||
|
||||
if ( fln_w != red_w ) {
|
||||
c3_free(pad_y);
|
||||
u3l_log("u3_walk_load failed");
|
||||
return u3m_bail(c3__fail);
|
||||
}
|
||||
else {
|
||||
u3_noun pad = u3i_bytes(fln_w, (c3_y *)pad_y);
|
||||
c3_free(pad_y);
|
||||
|
||||
return pad;
|
||||
}
|
||||
}
|
||||
|
||||
/* _walk_mkdirp(): recursively make directories in pax at bas_c (RETAIN)
|
||||
*/
|
||||
static void
|
||||
_walk_mkdirp(c3_c* bas_c, u3_noun pax)
|
||||
{
|
||||
c3_c* pax_c;
|
||||
c3_y* waq_y;
|
||||
c3_w pax_w, fas_w, len_w;
|
||||
|
||||
if ( u3_nul == pax ) {
|
||||
return;
|
||||
}
|
||||
|
||||
pax_w = u3r_met(3, u3h(pax));
|
||||
fas_w = strlen(bas_c);
|
||||
len_w = 1 + fas_w + pax_w;
|
||||
|
||||
pax_c = c3_malloc(1 + len_w);
|
||||
strcpy(pax_c, bas_c);
|
||||
|
||||
pax_c[fas_w] = '/';
|
||||
waq_y = (void*)(1 + pax_c + fas_w);
|
||||
u3r_bytes(0, pax_w, waq_y, u3h(pax));
|
||||
pax_c[len_w] = '\0';
|
||||
|
||||
if ( 0 != mkdir(pax_c, 0755) && EEXIST != errno ) {
|
||||
u3l_log("error mkdiring %s: %s", pax_c, strerror(errno));
|
||||
u3m_bail(c3__fail);
|
||||
}
|
||||
|
||||
_walk_mkdirp(pax_c, u3t(pax));
|
||||
c3_free(pax_c);
|
||||
}
|
||||
|
||||
/* u3_walk_save(): save file or bail.
|
||||
*/
|
||||
void
|
||||
u3_walk_save(c3_c* pas_c, u3_noun tim, u3_atom pad, c3_c* bas_c, u3_noun pax)
|
||||
{
|
||||
c3_i fid_i = open(pas_c, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
||||
c3_w fln_w, rit_w;
|
||||
c3_y* pad_y;
|
||||
|
||||
if ( fid_i < 0 ) {
|
||||
if ( ENOENT == errno && u3_nul != pax ) {
|
||||
_walk_mkdirp(bas_c, pax);
|
||||
return u3_walk_save(pas_c, tim, pad, 0, u3_nul);
|
||||
}
|
||||
|
||||
u3l_log("%s: %s", pas_c, strerror(errno));
|
||||
u3m_bail(c3__fail);
|
||||
}
|
||||
|
||||
fln_w = u3r_met(3, pad);
|
||||
pad_y = c3_malloc(fln_w);
|
||||
u3r_bytes(0, fln_w, pad_y, pad);
|
||||
u3z(pad);
|
||||
u3z(pax);
|
||||
|
||||
rit_w = write(fid_i, pad_y, fln_w);
|
||||
close(fid_i);
|
||||
c3_free(pad_y);
|
||||
|
||||
if ( rit_w != fln_w ) {
|
||||
u3l_log("%s: %s", pas_c, strerror(errno));
|
||||
u3m_bail(c3__fail);
|
||||
}
|
||||
|
||||
if ( 0 != tim ) {
|
||||
struct timeval tim_tv[2];
|
||||
|
||||
u3_time_out_tv(&tim_tv[0], u3k(tim));
|
||||
u3_time_out_tv(&tim_tv[1], tim);
|
||||
|
||||
utimes(pas_c, tim_tv);
|
||||
}
|
||||
}
|
||||
|
||||
/* _walk_in(): inner loop of _walk(), producing map.
|
||||
*/
|
||||
static u3_noun
|
||||
_walk_in(const c3_c* dir_c, c3_w len_w)
|
||||
{
|
||||
DIR* dir_d = opendir(dir_c);
|
||||
u3_noun map = u3_nul;
|
||||
|
||||
if ( !dir_d ) {
|
||||
return u3_nul;
|
||||
}
|
||||
else while ( 1 ) {
|
||||
struct dirent ent_n;
|
||||
struct dirent* out_n;
|
||||
|
||||
if ( u3_readdir_r(dir_d, &ent_n, &out_n) != 0 ) {
|
||||
u3l_log("%s: %s", dir_c, strerror(errno));
|
||||
break;
|
||||
}
|
||||
else if ( !out_n ) {
|
||||
break;
|
||||
}
|
||||
else if ( !strcmp(out_n->d_name, ".") ||
|
||||
!strcmp(out_n->d_name, "..") ||
|
||||
('~' == out_n->d_name[0]) ||
|
||||
('.' == out_n->d_name[0]) ) // XX restricts some spans
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
c3_c* fil_c = out_n->d_name;
|
||||
c3_w lef_w = len_w + 1 + strlen(fil_c);
|
||||
c3_c* pat_c = c3_malloc(lef_w + 1);
|
||||
struct stat buf_b;
|
||||
|
||||
strncpy(pat_c, dir_c, lef_w);
|
||||
pat_c[len_w] = '/';
|
||||
strncpy(pat_c + len_w + 1, fil_c, lef_w);
|
||||
pat_c[lef_w] = '\0';
|
||||
|
||||
if ( 0 != stat(pat_c, &buf_b) ) {
|
||||
c3_free(pat_c);
|
||||
} else {
|
||||
u3_noun tim = c3_stat_mtime(&buf_b);
|
||||
|
||||
if ( !S_ISDIR(buf_b.st_mode) ) {
|
||||
c3_c* dot_c = strrchr(fil_c, '.');
|
||||
c3_c* nam_c = strdup(fil_c);
|
||||
c3_c* ext_c = strdup(dot_c + 1);
|
||||
|
||||
nam_c[dot_c - fil_c] = 0;
|
||||
{
|
||||
u3_noun nam = u3i_string(nam_c);
|
||||
u3_noun ext = u3i_string(ext_c);
|
||||
u3_noun get = u3kdb_get(u3k(map), u3k(nam));
|
||||
u3_noun dat = u3_walk_load(pat_c);
|
||||
u3_noun hax;
|
||||
|
||||
if ( !strcmp("noun", ext_c) ) {
|
||||
dat = u3ke_cue(dat);
|
||||
}
|
||||
hax = u3do("sham", u3k(dat));
|
||||
if ( u3_none == get ) { get = u3_nul; }
|
||||
|
||||
get = u3kdb_put(get, ext, u3nt(c3y, hax, dat));
|
||||
map = u3kdb_put(map, nam, u3nc(c3n, get));
|
||||
}
|
||||
c3_free(nam_c);
|
||||
c3_free(ext_c);
|
||||
}
|
||||
else {
|
||||
u3_noun dir = _walk_in(pat_c, lef_w);
|
||||
|
||||
if ( u3_nul != dir ) {
|
||||
map = u3kdb_put
|
||||
(map, u3i_string(fil_c), u3nc(c3n, dir));
|
||||
}
|
||||
else u3z(tim);
|
||||
}
|
||||
c3_free(pat_c);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir_d);
|
||||
return map;
|
||||
}
|
||||
|
||||
/* u3_walk(): traverse `dir_c` to produce an arch, updating `old`.
|
||||
*/
|
||||
u3_noun
|
||||
u3_walk(const c3_c* dir_c, u3_noun old)
|
||||
{
|
||||
// XX - obviously, cheaper to update old data.
|
||||
u3z(old);
|
||||
{
|
||||
struct stat buf_b;
|
||||
|
||||
if ( 0 != stat(dir_c, &buf_b) ) {
|
||||
u3l_log("can't stat %s", dir_c);
|
||||
// return u3m_bail(c3__fail);
|
||||
c3_assert(0);
|
||||
}
|
||||
else {
|
||||
return u3nc(c3n,
|
||||
_walk_in(dir_c, strlen(dir_c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* u3_path(): C unix path in computer for file or directory.
|
||||
*/
|
||||
c3_c*
|
||||
u3_path(c3_o fyl, u3_noun pax)
|
||||
{
|
||||
c3_w len_w;
|
||||
c3_c *pas_c;
|
||||
|
||||
// measure
|
||||
//
|
||||
len_w = strlen(u3_Local);
|
||||
{
|
||||
u3_noun wiz = pax;
|
||||
|
||||
while ( u3_nul != wiz ) {
|
||||
len_w += (1 + u3r_met(3, u3h(wiz)));
|
||||
wiz = u3t(wiz);
|
||||
}
|
||||
}
|
||||
|
||||
// cut
|
||||
//
|
||||
pas_c = c3_malloc(len_w + 1);
|
||||
strncpy(pas_c, u3_Local, len_w);
|
||||
pas_c[len_w] = '\0';
|
||||
{
|
||||
u3_noun wiz = pax;
|
||||
c3_c* waq_c = (pas_c + strlen(pas_c));
|
||||
|
||||
while ( u3_nul != wiz ) {
|
||||
c3_w tis_w = u3r_met(3, u3h(wiz));
|
||||
|
||||
if ( (c3y == fyl) && (u3_nul == u3t(wiz)) ) {
|
||||
*waq_c++ = '.';
|
||||
} else *waq_c++ = '/';
|
||||
|
||||
u3r_bytes(0, tis_w, (c3_y*)waq_c, u3h(wiz));
|
||||
waq_c += tis_w;
|
||||
|
||||
wiz = u3t(wiz);
|
||||
}
|
||||
*waq_c = 0;
|
||||
}
|
||||
u3z(pax);
|
||||
return pas_c;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
:~ title+'Terminal'
|
||||
info+'A web interface to your Urbit\'s command line.'
|
||||
color+0x2e.4347
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v7.1hgb7.euged.6oj3e.cdhdg.rah02.glob' 0v7.1hgb7.euged.6oj3e.cdhdg.rah02]
|
||||
glob-http+['https://bootstrap.urbit.org/glob-0v5.hvjci.n7c4h.1onl6.34g14.fut7c.glob' 0v5.hvjci.n7c4h.1onl6.34g14.fut7c]
|
||||
base+'webterm'
|
||||
version+[1 0 1]
|
||||
version+[1 1 0]
|
||||
website+'https://tlon.io'
|
||||
license+'MIT'
|
||||
==
|
||||
|
@ -1 +1 @@
|
||||
[%zuse 417]
|
||||
[%zuse 416]
|
||||
|
Loading…
Reference in New Issue
Block a user