local Y=require("yaru.core") local gfx=require("yaru.gfx") local ui=require("yaru.ui") local component=require("component") local unicode=require("unicode") local M={} local RISKY={shutdown=true,set=true,setData=true,remove=true,makeReadonly=true,setLabel=true,format=true, stop=true,crash=true} local function fmt(v,depth) depth=depth or 0 local t=type(v) if t=="string"then return string.format("%q",#v>200 and v:sub(1,200).."…"or v)end if t~="table"or depth>1 then return tostring(v)end local parts,n={},0 for k,x in pairs(v)do n=n+1 if n>12 then parts[#parts+1]="…"break end parts[#parts+1]=(type(k)=="string"and k.."="or"")..fmt(x,depth+1) end return"{"..table.concat(parts,", ").."}" end local function wrap(s,w) local out={} for line in(s.."\n"):gmatch("(.-)\n")do while unicode.len(line)>w do out[#out+1]=unicode.sub(line,1,w);line=unicode.sub(line,w+1)end out[#out+1]=line end return out end function M.open() local st={ci=1,mi=1,ctop=0,mtop=0,args={text="",cur=0},out=nil} local win=Y.newWindow{app="explorer",title="Component Explorer",w=110,h=34,minW=80,minH=16} local function comps() local r={} for addr,typ in component.list()do r[#r+1]={addr=addr,type=typ}end table.sort(r,function(a,b)return a.type==b.type and a.addr0 and table.concat(parts,", ")or"(no values)",false} else st.out={tostring(r[2]),true}end end function win.draw(w,cw,ch) local t=Y.theme local list=comps() st.ci=math.min(st.ci,#list) local c=list[st.ci] local CW1,CW2=30,26 gfx.fill(1,1,CW1,ch,t.sidebar) gfx.text(2,1,#list.." components",t.dim) for i=1,ch-2 do local e=list[st.ctop+i] if not e then break end local y=i+1 if st.ctop+i==st.ci then ui.select(2,y,CW1-2,t.dark and 0x454545 or 0xDEDDDA)end gfx.text(3,y,ui.trunc(e.type,16),t.fg) gfx.text(20,y,e.addr:sub(1,8),t.dim) local idx=st.ctop+i ui.hit(w,1,y,CW1,1,function()st.ci,st.mi,st.mtop,st.out=idx,1,0,nil end) end if not c then return end local ms=methods(c.addr) st.mi=math.min(st.mi,math.max(1,#ms)) gfx.text(CW1+2,1,#ms.." methods",t.dim) for i=1,ch-2 do local m=ms[st.mtop+i] if not m then break end local y=i+1 if st.mtop+i==st.mi then ui.select(CW1+1,y,CW2-1,t.sel)end gfx.text(CW1+2,y,ui.trunc(m.name,CW2-5),st.mtop+i==st.mi and t.selFg or t.fg) if m.direct then gfx.text(CW1+CW2-3,y,"·",t.dim)end local idx=st.mtop+i ui.hit(w,CW1+1,y,CW2,1,function()st.mi,st.out=idx,nil end) end local m=ms[st.mi] local x,pw=CW1+CW2+2,cw-CW1-CW2-3 gfx.text(x,1,c.type,t.fg) gfx.text(x,2,c.addr,t.dim) if not m then return end local y=4 ui.round(x,y,pw,1,t.card) gfx.text(x+1,y,ui.trunc(c.type.."."..m.name..(m.direct and" (direct)"or" (indirect, 1 call/tick)"),pw-2),t.accent,t.card) y=y+2 local ok,doc=pcall(component.doc,c.addr,m.name) for _,l in ipairs(wrap(ok and doc or"No documentation.",pw))do if y>ch-9 then break end gfx.text(x,y,l,t.fg);y=y+1 end y=math.max(y+1,ch-7) gfx.text(x,y,"Arguments (Lua, comma separated)",t.dim) ui.entry(w,st.args,x,y+1,pw-10,Y.focus==w,"e.g. 1, \"text\", true") ui.button(w,x+pw-8,y+1,"Call",function() if RISKY[m.name]then Y.dialog{title="Call "..m.name.."?",text="This method can change or erase data, or stop the computer.", buttons={{"Cancel"},{"Call",function()call(c,m)end,"destructive"}}} else call(c,m)end end,"suggested",8) if st.out then local lines=wrap(st.out[1],pw) for i=1,math.min(#lines,ch-y-2)do gfx.text(x,y+2+i,lines[i],st.out[2]and t.danger or t.success)end end end function win.key(w,char,code) local r=ui.entryKey(st.args,char,code) if r=="enter"then local list=comps() local c=list[st.ci] local ms=c and methods(c.addr) if ms and ms[st.mi]and not RISKY[ms[st.mi].name]then call(c,ms[st.mi])end elseif not r then if code==Y.K.down then st.mi=st.mi+1 elseif code==Y.K.up then st.mi=math.max(1,st.mi-1) elseif code==Y.K.pgdn then st.ci=st.ci+1;st.mi=1 elseif code==Y.K.pgup then st.ci=math.max(1,st.ci-1);st.mi=1 end end end function win.paste(w,s)ui.entryPaste(st.args,s)end function win.scroll(w,x,y,dir) if x<=30 then st.ctop=math.max(0,st.ctop-dir*3)else st.mtop=math.max(0,st.mtop-dir*3)end end function win.onSignal(name)if name=="component_added"or name=="component_removed"then Y.redraw(win)end end return win end return M