local Y=require("yaru.core") local gfx=require("yaru.gfx") local ui=require("yaru.ui") local fs=require("filesystem") local unicode=require("unicode") local M={} local SKIP={["/dev"]=true,["/proc"]=true} function M.open(root) local st={q={text="",cur=0},root=root or"/",content=false,results={},sel=1,top=0} local win=Y.newWindow{app="search",title="Search",w=90,h=28,minW=50,minH=12} local gen=0 local function run() local q=unicode.lower(st.q.text) if q==""then return end gen=gen+1 local my=gen st.results,st.sel,st.top,st.busy,st.scanned={},1,0,true,0 require("thread").create(function() local function visit(dir) if my~=gen or SKIP[dir]then return end for name in fs.list(dir)do if my~=gen then return end local isDir=name:sub(-1)=="/" local n=isDir and name:sub(1,-2)or name local p=fs.concat(dir,n) st.scanned=st.scanned+1 if st.scanned%40==0 then Y.redraw(win);os.sleep(0)end if unicode.lower(n):find(q,1,true)then st.results[#st.results+1]={path=p,dir=isDir}end if isDir then if not fs.isLink(p)then visit(p)end elseif st.content and(fs.size(p)or 0)<65536 then local f=io.open(p) if f then local ln=0 for line in f:lines()do ln=ln+1 if unicode.lower(line):find(q,1,true)then st.results[#st.results+1]={path=p,line=ln,text=line:gsub("^%s+","")} break end end f:close() end end if#st.results>=300 then gen=gen+1 end end end pcall(visit,fs.canonical(st.root)) if my==gen or#st.results>=300 then st.busy=false end Y.redraw(win) end):detach() end local function open(r) if not r then return end if r.dir then Y.launch("files",r.path) elseif r.path:match("%.pic$")then Y.launch("viewer",r.path) else Y.launch("editor",r.path)end end function win.draw(w,cw,ch) local t=Y.theme ui.entry(w,st.q,2,1,cw-36,Y.focus==w,"File name"..(st.content and" or text inside files"or"")) ui.button(w,cw-33,1,"in "..ui.trunc(st.root,10),function() Y.dialog{title="Search In",entry=st.root,buttons={{"Cancel"},{"Set",function(p) if p and fs.isDirectory(p)then st.root=p end end,"suggested"}}} end,nil,15) gfx.text(cw-16,1,"Contents",t.fg) ui.switch(w,cw-7,1,st.content,function()st.content=not st.content end) local status=st.busy and string.format("Searching… %d items checked, %d found",st.scanned,#st.results) or(#st.results>0 and string.format("%d result%s",#st.results,#st.results==1 and""or"s")or(st.scanned and"Nothing found"or"Type and press Enter")) gfx.text(2,3,status,t.dim) local rows=ch-4 if st.sel-1st.top+rows then st.top=st.sel-rows end for i=1,rows do local r=st.results[st.top+i] if not r then break end local y=3+i local sel=st.top+i==st.sel if sel then ui.select(1,y,cw-1,t.sel)end local fg=sel and t.selFg or t.fg gfx.text(3,y,r.dir and"▆"or r.line and"≡"or"□",sel and fg or(r.dir and 0xE9AD0C or t.dim)) local name=fs.name(r.path) gfx.text(5,y,ui.trunc(name,30),fg) local rest=r.line and(r.line..": "..r.text)or fs.path(r.path) gfx.text(37,y,ui.trunc(rest,cw-39),sel and fg or t.dim) local idx=st.top+i ui.hit(w,1,y,cw,1,function(_,_,_,btn,dbl) st.sel=idx if dbl then open(st.results[idx])end if btn==1 then Y.menu(w.x+10,w.y+y+1,{{"Open",function()open(st.results[idx])end}, {"Open Folder",function()Y.launch("files",fs.path(st.results[idx].path))end}})end end) end end function win.key(w,char,code) local K=Y.K if code==K.down then st.sel=math.min(#st.results,st.sel+1)return end if code==K.up then st.sel=math.max(1,st.sel-1)return end local r=ui.entryKey(st.q,char,code) if r=="enter"then if st.results[st.sel]and st.lastQ==st.q.text then open(st.results[st.sel])else st.lastQ=st.q.text;run()end end end function win.paste(w,s)ui.entryPaste(st.q,s)end function win.cleanup()gen=gen+1 end return win end return M