2 -- ion/query/mod_query.lua -- Some common queries for Ion
4 -- Copyright (c) Tuomo Valkonen 2004-2009.
6 -- See the included file LICENSE for details.
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
19 local mod_query=_G["mod_query"]
24 local DIE_TIMEOUT_ERRORCODE=10 -- 10 seconds
25 local DIE_TIMEOUT_NO_ERRORCODE=2 -- 2 seconds
28 -- Generic helper functions {{{
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))
39 -- Display a message in \var{mplex}.
40 function mod_query.message(mplex, str)
41 ioncore.unsqueeze(mod_query.do_message(mplex, str))
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}
56 function mod_query.query(mplex, prompt, initvalue, handler, completor,
58 local function handle_it(str)
61 local function cycle(wedln)
62 wedln:complete('next', 'normal')
64 local function bcycle(wedln)
65 wedln:complete('prev', 'normal')
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"))
77 local wedln=mod_query.do_query(mplex, prompt, initvalue,
78 handle_it, completor, cycle, bcycle)
81 ioncore.unsqueeze(wedln)
84 wedln:set_context(context)
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
102 return mod_query.query(mplex, prompt, nil, handler_yesno, nil,
109 local function maybe_finish(pid)
112 if t and t.closed and t.dietime then
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 ""))
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}
129 for _, v in pairs(badsig_) do
133 local function chld_handler(p)
134 local t=errdata[p.pid]
137 t.signaled=(p.signaled and badsig[p.termsig])
139 t.hadcode=(p.exited and p.exitstatus~=0)
144 ioncore.get_hook("ioncore_sigchld_hook"):add(chld_handler)
146 function mod_query.exec_on_merr(mplex, cmd)
149 local function monitor(str)
154 t.errs=(t.errs or "")..str
163 local function timeout()
167 pid=ioncore.exec_on(mplex, cmd, monitor)
173 local tmr=ioncore.create_timer();
174 local tmd=math.max(DIE_TIMEOUT_NO_ERRORCODE, DIE_TIMEOUT_ERRORCODE)
176 tmr:set(tmd*1000, timeout)
178 errdata[pid]={tmr=tmr, mplex=mplex, starttime=now}
182 function mod_query.file_completor(wedln, str)
183 local ic=ioncore.lookup_script("ion-completefile")
185 mod_query.popen_completions(wedln,
186 ic.." "..string.shell_safe(str))
191 function mod_query.get_initdir(mplex)
192 --if mod_query.last_dir then
193 -- return mod_query.last_dir
195 local wd=(ioncore.get_dir_for(mplex) or os.getenv("PWD"))
198 elseif string.sub(wd, -1)~="/" then
205 function mod_query.query_execfile(mplex, prompt, prog)
207 local function handle_execwith(mplex, str)
208 mod_query.exec_on_merr(mplex, prog.." "..string.shell_safe(str))
210 return mod_query.query(mplex, prompt, mod_query.get_initdir(mplex),
211 handle_execwith, mod_query.file_completor,
216 function mod_query.query_execwith(mplex, prompt, dflt, prog, completor,
218 local function handle_execwith(frame, str)
219 if not str or str=="" then
222 local args=(noquote and str or string.shell_safe(str))
223 mod_query.exec_on_merr(mplex, prog.." "..args)
225 return mod_query.query(mplex, prompt, nil, handle_execwith, completor,
233 -- Completion helpers {{{
237 mod_query.COLLECT_THRESHOLD=2000
240 -- This function can be used to read completions from an external source.
241 -- The parameter \var{cp} is the completion proxy to be used,
242 -- and the string \var{cmd} the shell command to be executed, in the directory
244 -- To its stdout, the command should on the first line write the \var{common_beg}
245 -- parameter of \fnref{WComplProxy.set_completions} (which \var{fn} maybe used
246 -- to override) and a single actual completion on each of the successive lines.
247 -- The function \var{reshnd} may be used to override a result table
249 function mod_query.popen_completions(cp, cmd, fn, reshnd, wd)
251 local pst={cp=cp, maybe_stalled=0}
254 reshnd = function(rs, a)
255 if not rs.common_beg then
263 local function rcv(str)
270 if pst.maybe_stalled>=2 then
276 totallen=totallen+string.len(str)
277 if totallen>ioncore.RESULT_DATA_LIMIT then
278 error(TR("Too much result data"))
282 data=string.gsub(data..str, "([^\n]*)\n",
289 if lines>mod_query.COLLECT_THRESHOLD then
294 str=coroutine.yield()
297 if not results.common_beg then
298 results.common_beg=beg
301 (fn or WComplProxy.set_completions)(cp, results)
309 local found_clean=false
311 for k, v in pairs(pipes) do
313 if v.maybe_stalled<2 then
314 v.maybe_stalled=v.maybe_stalled+1
320 if not found_clean then
322 ioncore.popen_bgread(cmd, coroutine.wrap(rcv), nil, wd)
327 local function mk_completion_test(str, sub_ok, casei_ok)
328 local settings=mod_query.get()
331 return function(s) return true end
334 local function mk(str, sub_ok)
336 return function(s) return string.find(s, str, 1, true) end
338 local len=string.len(str)
339 return function(s) return string.sub(s, 1, len)==str end
343 casei_ok=(casei_ok and settings.caseicompl)
344 sub_ok=(sub_ok and settings.substrcompl)
347 return mk(str, sub_ok)
349 local fn=mk(string.lower(str), sub_ok)
350 return function(s) return fn(string.lower(s)) end
355 local function mk_completion_add(entries, str, sub_ok, casei_ok)
356 local tst=mk_completion_test(str, sub_ok, casei_ok)
360 table.insert(entries, s)
366 function mod_query.complete_keys(list, str, sub_ok, casei_ok)
368 local test_add=mk_completion_add(results, str, sub_ok, casei_ok)
370 for m, _ in pairs(list) do
378 function mod_query.complete_name(str, iter)
379 local sub_ok_first=true
382 local tst_add=mk_completion_add(entries, str, sub_ok_first, casei_ok)
389 if #entries==0 and not sub_ok_first then
390 local tst_add2=mk_completion_add(entries, str, true, casei_ok)
401 function mod_query.make_completor(completefn)
402 local function completor(cp, str, point)
403 cp:set_completions(completefn(str, point))
412 -- Simple queries for internal actions {{{
415 function mod_query.call_warn(mplex, fn)
416 local err = collect_errors(fn)
418 mod_query.warn(mplex, err)
424 function mod_query.complete_clientwin(str)
425 return mod_query.complete_name(str, ioncore.clientwin_i)
429 function mod_query.complete_workspace(str)
430 local function iter(fn)
431 return ioncore.region_i(function(obj)
432 return (not obj_is(obj, "WGroupWS")
436 return mod_query.complete_name(str, iter)
440 function mod_query.complete_region(str)
441 return mod_query.complete_name(str, ioncore.region_i)
445 function mod_query.gotoclient_handler(frame, str)
446 local cwin=ioncore.lookup_clientwin(str)
449 mod_query.warn(frame, TR("Could not find client window %s.", str))
456 function mod_query.attachclient_handler(frame, str)
457 local cwin=ioncore.lookup_clientwin(str)
460 mod_query.warn(frame, TR("Could not find client window %s.", str))
464 local reg=cwin:groupleader_of()
466 local function attach()
467 frame:attach(reg, { switchto = true })
470 if frame:rootwin_of()~=reg:rootwin_of() then
471 mod_query.warn(frame, TR("Cannot attach: different root windows."))
472 elseif reg:manager()==frame then
475 mod_query.call_warn(frame, attach)
480 function mod_query.workspace_handler(mplex, name)
481 local ws=ioncore.lookup_region(name, "WGroupWS")
485 local function create_handler(mplex_, layout)
486 if not layout or layout=="" then
490 if not ioncore.getlayout(layout) then
491 mod_query.warn(mplex_, TR("Unknown layout"))
493 local scr=mplex:screen_of()
495 local function mkws()
497 name=(name~="" and name),
500 if not ioncore.create_ws(scr, tmpl, layout) then
501 error(TR("Unknown error"))
505 mod_query.call_warn(mplex, mkws)
509 local function compl_layout(str)
510 local los=ioncore.getlayout(nil, true)
511 return mod_query.complete_keys(los, str, true, true)
514 mod_query.query(mplex, TR("New workspace layout (default):"), nil,
515 create_handler, mod_query.make_completor(compl_layout),
522 -- This query asks for the name of a client window and switches
523 -- focus to the one entered. It uses the completion function
524 -- \fnref{ioncore.complete_clientwin}.
525 function mod_query.query_gotoclient(mplex)
526 mod_query.query(mplex, TR("Go to window:"), nil,
527 mod_query.gotoclient_handler,
528 mod_query.make_completor(mod_query.complete_clientwin),
533 -- This query asks for the name of a client window and attaches
534 -- it to the frame the query was opened in. It uses the completion
535 -- function \fnref{ioncore.complete_clientwin}.
536 function mod_query.query_attachclient(mplex)
537 mod_query.query(mplex, TR("Attach window:"), nil,
538 mod_query.attachclient_handler,
539 mod_query.make_completor(mod_query.complete_clientwin),
545 -- This query asks for the name of a workspace. If a workspace
546 -- (an object inheriting \type{WGroupWS}) with such a name exists,
547 -- it will be switched to. Otherwise a new workspace with the
548 -- entered name will be created and the user will be queried for
549 -- the type of the workspace.
550 function mod_query.query_workspace(mplex)
551 mod_query.query(mplex, TR("Go to or create workspace:"), nil,
552 mod_query.workspace_handler,
553 mod_query.make_completor(mod_query.complete_workspace),
559 -- This query asks whether the user wants to exit Ion (no session manager)
560 -- or close the session (running under a session manager that supports such
561 -- requests). If the answer is 'y', 'Y' or 'yes', so will happen.
562 function mod_query.query_shutdown(mplex)
563 mod_query.query_yesno(mplex, TR("Exit Ion/Shutdown session (y/n)?"),
569 -- This query asks whether the user wants restart Ioncore.
570 -- If the answer is 'y', 'Y' or 'yes', so will happen.
571 function mod_query.query_restart(mplex)
572 mod_query.query_yesno(mplex, TR("Restart Ion (y/n)?"), ioncore.restart)
577 -- This function asks for a name new for the frame where the query
579 function mod_query.query_renameframe(frame)
580 mod_query.query(frame, TR("Frame name:"), frame:name(),
581 function(frame, str) frame:set_name(str) end,
587 -- This function asks for a name new for the workspace \var{ws},
588 -- or the one on which \var{mplex} resides, if it is not set.
589 -- If \var{mplex} is not set, one is looked for.
590 function mod_query.query_renameworkspace(mplex, ws)
593 mplex=ioncore.find_manager(ws, "WMPlex")
596 ws=ioncore.find_manager(mplex, "WGroupWS")
601 mod_query.query(mplex, TR("Workspace name:"), ws:name(),
602 function(mplex, str) ws:set_name(str) end,
614 -- Asks for a file to be edited. This script uses
615 -- \command{run-mailcap --mode=edit} by default, but you may provide an
616 -- alternative script to use. The default prompt is "Edit file:" (translated).
617 function mod_query.query_editfile(mplex, script, prompt)
618 mod_query.query_execfile(mplex,
619 prompt or TR("Edit file:"),
620 script or "run-mailcap --action=edit")
625 -- Asks for a file to be viewed. This script uses
626 -- \command{run-mailcap --action=view} by default, but you may provide an
627 -- alternative script to use. The default prompt is "View file:" (translated).
628 function mod_query.query_runfile(mplex, script, prompt)
629 mod_query.query_execfile(mplex,
630 prompt or TR("View file:"),
631 script or "run-mailcap --action=view")
636 local function isspace(s)
637 return string.find(s, "^%s*$")~=nil
641 local function break_cmdline(str, no_ws)
642 local st, en, beg, rest, ch, rem
645 local function ins(str)
647 if string.find(res[n], "^%s+$") then
648 table.insert(res, str)
654 local function ins_space(str)
658 table.insert(res, "")
661 if isspace(res[n]) then
664 table.insert(res, str)
669 -- Handle terminal startup syntax
670 st, en, beg, ch, rest=string.find(str, "^(%s*)(:+)(.*)")
672 if string.len(beg)>0 then
681 st, en, beg, rest, ch=string.find(str, "^(.-)(([%s'\"\\|])(.*))")
693 st, en, beg, rest=string.find(str, "^(\\.)(.*)")
695 st, en, beg, rest=string.find(str, "^(\".-[^\\]\")(.*)")
698 st, en, beg, rest=string.find(str, "^(\"\")(.*)")
701 st, en, beg, rest=string.find(str, "^('.-')(.*)")
709 st, en, beg, rest=string.find(str, "^.(%s*)(.*)")
730 local function unquote(str)
731 str=string.gsub(str, "^['\"]", "")
732 str=string.gsub(str, "([^\\])['\"]", "%1")
733 str=string.gsub(str, "\\(.)", "%1")
738 local function quote(str)
739 return string.gsub(str, "([%(%)\"'\\%*%?%[%]%| ])", "\\%1")
743 local function find_point(strs, point)
744 for i, s in ipairs(strs) do
745 point=point-string.len(s)
754 function mod_query.exec_completor_(wd, wedln, str, point)
755 local parts=break_cmdline(str)
756 local complidx=find_point(parts, point+1)
758 local s_compl, s_beg, s_end="", "", ""
760 if complidx==1 and string.find(parts[1], "^:+$") then
764 if string.find(parts[complidx], "[^%s]") then
765 s_compl=unquote(parts[complidx])
768 for i=1, complidx-1 do
769 s_beg=s_beg..parts[i]
772 for i=complidx+1, #parts do
773 s_end=s_end..parts[i]
777 if complidx==1 or (complidx==2 and isspace(parts[1])) then
779 elseif string.find(parts[1], "^:+$") then
782 elseif string.find(parts[2], "^%s*$") then
789 local function set_fn(cp, res)
790 res=table.map(quote, res)
791 res.common_beg=s_beg..(res.common_beg or "")
792 res.common_end=(res.common_end or "")..s_end
793 cp:set_completions(res)
796 local function filter_fn(res, s)
797 if not res.common_beg then
808 local ic=ioncore.lookup_script("ion-completefile")
810 mod_query.popen_completions(wedln,
811 ic..wp..string.shell_safe(s_compl),
812 set_fn, filter_fn, wd)
817 function mod_query.exec_completor(...)
818 mod_query.exec_completor_(nil, ...)
822 local cmd_overrides={}
826 -- Define a command override for the \fnrefx{mod_query}{query_exec} query.
827 function mod_query.defcmd(cmd, fn)
828 cmd_overrides[cmd]=fn
832 function mod_query.exec_handler(mplex, cmdline)
833 local parts=break_cmdline(cmdline, true)
834 local cmd=table.remove(parts, 1)
836 if cmd_overrides[cmd] then
837 cmd_overrides[cmd](mplex, table.map(unquote, parts))
839 mod_query.exec_on_merr(mplex, cmdline)
845 -- This function asks for a command to execute with \file{/bin/sh}.
846 -- If the command is prefixed with a colon (':'), the command will
847 -- be run in an XTerm (or other terminal emulator) using the script
848 -- \file{ion-runinxterm}. Two colons ('::') will ask you to press
849 -- enter after the command has finished.
850 function mod_query.query_exec(mplex)
851 local function compl(...)
852 local wd=ioncore.get_dir_for(mplex)
853 mod_query.exec_completor_(wd, ...)
855 mod_query.query(mplex, TR("Run:"), nil, mod_query.exec_handler,
866 mod_query.known_hosts={}
867 mod_query.hostnicks={}
868 mod_query.ssh_completions={}
871 function mod_query.get_known_hosts(mplex)
872 mod_query.known_hosts={}
874 local h=os.getenv("HOME")
876 f=io.open(h.."/.ssh/known_hosts")
879 warn(TR("Failed to open ~/.ssh/known_hosts"))
882 for l in f:lines() do
883 local st, en, hostname=string.find(l, "^([^%s,]+)")
885 table.insert(mod_query.known_hosts, hostname)
892 function mod_query.get_hostnicks(mplex)
893 mod_query.hostnicks={}
895 local substr, pat, patterns
896 local h=os.getenv("HOME")
899 f=io.open(h.."/.ssh/config")
902 warn(TR("Failed to open ~/.ssh/config"))
906 for l in f:lines() do
907 _, _, substr=string.find(l, "^%s*[hH][oO][sS][tT](.*)")
909 _, _, pat=string.find(substr, "^%s*[=%s]%s*(%S.*)")
912 elseif string.find(substr, "^[nN][aA][mM][eE]")
914 for s in string.gmatch(patterns, "%S+") do
915 if not string.find(s, "[*?]") then
916 table.insert(mod_query.hostnicks, s)
926 function mod_query.complete_ssh(str)
927 local st, en, user, at, host=string.find(str, "^([^@]*)(@?)(.*)$")
929 if string.len(at)==0 and string.len(host)==0 then
930 host = user; user = ""
938 local tst = mk_completion_test(host, true, true)
940 for _, v in ipairs(mod_query.ssh_completions) do
942 table.insert(res, user .. v)
951 -- This query asks for a host to connect to with SSH.
952 -- Hosts to tab-complete are read from \file{\~{}/.ssh/known\_hosts}.
953 function mod_query.query_ssh(mplex, ssh)
954 mod_query.get_known_hosts(mplex)
955 mod_query.get_hostnicks(mplex)
957 for _, v in ipairs(mod_query.known_hosts) do
958 table.insert(mod_query.ssh_completions, v)
960 for _, v in ipairs(mod_query.hostnicks) do
961 table.insert(mod_query.ssh_completions, v)
966 local function handle_exec(mplex, str)
967 if not (str and string.find(str, "[^%s]")) then
971 mod_query.exec_on_merr(mplex, ssh.." "..string.shell_safe(str))
974 return mod_query.query(mplex, TR("SSH to:"), nil, handle_exec,
975 mod_query.make_completor(mod_query.complete_ssh),
985 function mod_query.man_completor(wedln, str)
986 local mc=ioncore.lookup_script("ion-completeman")
987 local icase=(mod_query.get().caseicompl and " -icase" or "")
990 mod_query.popen_completions(wedln, (mc..icase..mid.." -complete "
991 ..string.shell_safe(str)))
997 -- This query asks for a manual page to display. By default it runs the
998 -- \command{man} command in an \command{xterm} using \command{ion-runinxterm},
999 -- but it is possible to pass another program as the \var{prog} argument.
1000 function mod_query.query_man(mplex, prog)
1001 local dflt=ioncore.progname()
1002 mod_query.query_execwith(mplex, TR("Manual page (%s):", dflt),
1003 dflt, prog or ":man",
1004 mod_query.man_completor, "man",
1005 true --[[ no quoting ]])
1012 -- Lua code execution {{{
1015 function mod_query.create_run_env(mplex)
1016 local origenv=getfenv()
1017 local meta={__index=origenv, __newindex=origenv}
1020 _sub=mplex:current(),
1023 setmetatable(env, meta)
1027 function mod_query.do_handle_lua(mplex, env, code)
1029 local function collect_print(...)
1034 tmp=tmp..tostring(arg[i])..(i==l and "\n" or "\t")
1036 print_res=(print_res and print_res..tmp or tmp)
1039 local f, err=loadstring(code)
1041 mod_query.warn(mplex, err)
1045 env.print=collect_print
1048 err=collect_errors(f)
1050 mod_query.warn(mplex, err)
1051 elseif print_res then
1052 mod_query.message(mplex, print_res)
1056 local function getindex(t)
1057 local mt=getmetatable(t)
1058 if mt then return mt.__index end
1062 function mod_query.do_complete_lua(env, str)
1063 -- Get the variable to complete, including containing tables.
1064 -- This will also match string concatenations and such because
1065 -- Lua's regexps don't support optional subexpressions, but we
1066 -- handle them in the next step.
1069 local _, _, tocomp=string.find(str, "([%w_.:]*)$")
1071 -- Descend into tables
1072 if tocomp and string.len(tocomp)>=1 then
1073 for t in string.gmatch(tocomp, "([^.:]*)[.:]") do
1075 if string.len(t)==0 then
1078 if type(comptab[t])=="table" then
1080 elseif type(comptab[t])=="userdata" then
1081 comptab=getindex(comptab[t])
1090 if not comptab then return {} end
1094 -- Get the actual variable to complete without containing tables
1095 _, _, compl.common_beg, tocomp=string.find(str, "(.-)([%w_]*)$")
1097 local l=string.len(tocomp)
1102 if type(tab) == "table" then
1103 for k in pairs(tab) do
1104 if type(k)=="string" then
1105 if string.sub(k, 1, l)==tocomp then
1106 table.insert(compl, k)
1112 -- We only want to display full list of functions for objects, not
1113 -- the tables representing the classes.
1114 --if not metas then break end
1118 if not tab or seen[tab] then break end
1121 -- If there was only one completion and it is a string or function,
1122 -- concatenate it with "." or "(", respectively.
1124 if type(comptab[compl[1]])=="table" then
1125 compl[1]=compl[1] .. "."
1126 elseif type(comptab[compl[1]])=="function" then
1127 compl[1]=compl[1] .. "("
1136 -- This query asks for Lua code to execute. It sets the variable '\var{\_}'
1137 -- in the local environment of the string to point to the mplex where the
1138 -- query was created. It also sets the table \var{arg} in the local
1139 -- environment to \code{\{_, _:current()\}}.
1140 function mod_query.query_lua(mplex)
1141 local env=mod_query.create_run_env(mplex)
1143 local function complete(cp, code)
1144 cp:set_completions(mod_query.do_complete_lua(env, code))
1147 local function handler(mplex, code)
1148 return mod_query.do_handle_lua(mplex, env, code)
1151 mod_query.query(mplex, TR("Lua code:"), nil, handler, complete, "lua")
1160 -- This query can be used to create a query of a defined menu.
1161 function mod_query.query_menu(mplex, sub, themenu, prompt)
1162 if type(sub)=="string" then
1163 -- Backwards compat. shift
1169 local menu=ioncore.evalmenu(themenu, mplex, sub)
1170 local menuname=(type(themenu)=="string" and themenu or "?")
1173 mod_query.warn(mplex, TR("Unknown menu %s.", tostring(themenu)))
1178 prompt=menuname..":"
1183 local function xform_name(n, is_submenu)
1184 return string.lower(string.gsub(n, "[-/%s]+", "-"))
1187 local function xform_menu(t, m, p)
1188 for _, v in ipairs(m) do
1190 local is_submenu=v.submenu_fn
1191 local n=p..xform_name(v.name)
1193 while t[n] or t[n..'/'] do
1203 if is_submenu and not v.noautoexpand then
1204 local sm=v.submenu_fn()
1206 xform_menu(t, sm, n)
1208 ioncore.warn_traced(TR("Missing submenu ")
1217 local ntab=xform_menu({}, menu, "")
1219 local function complete(str)
1220 return mod_query.complete_keys(ntab, str, true, true)
1223 local function handle(mplex, str)
1227 local err=collect_errors(function()
1231 mod_query.warn(mplex, err)
1233 elseif e.submenu_fn then
1234 mod_query.query_menu(mplex, e.submenu_fn(),
1238 mod_query.warn(mplex, TR("No entry '%s'", str))
1242 mod_query.query(mplex, prompt, nil, handle,
1243 mod_query.make_completor(complete), "menu."..menuname)
1249 -- Miscellaneous {{{
1253 -- Display an "About Ion" message in \var{mplex}.
1254 function mod_query.show_about_ion(mplex)
1255 mod_query.message(mplex, ioncore.aboutmsg())
1260 -- Show information about a region tree
1261 function mod_query.show_tree(mplex, reg, max_depth)
1262 local function indent(s)
1264 return i..string.gsub(s, "\n", "\n"..i)
1267 local function get_info(reg, indent, d)
1269 return (indent .. "No region")
1272 local function n(s) return (s or "") end
1274 local s=string.format("%s%s \"%s\"", indent, obj_typename(reg),
1276 indent = indent .. " "
1277 if obj_is(reg, "WClientWin") then
1278 local i=reg:get_ident()
1279 s=s .. TR("\n%sClass: %s\n%sRole: %s\n%sInstance: %s\n%sXID: 0x%x",
1282 indent, n(i.instance),
1286 if (not max_depth or max_depth > d) and reg.managed_i then
1288 reg:managed_i(function(sub)
1290 s=s .. "\n" .. indent .. "---"
1293 s=s .. "\n" .. get_info(sub, indent, d+1)
1301 mod_query.message(mplex, get_info(reg, "", 0))
1307 dopath('mod_query_chdir')
1309 -- Mark ourselves loaded.
1310 package.loaded["mod_query"]=true
1313 -- Load configuration file
1314 dopath('cfg_query', true)