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(cp
, str
)
183 local ic
=ioncore
.lookup_script("ion-completefile")
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
194 local wd
=(ioncore
.get_dir_for(mplex
) or os
.getenv("PWD"))
197 elseif string.sub(wd
, -1)~="/" then
204 function mod_query
.query_execfile(mplex
, prompt
, prog
)
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
,
215 function mod_query
.query_execwith(mplex
, prompt
, dflt
, prog
, completor
,
217 local function handle_execwith(frame
, str
)
218 if not str
or str
=="" then
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
,
232 -- Completion helpers {{{
236 mod_query
.COLLECT_THRESHOLD
=2000
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
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}
255 reshnd
= function(rs
, a
)
256 if not rs
.common_beg
then
264 local function rcv(str
)
271 if pst
.maybe_stalled
>=2 then
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",
290 if lines
>mod_query
.COLLECT_THRESHOLD
then
295 str
=coroutine
.yield()
298 if not results
.common_beg
then
299 results
.common_beg
=beg
302 (fn
or WComplProxy
.set_completions
)(cp
, results
)
310 local found_clean
=false
312 for k
, v
in pairs(pipes
) do
314 if v
.maybe_stalled
<2 then
315 v
.maybe_stalled
=v
.maybe_stalled
+1
321 if not found_clean
then
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()
332 return function(s
) return true end
335 local function mk(str
, sub_ok
)
337 return function(s
) return string.find(s
, str
, 1, true) end
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
)
348 return mk(str
, sub_ok
)
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
)
361 table.insert(entries
, s
)
367 function mod_query
.complete_keys(list
, str
, sub_ok
, casei_ok
)
369 local test_add
=mk_completion_add(results
, str
, sub_ok
, casei_ok
)
371 for m
, _
in pairs(list
) do
379 function mod_query
.complete_name(str
, iter
)
380 local sub_ok_first
=true
383 local tst_add
=mk_completion_add(entries
, str
, sub_ok_first
, casei_ok
)
390 if #entries
==0 and not sub_ok_first
then
391 local tst_add2
=mk_completion_add(entries
, str
, true, casei_ok
)
402 function mod_query
.make_completor(completefn
)
403 local function completor(cp
, str
, point
)
404 cp
:set_completions(completefn(str
, point
))
413 -- Simple queries for internal actions {{{
416 function mod_query
.call_warn(mplex
, fn
)
417 local err
= collect_errors(fn
)
419 mod_query
.warn(mplex
, 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")
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
)
450 mod_query
.warn(frame
, TR("Could not find client window %s.", str
))
457 function mod_query
.attachclient_handler(frame
, str
)
458 local cwin
=ioncore
.lookup_clientwin(str
)
461 mod_query
.warn(frame
, TR("Could not find client window %s.", str
))
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
476 mod_query
.call_warn(frame
, attach
)
481 function mod_query
.workspace_handler(mplex
, name
)
482 local ws
=ioncore
.lookup_region(name
, "WGroupWS")
486 local function create_handler(mplex_
, layout
)
487 if not layout
or layout
=="" then
491 if not ioncore
.getlayout(layout
) then
492 mod_query
.warn(mplex_
, TR("Unknown layout"))
494 local scr
=mplex
:screen_of()
496 local function mkws()
498 name
=(name
~="" and name
),
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
),
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
),
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
),
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
),
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)?"),
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
)
578 -- This function asks for a name new for the frame where the query
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,
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
)
594 mplex
=ioncore
.find_manager(ws
, "WMPlex")
597 ws
=ioncore
.find_manager(mplex
, "WGroupWS")
602 mod_query
.query(mplex
, TR("Workspace name:"), ws
:name(),
603 function(mplex
, str
) ws
:set_name(str
) end,
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")
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
646 local function ins(str
)
648 if string.find(res
[n
], "^%s+$") then
649 table.insert(res
, str
)
655 local function ins_space(str
)
659 table.insert(res
, "")
662 if isspace(res
[n
]) then
665 table.insert(res
, str
)
670 -- Handle terminal startup syntax
671 st
, en
, beg
, ch
, rest
=string.find(str
, "^(%s*)(:+)(.*)")
673 if string.len(beg
)>0 then
682 st
, en
, beg
, rest
, ch
=string.find(str
, "^(.-)(([%s'\"\\|])(.*))")
694 st
, en
, beg
, rest
=string.find(str
, "^(\\.)(.*)")
696 st
, en
, beg
, rest
=string.find(str
, "^(\".-[^\\]\")(.*)")
699 st
, en
, beg
, rest
=string.find(str
, "^(\"\")(.*)")
702 st
, en
, beg
, rest
=string.find(str
, "^('.-')(.*)")
710 st
, en
, beg
, rest
=string.find(str
, "^.(%s*)(.*)")
731 local function unquote(str
)
732 str
=string.gsub(str
, "^['\"]", "")
733 str
=string.gsub(str
, "([^\\])['\"]", "%1")
734 str
=string.gsub(str
, "\\(.)", "%1")
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
)
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
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
]
778 if complidx
==1 or (complidx
==2 and isspace(parts
[1])) then
780 elseif string.find(parts
[1], "^:+$") then
783 elseif string.find(parts
[2], "^%s*$") then
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
809 local ic
=ioncore
.lookup_script("ion-completefile")
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
={}
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
))
840 mod_query
.exec_on_merr(mplex
, cmdline
)
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
,
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
={}
875 local h
=os
.getenv("HOME")
877 f
=io
.open(h
.."/.ssh/known_hosts")
880 warn(TR("Failed to open ~/.ssh/known_hosts"))
883 for l
in f
:lines() do
884 local st
, en
, hostname
=string.find(l
, "^([^%s,]+)")
886 table.insert(mod_query
.known_hosts
, hostname
)
893 function mod_query
.get_hostnicks(mplex
)
894 mod_query
.hostnicks
={}
896 local substr
, pat
, patterns
897 local h
=os
.getenv("HOME")
900 f
=io
.open(h
.."/.ssh/config")
903 warn(TR("Failed to open ~/.ssh/config"))
907 for l
in f
:lines() do
908 _
, _
, substr
=string.find(l
, "^%s*[hH][oO][sS][tT](.*)")
910 _
, _
, pat
=string.find(substr
, "^%s*[=%s]%s*(%S.*)")
913 elseif string.find(substr
, "^[nN][aA][mM][eE]")
915 for s
in string.gmatch(patterns
, "%S+") do
916 if not string.find(s
, "[*?]") then
917 table.insert(mod_query
.hostnicks
, s
)
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
= ""
939 local tst
= mk_completion_test(host
, true, true)
941 for _
, v
in ipairs(mod_query
.ssh_completions
) do
943 table.insert(res
, user
.. v
)
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
)
967 local function handle_exec(mplex
, str
)
968 if not (str
and string.find(str
, "[^%s]")) then
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
),
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 "")
991 mod_query
.popen_completions(cp
, (mc
..icase
..mid
.." -complete "
992 ..string.shell_safe(str
)))
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 ]])
1013 -- Lua code execution {{{
1016 function mod_query
.create_run_env(mplex
)
1018 if _ENV
then origenv
=_ENV
else origenv
=getfenv() end
1019 local meta
={__index
=origenv
, __newindex
=origenv
}
1022 _sub
=mplex
:current(),
1025 setmetatable(env
, meta
)
1029 function mod_query
.do_handle_lua(mplex
, env
, code
)
1031 local function collect_print(...)
1036 tmp
=tmp
..tostring(arg
[i
])..(i
==l
and "\n" or "\t")
1038 print_res
=(print_res
and print_res
..tmp
or tmp
)
1043 env
.print=collect_print
1044 local f
, err
=load(code
,nil, nil, env
)
1046 mod_query
.warn(mplex
, err
)
1049 err
=collect_errors(f
)
1051 local f
, err
=loadstring(code
)
1053 mod_query
.warn(mplex
, err
)
1056 env
.print=collect_print
1058 err
=collect_errors(f
)
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
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.
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
1086 if string.len(t
)==0 then
1089 if type(comptab
[t
])=="table" then
1091 elseif type(comptab
[t
])=="userdata" then
1092 comptab
=getindex(comptab
[t
])
1101 if not comptab
then return {} end
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
)
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
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.
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] .. "("
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")
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
1180 local menu
=ioncore
.evalmenu(themenu
, mplex
, sub
)
1181 local menuname
=(type(themenu
)=="string" and themenu
or "?")
1184 mod_query
.warn(mplex
, TR("Unknown menu %s.", tostring(themenu
)))
1189 prompt
=menuname
..":"
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
1201 local is_submenu
=v
.submenu_fn
1202 local n
=p
..xform_name(v
.name
)
1204 while t
[n
] or t
[n
..'/'] do
1214 if is_submenu
and not v
.noautoexpand
then
1215 local sm
=v
.submenu_fn()
1217 xform_menu(t
, sm
, n
)
1219 ioncore
.warn_traced(TR("Missing submenu ")
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
)
1238 local err
=collect_errors(function()
1242 mod_query
.warn(mplex
, err
)
1244 elseif e
.submenu_fn
then
1245 mod_query
.query_menu(mplex
, e
.submenu_fn(),
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
)
1260 -- Miscellaneous {{{
1264 -- Display an "About Ion" message in \var{mplex}.
1265 function mod_query
.show_about_ion(mplex
)
1266 mod_query
.message(mplex
, ioncore
.aboutmsg())
1271 -- Show information about a region tree
1272 function mod_query
.show_tree(mplex
, reg
, max_depth
)
1273 local function indent(s
)
1275 return i
..string.gsub(s
, "\n", "\n"..i
)
1278 local function get_info(reg
, indent
, d
)
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
),
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",
1293 indent
, n(i
.instance
),
1297 if (not max_depth
or max_depth
> d
) and reg
.managed_i
then
1299 reg
:managed_i(function(sub
)
1301 s
=s
.. "\n" .. indent
.. "---"
1304 s
=s
.. "\n" .. get_info(sub
, indent
, d
+1)
1312 mod_query
.message(mplex
, get_info(reg
, "", 0))
1318 dopath('mod_query_chdir')
1320 -- Mark ourselves loaded.
1321 package
.loaded
["mod_query"]=true
1324 -- Load configuration file
1325 dopath('cfg_query', true)