Remove binary from git
[notion.git] / mod_query / mod_query.lua
blob776dae06df745503b50f7865e9bec415b098dbc6
1 --
2 -- ion/query/mod_query.lua -- Some common queries for Ion
3 --
4 -- Copyright (c) Tuomo Valkonen 2004-2009.
5 --
6 -- See the included file LICENSE for details.
7 --
10 -- This is a slight abuse of the package.loaded variable perhaps, but
11 -- library-like packages should handle checking if they're loaded instead of
12 -- confusing the user with require/include differences.
13 if package.loaded["mod_query"] then return end
15 if not ioncore.load_module("mod_query") then
16 return
17 end
19 local mod_query=_G["mod_query"]
21 assert(mod_query)
24 local DIE_TIMEOUT_ERRORCODE=10 -- 10 seconds
25 local DIE_TIMEOUT_NO_ERRORCODE=2 -- 2 seconds
28 -- Generic helper functions {{{
31 --DOC
32 -- Display an error message box in the multiplexer \var{mplex}.
33 function mod_query.warn(mplex, str)
34 ioncore.unsqueeze(mod_query.do_warn(mplex, str))
35 end
38 --DOC
39 -- Display a message in \var{mplex}.
40 function mod_query.message(mplex, str)
41 ioncore.unsqueeze(mod_query.do_message(mplex, str))
42 end
45 --DOC
46 -- Low-level query routine. \var{mplex} is the \type{WMPlex} to display
47 -- the query in, \var{prompt} the prompt string, and \var{initvalue}
48 -- the initial contents of the query box. \var{handler} is a function
49 -- that receives (\var{mplex}, result string) as parameter when the
50 -- query has been succesfully completed, \var{completor} the completor
51 -- routine which receives a (\var{cp}, \var{str}, \var{point}) as parameters.
52 -- The parameter \var{str} is the string to be completed and \var{point}
53 -- cursor's location within it. Completions should be eventually,
54 -- possibly asynchronously, set with \fnref{WComplProxy.set_completions}
55 -- on \var{cp}.
56 function mod_query.query(mplex, prompt, initvalue, handler, completor,
57 context)
58 local function handle_it(str)
59 handler(mplex, str)
60 end
61 local function cycle(wedln)
62 wedln:complete('next', 'normal')
63 end
64 local function bcycle(wedln)
65 wedln:complete('prev', 'normal')
66 end
68 -- Check that no other queries or message boxes are open in the mplex.
69 local ok=mplex:managed_i(function(r)
70 return not (obj_is(r, "WEdln") or
71 obj_is(r, "WMessage"))
72 end)
73 if not ok then
74 return
75 end
77 local wedln=mod_query.do_query(mplex, prompt, initvalue,
78 handle_it, completor, cycle, bcycle)
80 if wedln then
81 ioncore.unsqueeze(wedln)
83 if context then
84 wedln:set_context(context)
85 end
86 end
88 return wedln
89 end
92 --DOC
93 -- This function query will display a query with prompt \var{prompt} in
94 -- \var{mplex} and if the user answers affirmately, call \var{handler}
95 -- with \var{mplex} as parameter.
96 function mod_query.query_yesno(mplex, prompt, handler)
97 local function handler_yesno(mplex, str)
98 if str=="y" or str=="Y" or str=="yes" then
99 handler(mplex)
102 return mod_query.query(mplex, prompt, nil, handler_yesno, nil,
103 "yesno")
107 local errdata={}
109 local function maybe_finish(pid)
110 local t=errdata[pid]
112 if t and t.closed and t.dietime then
113 errdata[pid]=nil
114 local tmd=os.difftime(t.dietime, t.starttime)
115 --if tmd<DIE_TIMEOUT_ERRORCODE and t.signaled then
116 -- local msg=TR("Program received signal ")..t.termsig.."\n"
117 -- mod_query.warn(t.mplex, msg..(t.errs or ""))
118 --else
119 if ((tmd<DIE_TIMEOUT_ERRORCODE and (t.hadcode or t.signaled)) or
120 (tmd<DIE_TIMEOUT_NO_ERRORCODE)) and t.errs then
121 mod_query.warn(t.mplex, t.errs)
127 local badsig_={4, 5, 6, 7, 8, 11}
128 local badsig={}
129 for _, v in pairs(badsig_) do
130 badsig[v]=true
133 local function chld_handler(p)
134 local t=errdata[p.pid]
135 if t then
136 t.dietime=os.time()
137 t.signaled=(p.signaled and badsig[p.termsig])
138 t.termsig=p.termsig
139 t.hadcode=(p.exited and p.exitstatus~=0)
140 maybe_finish(pid)
144 ioncore.get_hook("ioncore_sigchld_hook"):add(chld_handler)
146 function mod_query.exec_on_merr(mplex, cmd)
147 local pid
149 local function monitor(str)
150 if pid then
151 local t=errdata[pid]
152 if t then
153 if str then
154 t.errs=(t.errs or "")..str
155 else
156 t.closed=true
157 maybe_finish(pid)
163 local function timeout()
164 errdata[pid]=nil
167 pid=ioncore.exec_on(mplex, cmd, monitor)
169 if pid<=0 then
170 return
173 local tmr=ioncore.create_timer();
174 local tmd=math.max(DIE_TIMEOUT_NO_ERRORCODE, DIE_TIMEOUT_ERRORCODE)
175 local now=os.time()
176 tmr:set(tmd*1000, timeout)
178 errdata[pid]={tmr=tmr, mplex=mplex, starttime=now}
182 function mod_query.file_completor(cp, str)
183 local ic=ioncore.lookup_script("ion-completefile")
184 if ic then
185 mod_query.popen_completions(cp, ic.." "..string.shell_safe(str))
190 function mod_query.get_initdir(mplex)
191 --if mod_query.last_dir then
192 -- return mod_query.last_dir
193 --end
194 local wd=(ioncore.get_dir_for(mplex) or os.getenv("PWD"))
195 if wd==nil then
196 wd="/"
197 elseif string.sub(wd, -1)~="/" then
198 wd=wd .. "/"
200 return wd
204 function mod_query.query_execfile(mplex, prompt, prog)
205 assert(prog~=nil)
206 local function handle_execwith(mplex, str)
207 mod_query.exec_on_merr(mplex, prog.." "..string.shell_safe(str))
209 return mod_query.query(mplex, prompt, mod_query.get_initdir(mplex),
210 handle_execwith, mod_query.file_completor,
211 "filename")
215 function mod_query.query_execwith(mplex, prompt, dflt, prog, completor,
216 context, noquote)
217 local function handle_execwith(frame, str)
218 if not str or str=="" then
219 str=dflt
221 local args=(noquote and str or string.shell_safe(str))
222 mod_query.exec_on_merr(mplex, prog.." "..args)
224 return mod_query.query(mplex, prompt, nil, handle_execwith, completor,
225 context)
229 -- }}}
232 -- Completion helpers {{{
234 local pipes={}
236 mod_query.COLLECT_THRESHOLD=2000
238 --DOC
239 -- This function can be used to read completions from an external source.
240 -- The parameter \var{cp} is the completion proxy to be used,
241 -- and the string \var{cmd} the shell command to be executed, in the directory
242 -- \var{wd}.
243 -- To its stdout, the command should on the first line write the \var{common_beg}
244 -- parameter of \fnref{WComplProxy.set_completions} (which \var{fn} maybe used
245 -- to override) and a single actual completion on each of the successive lines.
246 -- The function \var{reshnd} may be used to override a result table
247 -- building routine. Its first argument is the completion table to be
248 -- passed to \fnref{WComplProxy.set_completions}, and the second a new
249 -- line of output from the command.
250 function mod_query.popen_completions(cp, cmd, fn, reshnd, wd)
252 local pst={cp=cp, maybe_stalled=0}
254 if not reshnd then
255 reshnd = function(rs, a)
256 if not rs.common_beg then
257 rs.common_beg=a
258 else
259 table.insert(rs, a)
264 local function rcv(str)
265 local data=""
266 local results={}
267 local totallen=0
268 local lines=0
270 while str do
271 if pst.maybe_stalled>=2 then
272 pipes[rcv]=nil
273 return
275 pst.maybe_stalled=0
277 totallen=totallen+string.len(str)
278 if totallen>ioncore.RESULT_DATA_LIMIT then
279 error(TR("Too much result data"))
283 data=string.gsub(data..str, "([^\n]*)\n",
284 function(s)
285 reshnd(results, s)
286 lines=lines+1
287 return ""
288 end)
290 if lines>mod_query.COLLECT_THRESHOLD then
291 collectgarbage()
292 lines=0
295 str=coroutine.yield()
298 if not results.common_beg then
299 results.common_beg=beg
302 (fn or WComplProxy.set_completions)(cp, results)
304 pipes[rcv]=nil
305 results={}
307 collectgarbage()
310 local found_clean=false
312 for k, v in pairs(pipes) do
313 if v.cp==cp then
314 if v.maybe_stalled<2 then
315 v.maybe_stalled=v.maybe_stalled+1
316 found_clean=true
321 if not found_clean then
322 pipes[rcv]=pst
323 ioncore.popen_bgread(cmd, coroutine.wrap(rcv), nil, wd)
328 local function mk_completion_test(str, sub_ok, casei_ok)
329 local settings=mod_query.get()
331 if not str then
332 return function(s) return true end
335 local function mk(str, sub_ok)
336 if sub_ok then
337 return function(s) return string.find(s, str, 1, true) end
338 else
339 local len=string.len(str)
340 return function(s) return string.sub(s, 1, len)==str end
344 casei_ok=(casei_ok and settings.caseicompl)
345 sub_ok=(sub_ok and settings.substrcompl)
347 if not casei_ok then
348 return mk(str, sub_ok)
349 else
350 local fn=mk(string.lower(str), sub_ok)
351 return function(s) return fn(string.lower(s)) end
356 local function mk_completion_add(entries, str, sub_ok, casei_ok)
357 local tst=mk_completion_test(str, sub_ok, casei_ok)
359 return function(s)
360 if s and tst(s) then
361 table.insert(entries, s)
367 function mod_query.complete_keys(list, str, sub_ok, casei_ok)
368 local results={}
369 local test_add=mk_completion_add(results, str, sub_ok, casei_ok)
371 for m, _ in pairs(list) do
372 test_add(m)
375 return results
376 end
379 function mod_query.complete_name(str, iter)
380 local sub_ok_first=true
381 local casei_ok=true
382 local entries={}
383 local tst_add=mk_completion_add(entries, str, sub_ok_first, casei_ok)
385 iter(function(reg)
386 tst_add(reg:name())
387 return true
388 end)
390 if #entries==0 and not sub_ok_first then
391 local tst_add2=mk_completion_add(entries, str, true, casei_ok)
392 iter(function(reg)
393 tst_add2(reg:name())
394 return true
395 end)
398 return entries
402 function mod_query.make_completor(completefn)
403 local function completor(cp, str, point)
404 cp:set_completions(completefn(str, point))
406 return completor
410 -- }}}
413 -- Simple queries for internal actions {{{
416 function mod_query.call_warn(mplex, fn)
417 local err = collect_errors(fn)
418 if err then
419 mod_query.warn(mplex, err)
421 return err
425 function mod_query.complete_clientwin(str)
426 return mod_query.complete_name(str, ioncore.clientwin_i)
430 function mod_query.complete_workspace(str)
431 local function iter(fn)
432 return ioncore.region_i(function(obj)
433 return (not obj_is(obj, "WGroupWS")
434 or fn(obj))
435 end)
437 return mod_query.complete_name(str, iter)
441 function mod_query.complete_region(str)
442 return mod_query.complete_name(str, ioncore.region_i)
446 function mod_query.gotoclient_handler(frame, str)
447 local cwin=ioncore.lookup_clientwin(str)
449 if cwin==nil then
450 mod_query.warn(frame, TR("Could not find client window %s.", str))
451 else
452 cwin:goto_focus()
457 function mod_query.attachclient_handler(frame, str)
458 local cwin=ioncore.lookup_clientwin(str)
460 if not cwin then
461 mod_query.warn(frame, TR("Could not find client window %s.", str))
462 return
465 local reg=cwin:groupleader_of()
467 local function attach()
468 frame:attach(reg, { switchto = true })
471 if frame:rootwin_of()~=reg:rootwin_of() then
472 mod_query.warn(frame, TR("Cannot attach: different root windows."))
473 elseif reg:manager()==frame then
474 reg:goto_focus()
475 else
476 mod_query.call_warn(frame, attach)
481 function mod_query.workspace_handler(mplex, name)
482 local ws=ioncore.lookup_region(name, "WGroupWS")
483 if ws then
484 ws:goto_focus()
485 else
486 local function create_handler(mplex_, layout)
487 if not layout or layout=="" then
488 layout="default"
491 if not ioncore.getlayout(layout) then
492 mod_query.warn(mplex_, TR("Unknown layout"))
493 else
494 local scr=mplex:screen_of()
496 local function mkws()
497 local tmpl={
498 name=(name~="" and name),
499 switchto=true
501 if not ioncore.create_ws(scr, tmpl, layout) then
502 error(TR("Unknown error"))
506 mod_query.call_warn(mplex, mkws)
510 local function compl_layout(str)
511 local los=ioncore.getlayout(nil, true)
512 return mod_query.complete_keys(los, str, true, true)
515 mod_query.query(mplex, TR("New workspace layout (default):"), nil,
516 create_handler, mod_query.make_completor(compl_layout),
517 "workspacelayout")
522 --DOC
523 -- This query asks for the name of a client window and switches
524 -- focus to the one entered. It uses the completion function
525 -- \fnref{ioncore.complete_clientwin}.
526 function mod_query.query_gotoclient(mplex)
527 mod_query.query(mplex, TR("Go to window:"), nil,
528 mod_query.gotoclient_handler,
529 mod_query.make_completor(mod_query.complete_clientwin),
530 "windowname")
533 --DOC
534 -- This query asks for the name of a client window and attaches
535 -- it to the frame the query was opened in. It uses the completion
536 -- function \fnref{ioncore.complete_clientwin}.
537 function mod_query.query_attachclient(mplex)
538 mod_query.query(mplex, TR("Attach window:"), nil,
539 mod_query.attachclient_handler,
540 mod_query.make_completor(mod_query.complete_clientwin),
541 "windowname")
545 --DOC
546 -- This query asks for the name of a workspace. If a workspace
547 -- (an object inheriting \type{WGroupWS}) with such a name exists,
548 -- it will be switched to. Otherwise a new workspace with the
549 -- entered name will be created and the user will be queried for
550 -- the type of the workspace.
551 function mod_query.query_workspace(mplex)
552 mod_query.query(mplex, TR("Go to or create workspace:"), nil,
553 mod_query.workspace_handler,
554 mod_query.make_completor(mod_query.complete_workspace),
555 "workspacename")
559 --DOC
560 -- This query asks whether the user wants to exit Ion (no session manager)
561 -- or close the session (running under a session manager that supports such
562 -- requests). If the answer is 'y', 'Y' or 'yes', so will happen.
563 function mod_query.query_shutdown(mplex)
564 mod_query.query_yesno(mplex, TR("Exit Notion/Shutdown session (y/n)?"),
565 ioncore.shutdown)
569 --DOC
570 -- This query asks whether the user wants restart Ioncore.
571 -- If the answer is 'y', 'Y' or 'yes', so will happen.
572 function mod_query.query_restart(mplex)
573 mod_query.query_yesno(mplex, TR("Restart Notion (y/n)?"), ioncore.restart)
577 --DOC
578 -- This function asks for a name new for the frame where the query
579 -- was created.
580 function mod_query.query_renameframe(frame)
581 mod_query.query(frame, TR("Frame name:"), frame:name(),
582 function(frame, str) frame:set_name(str) end,
583 nil, "framename")
587 --DOC
588 -- This function asks for a name new for the workspace \var{ws},
589 -- or the one on which \var{mplex} resides, if it is not set.
590 -- If \var{mplex} is not set, one is looked for.
591 function mod_query.query_renameworkspace(mplex, ws)
592 if not mplex then
593 assert(ws)
594 mplex=ioncore.find_manager(ws, "WMPlex")
595 elseif not ws then
596 assert(mplex)
597 ws=ioncore.find_manager(mplex, "WGroupWS")
600 assert(mplex and ws)
602 mod_query.query(mplex, TR("Workspace name:"), ws:name(),
603 function(mplex, str) ws:set_name(str) end,
604 nil, "framename")
608 -- }}}
611 -- Run/view/edit {{{
614 --DOC
615 -- Asks for a file to be edited. This script uses
616 -- \command{run-mailcap --mode=edit} by default, but you may provide an
617 -- alternative script to use. The default prompt is "Edit file:" (translated).
618 function mod_query.query_editfile(mplex, script, prompt)
619 mod_query.query_execfile(mplex,
620 prompt or TR("Edit file:"),
621 script or "run-mailcap --action=edit")
625 --DOC
626 -- Asks for a file to be viewed. This script uses
627 -- \command{run-mailcap --action=view} by default, but you may provide an
628 -- alternative script to use. The default prompt is "View file:" (translated).
629 function mod_query.query_runfile(mplex, script, prompt)
630 mod_query.query_execfile(mplex,
631 prompt or TR("View file:"),
632 script or "run-mailcap --action=view")
637 local function isspace(s)
638 return string.find(s, "^%s*$")~=nil
642 local function break_cmdline(str, no_ws)
643 local st, en, beg, rest, ch, rem
644 local res={""}
646 local function ins(str)
647 local n=#res
648 if string.find(res[n], "^%s+$") then
649 table.insert(res, str)
650 else
651 res[n]=res[n]..str
655 local function ins_space(str)
656 local n=#res
657 if no_ws then
658 if res[n]~="" then
659 table.insert(res, "")
661 else
662 if isspace(res[n]) then
663 res[n]=res[n]..str
664 else
665 table.insert(res, str)
670 -- Handle terminal startup syntax
671 st, en, beg, ch, rest=string.find(str, "^(%s*)(:+)(.*)")
672 if beg then
673 if string.len(beg)>0 then
674 ins_space(beg)
676 ins(ch)
677 ins_space("")
678 str=rest
681 while str~="" do
682 st, en, beg, rest, ch=string.find(str, "^(.-)(([%s'\"\\|])(.*))")
683 if not beg then
684 ins(str)
685 break
688 ins(beg)
689 str=rest
691 local sp=false
693 if ch=="\\" then
694 st, en, beg, rest=string.find(str, "^(\\.)(.*)")
695 elseif ch=='"' then
696 st, en, beg, rest=string.find(str, "^(\".-[^\\]\")(.*)")
698 if not beg then
699 st, en, beg, rest=string.find(str, "^(\"\")(.*)")
701 elseif ch=="'" then
702 st, en, beg, rest=string.find(str, "^('.-')(.*)")
703 else
704 if ch=='|' then
705 ins_space('')
706 ins(ch)
707 else -- ch==' '
708 ins_space(ch)
710 st, en, beg, rest=string.find(str, "^.(%s*)(.*)")
711 assert(beg and rest)
712 ins_space(beg)
713 sp=true
714 str=rest
717 if not sp then
718 if not beg then
719 beg=str
720 rest=""
722 ins(beg)
723 str=rest
727 return res
731 local function unquote(str)
732 str=string.gsub(str, "^['\"]", "")
733 str=string.gsub(str, "([^\\])['\"]", "%1")
734 str=string.gsub(str, "\\(.)", "%1")
735 return str
739 local function quote(str)
740 return string.gsub(str, "([%(%)\"'\\%*%?%[%]%| ])", "\\%1")
744 local function find_point(strs, point)
745 for i, s in ipairs(strs) do
746 point=point-string.len(s)
747 if point<=1 then
748 return i
751 return #strs
755 function mod_query.exec_completor_(wd, cp, str, point)
756 local parts=break_cmdline(str)
757 local complidx=find_point(parts, point+1)
759 local s_compl, s_beg, s_end="", "", ""
761 if complidx==1 and string.find(parts[1], "^:+$") then
762 complidx=complidx+1
765 if string.find(parts[complidx], "[^%s]") then
766 s_compl=unquote(parts[complidx])
769 for i=1, complidx-1 do
770 s_beg=s_beg..parts[i]
773 for i=complidx+1, #parts do
774 s_end=s_end..parts[i]
777 local wp=" "
778 if complidx==1 or (complidx==2 and isspace(parts[1])) then
779 wp=" -wp "
780 elseif string.find(parts[1], "^:+$") then
781 if complidx==2 then
782 wp=" -wp "
783 elseif string.find(parts[2], "^%s*$") then
784 if complidx==3 then
785 wp=" -wp "
790 local function set_fn(cp, res)
791 res=table.map(quote, res)
792 res.common_beg=s_beg..(res.common_beg or "")
793 res.common_end=(res.common_end or "")..s_end
794 cp:set_completions(res)
797 local function filter_fn(res, s)
798 if not res.common_beg then
799 if s=="./" then
800 res.common_beg=""
801 else
802 res.common_beg=s
804 else
805 table.insert(res, s)
809 local ic=ioncore.lookup_script("ion-completefile")
810 if ic then
811 mod_query.popen_completions(cp,
812 ic..wp..string.shell_safe(s_compl),
813 set_fn, filter_fn, wd)
818 function mod_query.exec_completor(...)
819 mod_query.exec_completor_(nil, ...)
823 local cmd_overrides={}
826 --DOC
827 -- Define a command override for the \fnrefx{mod_query}{query_exec} query.
828 function mod_query.defcmd(cmd, fn)
829 cmd_overrides[cmd]=fn
833 function mod_query.exec_handler(mplex, cmdline)
834 local parts=break_cmdline(cmdline, true)
835 local cmd=table.remove(parts, 1)
837 if cmd_overrides[cmd] then
838 cmd_overrides[cmd](mplex, table.map(unquote, parts))
839 elseif cmd~="" then
840 mod_query.exec_on_merr(mplex, cmdline)
845 --DOC
846 -- This function asks for a command to execute with \file{/bin/sh}.
847 -- If the command is prefixed with a colon (':'), the command will
848 -- be run in an XTerm (or other terminal emulator) using the script
849 -- \file{ion-runinxterm}. Two colons ('::') will ask you to press
850 -- enter after the command has finished.
851 function mod_query.query_exec(mplex)
852 local function compl(...)
853 local wd=ioncore.get_dir_for(mplex)
854 mod_query.exec_completor_(wd, ...)
856 mod_query.query(mplex, TR("Run:"), nil, mod_query.exec_handler,
857 compl, "run")
861 -- }}}
864 -- SSH {{{
867 mod_query.known_hosts={}
868 mod_query.hostnicks={}
869 mod_query.ssh_completions={}
872 function mod_query.get_known_hosts(mplex)
873 mod_query.known_hosts={}
874 local f
875 local h=os.getenv("HOME")
876 if h then
877 f=io.open(h.."/.ssh/known_hosts")
879 if not f then
880 warn(TR("Failed to open ~/.ssh/known_hosts"))
881 return
883 for l in f:lines() do
884 local st, en, hostname=string.find(l, "^([^%s,]+)")
885 if hostname then
886 table.insert(mod_query.known_hosts, hostname)
889 f:close()
893 function mod_query.get_hostnicks(mplex)
894 mod_query.hostnicks={}
895 local f
896 local substr, pat, patterns
897 local h=os.getenv("HOME")
899 if h then
900 f=io.open(h.."/.ssh/config")
902 if not f then
903 warn(TR("Failed to open ~/.ssh/config"))
904 return
907 for l in f:lines() do
908 _, _, substr=string.find(l, "^%s*[hH][oO][sS][tT](.*)")
909 if substr then
910 _, _, pat=string.find(substr, "^%s*[=%s]%s*(%S.*)")
911 if pat then
912 patterns=pat
913 elseif string.find(substr, "^[nN][aA][mM][eE]")
914 and patterns then
915 for s in string.gmatch(patterns, "%S+") do
916 if not string.find(s, "[*?]") then
917 table.insert(mod_query.hostnicks, s)
923 f:close()
927 function mod_query.complete_ssh(str)
928 local st, en, user, at, host=string.find(str, "^([^@]*)(@?)(.*)$")
930 if string.len(at)==0 and string.len(host)==0 then
931 host = user; user = ""
934 if at=="@" then
935 user = user .. at
938 local res = {}
939 local tst = mk_completion_test(host, true, true)
941 for _, v in ipairs(mod_query.ssh_completions) do
942 if tst(v) then
943 table.insert(res, user .. v)
947 return res
951 --DOC
952 -- This query asks for a host to connect to with SSH.
953 -- Hosts to tab-complete are read from \file{\~{}/.ssh/known\_hosts}.
954 function mod_query.query_ssh(mplex, ssh)
955 mod_query.get_known_hosts(mplex)
956 mod_query.get_hostnicks(mplex)
958 for _, v in ipairs(mod_query.known_hosts) do
959 table.insert(mod_query.ssh_completions, v)
961 for _, v in ipairs(mod_query.hostnicks) do
962 table.insert(mod_query.ssh_completions, v)
965 ssh=(ssh or ":ssh")
967 local function handle_exec(mplex, str)
968 if not (str and string.find(str, "[^%s]")) then
969 return
972 mod_query.exec_on_merr(mplex, ssh.." "..string.shell_safe(str))
975 return mod_query.query(mplex, TR("SSH to:"), nil, handle_exec,
976 mod_query.make_completor(mod_query.complete_ssh),
977 "ssh")
980 -- }}}
983 -- Man pages {{{{
986 function mod_query.man_completor(cp, str)
987 local mc=ioncore.lookup_script("ion-completeman")
988 local icase=(mod_query.get().caseicompl and " -icase" or "")
989 local mid=""
990 if mc then
991 mod_query.popen_completions(cp, (mc..icase..mid.." -complete "
992 ..string.shell_safe(str)))
997 --DOC
998 -- This query asks for a manual page to display. By default it runs the
999 -- \command{man} command in an \command{xterm} using \command{ion-runinxterm},
1000 -- but it is possible to pass another program as the \var{prog} argument.
1001 function mod_query.query_man(mplex, prog)
1002 local dflt=ioncore.progname()
1003 mod_query.query_execwith(mplex, TR("Manual page (%s):", dflt),
1004 dflt, prog or ":man",
1005 mod_query.man_completor, "man",
1006 true --[[ no quoting ]])
1010 -- }}}
1013 -- Lua code execution {{{
1016 function mod_query.create_run_env(mplex)
1017 local origenv
1018 if _ENV then origenv=_ENV else origenv=getfenv() end
1019 local meta={__index=origenv, __newindex=origenv}
1020 local env={
1021 _=mplex,
1022 _sub=mplex:current(),
1023 print=my_print
1025 setmetatable(env, meta)
1026 return env
1029 function mod_query.do_handle_lua(mplex, env, code)
1030 local print_res
1031 local function collect_print(...)
1032 local tmp=""
1033 local arg={...}
1034 local l=#arg
1035 for i=1,l do
1036 tmp=tmp..tostring(arg[i])..(i==l and "\n" or "\t")
1038 print_res=(print_res and print_res..tmp or tmp)
1042 if _ENV then
1043 env.print=collect_print
1044 local f, err=load(code,nil, nil, env)
1045 if not f then
1046 mod_query.warn(mplex, err)
1047 return
1049 err=collect_errors(f)
1050 else
1051 local f, err=loadstring(code)
1052 if not f then
1053 mod_query.warn(mplex, err)
1054 return
1056 env.print=collect_print
1057 setfenv(f, env)
1058 err=collect_errors(f)
1060 if err then
1061 mod_query.warn(mplex, err)
1062 elseif print_res then
1063 mod_query.message(mplex, print_res)
1067 local function getindex(t)
1068 local mt=getmetatable(t)
1069 if mt then return mt.__index end
1070 return nil
1073 function mod_query.do_complete_lua(env, str)
1074 -- Get the variable to complete, including containing tables.
1075 -- This will also match string concatenations and such because
1076 -- Lua's regexps don't support optional subexpressions, but we
1077 -- handle them in the next step.
1078 local comptab=env
1079 local metas=true
1080 local _, _, tocomp=string.find(str, "([%w_.:]*)$")
1082 -- Descend into tables
1083 if tocomp and string.len(tocomp)>=1 then
1084 for t in string.gmatch(tocomp, "([^.:]*)[.:]") do
1085 metas=false
1086 if string.len(t)==0 then
1087 comptab=env;
1088 elseif comptab then
1089 if type(comptab[t])=="table" then
1090 comptab=comptab[t]
1091 elseif type(comptab[t])=="userdata" then
1092 comptab=getindex(comptab[t])
1093 metas=true
1094 else
1095 comptab=nil
1101 if not comptab then return {} end
1103 local compl={}
1105 -- Get the actual variable to complete without containing tables
1106 _, _, compl.common_beg, tocomp=string.find(str, "(.-)([%w_]*)$")
1108 local l=string.len(tocomp)
1110 local tab=comptab
1111 local seen={}
1112 while true do
1113 if type(tab) == "table" then
1114 for k in pairs(tab) do
1115 if type(k)=="string" then
1116 if string.sub(k, 1, l)==tocomp then
1117 table.insert(compl, k)
1123 -- We only want to display full list of functions for objects, not
1124 -- the tables representing the classes.
1125 --if not metas then break end
1127 seen[tab]=true
1128 tab=getindex(tab)
1129 if not tab or seen[tab] then break end
1132 -- If there was only one completion and it is a string or function,
1133 -- concatenate it with "." or "(", respectively.
1134 if #compl==1 then
1135 if type(comptab[compl[1]])=="table" then
1136 compl[1]=compl[1] .. "."
1137 elseif type(comptab[compl[1]])=="function" then
1138 compl[1]=compl[1] .. "("
1142 return compl
1146 --DOC
1147 -- This query asks for Lua code to execute. It sets the variable '\var{\_}'
1148 -- in the local environment of the string to point to the mplex where the
1149 -- query was created. It also sets the table \var{arg} in the local
1150 -- environment to \code{\{_, _:current()\}}.
1151 function mod_query.query_lua(mplex)
1152 local env=mod_query.create_run_env(mplex)
1154 local function complete(cp, code)
1155 cp:set_completions(mod_query.do_complete_lua(env, code))
1158 local function handler(mplex, code)
1159 return mod_query.do_handle_lua(mplex, env, code)
1162 mod_query.query(mplex, TR("Lua code:"), nil, handler, complete, "lua")
1165 -- }}}
1168 -- Menu query {{{
1170 --DOC
1171 -- This query can be used to create a query of a defined menu.
1172 function mod_query.query_menu(mplex, sub, themenu, prompt)
1173 if type(sub)=="string" then
1174 -- Backwards compat. shift
1175 prompt=themenu
1176 themenu=sub
1177 sub=nil
1180 local menu=ioncore.evalmenu(themenu, mplex, sub)
1181 local menuname=(type(themenu)=="string" and themenu or "?")
1183 if not menu then
1184 mod_query.warn(mplex, TR("Unknown menu %s.", tostring(themenu)))
1185 return
1188 if not prompt then
1189 prompt=menuname..":"
1190 else
1191 prompt=TR(prompt)
1194 local function xform_name(n, is_submenu)
1195 return string.lower(string.gsub(n, "[-/%s]+", "-"))
1198 local function xform_menu(t, m, p)
1199 for _, v in ipairs(m) do
1200 if v.name then
1201 local is_submenu=v.submenu_fn
1202 local n=p..xform_name(v.name)
1204 while t[n] or t[n..'/'] do
1205 n=n.."'"
1208 if is_submenu then
1209 n=n..'/'
1212 t[n]=v
1214 if is_submenu and not v.noautoexpand then
1215 local sm=v.submenu_fn()
1216 if sm then
1217 xform_menu(t, sm, n)
1218 else
1219 ioncore.warn_traced(TR("Missing submenu ")
1220 ..(v.name or ""))
1225 return t
1228 local ntab=xform_menu({}, menu, "")
1230 local function complete(str)
1231 return mod_query.complete_keys(ntab, str, true, true)
1234 local function handle(mplex, str)
1235 local e=ntab[str]
1236 if e then
1237 if e.func then
1238 local err=collect_errors(function()
1239 e.func(mplex, _sub)
1240 end)
1241 if err then
1242 mod_query.warn(mplex, err)
1244 elseif e.submenu_fn then
1245 mod_query.query_menu(mplex, e.submenu_fn(),
1246 TR("%s:", e.name))
1248 else
1249 mod_query.warn(mplex, TR("No entry '%s'", str))
1253 mod_query.query(mplex, prompt, nil, handle,
1254 mod_query.make_completor(complete), "menu."..menuname)
1257 -- }}}
1260 -- Miscellaneous {{{
1263 --DOC
1264 -- Display an "About Ion" message in \var{mplex}.
1265 function mod_query.show_about_ion(mplex)
1266 mod_query.message(mplex, ioncore.aboutmsg())
1270 --DOC
1271 -- Show information about a region tree
1272 function mod_query.show_tree(mplex, reg, max_depth)
1273 local function indent(s)
1274 local i=" "
1275 return i..string.gsub(s, "\n", "\n"..i)
1278 local function get_info(reg, indent, d)
1279 if not reg then
1280 return (indent .. "No region")
1283 local function n(s) return (s or "") end
1285 local s=string.format("%s%s \"%s\"", indent, obj_typename(reg),
1286 n(reg:name()))
1287 indent = indent .. " "
1288 if obj_is(reg, "WClientWin") then
1289 local i=reg:get_ident()
1290 s=s .. TR("\n%sClass: %s\n%sRole: %s\n%sInstance: %s\n%sXID: 0x%x",
1291 indent, n(i.class),
1292 indent, n(i.role),
1293 indent, n(i.instance),
1294 indent, reg:xid())
1297 if (not max_depth or max_depth > d) and reg.managed_i then
1298 local first=true
1299 reg:managed_i(function(sub)
1300 if first then
1301 s=s .. "\n" .. indent .. "---"
1302 first=false
1304 s=s .. "\n" .. get_info(sub, indent, d+1)
1305 return true
1306 end)
1309 return s
1312 mod_query.message(mplex, get_info(reg, "", 0))
1315 -- }}}
1317 -- Load extras
1318 dopath('mod_query_chdir')
1320 -- Mark ourselves loaded.
1321 package.loaded["mod_query"]=true
1324 -- Load configuration file
1325 dopath('cfg_query', true)