]> git.decadent.org.uk Git - ion3.git/blobdiff - mod_query/mod_query.lua
Imported Upstream version 20090110
[ion3.git] / mod_query / mod_query.lua
index 0f51738edceb6c7dde1058f5354a01cdf09cb18a..bd3414bd95764cd5c6d62147d9181e4af0e44d18 100644 (file)
@@ -1,12 +1,9 @@
 --
 -- ion/query/mod_query.lua -- Some common queries for Ion
 -- 
--- Copyright (c) Tuomo Valkonen 2004-2006.
+-- Copyright (c) Tuomo Valkonen 2004-2009.
 -- 
--- Ion is free software; you can redistribute it and/or modify it under
--- the terms of the GNU Lesser General Public License as published by
--- the Free Software Foundation; either version 2.1 of the License, or
--- (at your option) any later version.
+-- See the included file LICENSE for details.
 --
 
 
@@ -31,11 +28,17 @@ local DIE_TIMEOUT_NO_ERRORCODE=2 -- 2 seconds
 -- Generic helper functions {{{
 
 
-function mod_query.make_completor(completefn)
-    local function completor(cp, str, point)
-        cp:set_completions(completefn(str, point))
-    end
-    return completor
+--DOC
+-- Display an error message box in the multiplexer \var{mplex}.
+function mod_query.warn(mplex, str)
+    ioncore.unsqueeze(mod_query.do_warn(mplex, str))
+end
+
+
+--DOC
+-- Display a message in \var{mplex}.
+function mod_query.message(mplex, str)
+    ioncore.unsqueeze(mod_query.do_message(mplex, str))
 end
 
 
@@ -58,18 +61,28 @@ function mod_query.query(mplex, prompt, initvalue, handler, completor,
     local function cycle(wedln)
         wedln:complete('next', 'normal')
     end
+    local function bcycle(wedln)
+        wedln:complete('prev', 'normal')
+    end
 
-    -- Check that no other queries are open in the mplex.
-    local l=mplex:managed_list()
-    for i, r in pairs(l) do
-        if obj_is(r, "WEdln") then
-            return
-        end
+    -- Check that no other queries or message boxes are open in the mplex.
+    local ok=mplex:managed_i(function(r)
+                                 return not (obj_is(r, "WEdln") or
+                                             obj_is(r, "WMessage"))
+                             end)
+    if not ok then
+        return
     end
+    
     local wedln=mod_query.do_query(mplex, prompt, initvalue, 
-                                   handle_it, completor, cycle)
-    if context then
-        wedln:set_context(context)
+                                   handle_it, completor, cycle, bcycle)
+                                   
+    if wedln then
+        ioncore.unsqueeze(wedln)
+        
+        if context then
+            wedln:set_context(context)
+        end
     end
     
     return wedln
@@ -175,6 +188,20 @@ function mod_query.file_completor(wedln, str)
 end
 
 
+function mod_query.get_initdir(mplex)
+    --if mod_query.last_dir then
+    --    return mod_query.last_dir
+    --end
+    local wd=(ioncore.get_dir_for(mplex) or os.getenv("PWD"))
+    if wd==nil then
+        wd="/"
+    elseif string.sub(wd, -1)~="/" then
+        wd=wd .. "/"
+    end
+    return wd
+end
+
+
 function mod_query.query_execfile(mplex, prompt, prog)
     assert(prog~=nil)
     local function handle_execwith(mplex, str)
@@ -200,39 +227,10 @@ function mod_query.query_execwith(mplex, prompt, dflt, prog, completor,
 end
 
 
-function mod_query.get_initdir(mplex)
-    --if mod_query.last_dir then
-    --    return mod_query.last_dir
-    --end
-    local wd=(ioncore.get_dir_for(mplex) or os.getenv("PWD"))
-    if wd==nil then
-        wd="/"
-    elseif string.sub(wd, -1)~="/" then
-        wd=wd .. "/"
-    end
-    return wd
-end
-
+-- }}}
 
-local MAXDEPTH=10
-
-
-function mod_query.complete_from_list(list, str)
-    local results={}
-    local len=string.len(str)
-    if len==0 then
-        results=list
-    else
-        for _, m in pairs(list) do
-            if string.sub(m, 1, len)==str then
-                table.insert(results, m)
-            end
-        end
-    end
-    
-    return results
-end    
 
+-- Completion helpers {{{
 
 local pipes={}
 
@@ -241,13 +239,14 @@ mod_query.COLLECT_THRESHOLD=2000
 --DOC
 -- This function can be used to read completions from an external source.
 -- The parameter \var{cp} is the completion proxy to be used,
--- and the string \var{cmd} the shell command to be executed. To its stdout, 
--- the command should on the first line write the \var{common_beg} 
+-- and the string \var{cmd} the shell command to be executed, in the directory
+-- \var{wd}. 
+-- To its stdout, the command should on the first line write the \var{common_beg}
 -- parameter of \fnref{WComplProxy.set_completions} (which \var{fn} maybe used
 -- to override) and a single actual completion on each of the successive lines.
 -- The function \var{reshnd} may be used to override a result table
 -- building routine.
-function mod_query.popen_completions(cp, cmd, fn, reshnd)
+function mod_query.popen_completions(cp, cmd, fn, reshnd, wd)
     
     local pst={cp=cp, maybe_stalled=0}
     
@@ -320,8 +319,90 @@ function mod_query.popen_completions(cp, cmd, fn, reshnd)
     
     if not found_clean then
         pipes[rcv]=pst
-        ioncore.popen_bgread(cmd, coroutine.wrap(rcv))
+        ioncore.popen_bgread(cmd, coroutine.wrap(rcv), nil, wd)
+    end
+end
+
+
+local function mk_completion_test(str, sub_ok, casei_ok)
+    local settings=mod_query.get()
+    
+    if not str then
+        return function(s) return true end
+    end
+    
+    local function mk(str, sub_ok)
+        if sub_ok then
+            return function(s) return string.find(s, str, 1, true) end
+        else
+            local len=string.len(str)
+            return function(s) return string.sub(s, 1, len)==str end
+        end
+    end
+    
+    casei_ok=(casei_ok and settings.caseicompl)
+    sub_ok=(sub_ok and settings.substrcompl)
+    
+    if not casei_ok then
+        return mk(str, sub_ok)
+    else
+        local fn=mk(string.lower(str), sub_ok)
+        return function(s) return fn(string.lower(s)) end
+    end
+end
+
+
+local function mk_completion_add(entries, str, sub_ok, casei_ok)
+    local tst=mk_completion_test(str, sub_ok, casei_ok)
+    
+    return function(s) 
+               if s and tst(s) then
+                   table.insert(entries, s)
+               end
+           end
+end
+
+
+function mod_query.complete_keys(list, str, sub_ok, casei_ok)
+    local results={}
+    local test_add=mk_completion_add(results, str, sub_ok, casei_ok)
+    
+    for m, _ in pairs(list) do
+        test_add(m)
+    end
+
+    return results
+end    
+
+
+function mod_query.complete_name(str, iter)
+    local sub_ok_first=true
+    local casei_ok=true
+    local entries={}
+    local tst_add=mk_completion_add(entries, str, sub_ok_first, casei_ok)
+    
+    iter(function(reg)
+             tst_add(reg:name())
+             return true
+         end)
+    
+    if #entries==0 and not sub_ok_first then
+        local tst_add2=mk_completion_add(entries, str, true, casei_ok)
+        iter(function(reg)
+                 tst_add2(reg:name())
+                 return true
+             end)
+    end
+    
+    return entries
+end
+
+
+function mod_query.make_completor(completefn)
+    local function completor(cp, str, point)
+        cp:set_completions(completefn(str, point))
     end
+    return completor
 end
 
 
@@ -340,36 +421,24 @@ function mod_query.call_warn(mplex, fn)
 end
 
 
-function mod_query.complete_name(str, list)
-    local entries={}
-    local l=string.len(str)
-    for i, reg in pairs(list) do
-        local nm=reg:name()
-        if nm and string.sub(nm, 1, l)==str then
-            table.insert(entries, nm)
-        end
-    end
-    if #entries==0 then
-        for i, reg in pairs(list) do
-            local nm=reg:name()
-            if nm and string.find(nm, str, 1, true) then
-                table.insert(entries, nm)
-            end
-        end
-    end
-    return entries
-end
-
 function mod_query.complete_clientwin(str)
-    return mod_query.complete_name(str, ioncore.clientwin_list())
+    return mod_query.complete_name(str, ioncore.clientwin_i)
 end
 
+
 function mod_query.complete_workspace(str)
-    return mod_query.complete_name(str, ioncore.region_list("WGroupWS"))
+    local function iter(fn) 
+        return ioncore.region_i(function(obj)
+                                    return (not obj_is(obj, "WGroupWS")
+                                            or fn(obj))
+                                end)
+    end
+    return mod_query.complete_name(str, iter)
 end
 
+
 function mod_query.complete_region(str)
-    return mod_query.complete_name(str, ioncore.region_list())
+    return mod_query.complete_name(str, ioncore.region_i)
 end
 
 
@@ -383,6 +452,7 @@ function mod_query.gotoclient_handler(frame, str)
     end
 end
 
+
 function mod_query.attachclient_handler(frame, str)
     local cwin=ioncore.lookup_clientwin(str)
     
@@ -391,24 +461,12 @@ function mod_query.attachclient_handler(frame, str)
         return
     end
     
-    local reg=cwin:manager()
-    local attach
+    local reg=cwin:groupleader_of()
     
-    if not obj_is(reg, "WGroupCW") then
-        reg = cwin
-        attach = function()
-                     frame:attach_new {
-                         type = "WGroupCW", 
-                         switchto = true,
-                         managed = {{ reg = cwin, bottom = true }}
-                     }
-                 end
-    else
-        attach = function()
-                     frame:attach(reg, { switchto = true })
-                 end
+    local function attach()
+        frame:attach(reg, { switchto = true })
     end
-        
+    
     if frame:rootwin_of()~=reg:rootwin_of() then
         mod_query.warn(frame, TR("Cannot attach: different root windows."))
     elseif reg:manager()==frame then
@@ -423,25 +481,47 @@ function mod_query.workspace_handler(mplex, name)
     local ws=ioncore.lookup_region(name, "WGroupWS")
     if ws then
         ws:goto()
-        return
-    end
+    else
+        local function create_handler(mplex_, layout)
+            if not layout or layout=="" then
+                layout="default"
+            end
+            
+            if not ioncore.getlayout(layout) then
+                mod_query.warn(mplex_, TR("Unknown layout"))
+            else
+                local scr=mplex:screen_of()
+                
+                local function mkws()
+                    local tmpl={
+                        name=(name~="" and name),
+                        switchto=true
+                    } 
+                    if not ioncore.create_ws(scr, tmpl, layout) then
+                        error(TR("Unknown error"))
+                    end
+                end
 
-    local scr=mplex:screen_of()
-    
-    local function mkws()
-       if not ioncore.create_ws(scr, {name=name}) then
-            error(TR("Unknown error"))
+                mod_query.call_warn(mplex, mkws)
+            end
         end
-    end
 
-    mod_query.call_warn(mplex, mkws)
+        local function compl_layout(str)
+            local los=ioncore.getlayout(nil, true)
+            return mod_query.complete_keys(los, str, true, true)
+        end
+        
+        mod_query.query(mplex, TR("New workspace layout (default):"), nil,
+                        create_handler, mod_query.make_completor(compl_layout),
+                        "workspacelayout")
+    end
 end
 
 
 --DOC
--- This query asks for the name of a client window and attaches
--- it to the frame the query was opened in. It uses the completion
--- function \fnref{ioncore.complete_clientwin}.
+-- This query asks for the name of a client window and switches
+-- focus to the one entered. It uses the completion function
+-- \fnref{ioncore.complete_clientwin}.
 function mod_query.query_gotoclient(mplex)
     mod_query.query(mplex, TR("Go to window:"), nil,
                     mod_query.gotoclient_handler,
@@ -450,9 +530,9 @@ function mod_query.query_gotoclient(mplex)
 end
 
 --DOC
--- This query asks for the name of a client window and switches
--- focus to the one entered. It uses the completion function
--- \fnref{ioncore.complete_clientwin}.
+-- This query asks for the name of a client window and attaches
+-- it to the frame the query was opened in. It uses the completion
+-- function \fnref{ioncore.complete_clientwin}.
 function mod_query.query_attachclient(mplex)
     mod_query.query(mplex, TR("Attach window:"), nil,
                     mod_query.attachclient_handler, 
@@ -504,10 +584,20 @@ end
 
 
 --DOC
--- This function asks for a name new for the workspace on which the
--- query resides.
-function mod_query.query_renameworkspace(mplex)
-    local ws=ioncore.find_manager(mplex, "WGroupWS")
+-- This function asks for a name new for the workspace \var{ws},
+-- or the one on which \var{mplex} resides, if it is not set.
+-- If \var{mplex} is not set, one is looked for.
+function mod_query.query_renameworkspace(mplex, ws)
+    if not mplex then
+        assert(ws)
+        mplex=ioncore.find_manager(ws, "WMPlex")
+    elseif not ws then
+        assert(mplex)
+        ws=ioncore.find_manager(mplex, "WGroupWS")
+    end
+    
+    assert(mplex and ws)
+    
     mod_query.query(mplex, TR("Workspace name:"), ws:name(),
                     function(mplex, str) ws:set_name(str) end,
                     nil, "framename")
@@ -661,7 +751,7 @@ local function find_point(strs, point)
 end
 
 
-function mod_query.exec_completor(wedln, str, point)
+function mod_query.exec_completor_(wd, wedln, str, point)
     local parts=break_cmdline(str)
     local complidx=find_point(parts, point+1)
     
@@ -719,11 +809,16 @@ function mod_query.exec_completor(wedln, str, point)
     if ic then
         mod_query.popen_completions(wedln,
                                    ic..wp..string.shell_safe(s_compl),
-                                   set_fn, filter_fn)
+                                   set_fn, filter_fn, wd)
     end
 end
 
 
+function mod_query.exec_completor(...)
+    mod_query.exec_completor_(nil, ...)
+end
+
+
 local cmd_overrides={}
 
 
@@ -753,9 +848,12 @@ end
 -- \file{ion-runinxterm}. Two colons ('::') will ask you to press 
 -- enter after the command has finished.
 function mod_query.query_exec(mplex)
-    mod_query.query(mplex, TR("Run:"), nil, mod_query.exec_handler, 
-                    mod_query.exec_completor,
-                    "run")
+    local function compl(...)
+        local wd=ioncore.get_dir_for(mplex)
+        mod_query.exec_completor_(wd, ...)
+    end
+    mod_query.query(mplex, TR("Run:"), nil, mod_query.exec_handler,
+                    compl, "run")
 end
 
 
@@ -766,6 +864,8 @@ end
 
 
 mod_query.known_hosts={}
+mod_query.hostnicks={}
+mod_query.ssh_completions={}
 
 
 function mod_query.get_known_hosts(mplex)
@@ -789,8 +889,6 @@ function mod_query.get_known_hosts(mplex)
 end
 
 
-mod_query.hostnicks={}
-
 function mod_query.get_hostnicks(mplex)
     mod_query.hostnicks={}
     local f
@@ -800,7 +898,7 @@ function mod_query.get_hostnicks(mplex)
     if h then
         f=io.open(h.."/.ssh/config")
     end
-    if not f then 
+    if not f then
         warn(TR("Failed to open ~/.ssh/config"))
         return
     end
@@ -813,7 +911,7 @@ function mod_query.get_hostnicks(mplex)
                 patterns=pat
             elseif string.find(substr, "^[nN][aA][mM][eE]")
                 and patterns then
-                for s in string.gfind(patterns, "%S+") do
+                for s in string.gmatch(patterns, "%S+") do
                     if not string.find(s, "[*?]") then
                         table.insert(mod_query.hostnicks, s)
                     end
@@ -837,21 +935,10 @@ function mod_query.complete_ssh(str)
     end
     
     local res = {}
-    
-    if string.len(host)==0 then
-        if string.len(user)==0 then
-            return mod_query.ssh_completions
-        end
-        
-        for _, v in ipairs(mod_query.ssh_completions) do
-            table.insert(res, user .. v)
-        end
-        return res
-    end
+    local tst = mk_completion_test(host, true, true)
     
     for _, v in ipairs(mod_query.ssh_completions) do
-        local s, e=string.find(v, host, 1, true)
-        if s==1 and e>=1 then
+        if tst(v) then
             table.insert(res, user .. v)
         end
     end
@@ -859,7 +946,6 @@ function mod_query.complete_ssh(str)
     return res
 end
 
-mod_query.ssh_completions={}
 
 --DOC
 -- This query asks for a host to connect to with SSH. 
@@ -898,8 +984,10 @@ end
 
 function mod_query.man_completor(wedln, str)
     local mc=ioncore.lookup_script("ion-completeman")
+    local icase=(mod_query.get().caseicompl and " -icase" or "")
+    local mid=""
     if mc then
-        mod_query.popen_completions(wedln, (mc.." -complete "
+        mod_query.popen_completions(wedln, (mc..icase..mid.." -complete "
                                             ..string.shell_safe(str)))
     end
 end
@@ -940,6 +1028,7 @@ function mod_query.do_handle_lua(mplex, env, code)
     local print_res
     local function collect_print(...)
         local tmp=""
+        local arg={...}
         local l=#arg
         for i=1,l do
             tmp=tmp..tostring(arg[i])..(i==l and "\n" or "\t")
@@ -981,7 +1070,7 @@ function mod_query.do_complete_lua(env, str)
     
     -- Descend into tables
     if tocomp and string.len(tocomp)>=1 then
-        for t in string.gfind(tocomp, "([^.:]*)[.:]") do
+        for t in string.gmatch(tocomp, "([^.:]*)[.:]") do
             metas=false
             if string.len(t)==0 then
                 comptab=env;
@@ -1069,9 +1158,15 @@ end
 
 --DOC
 -- This query can be used to create a query of a defined menu.
-function mod_query.query_menu(mplex, themenu, prompt)
-    local _sub=mplex:current()
-    local menu=ioncore.evalmenu(themenu, {mplex, _sub})
+function mod_query.query_menu(mplex, sub, themenu, prompt)
+    if type(sub)=="string" then
+        -- Backwards compat. shift
+        prompt=themenu
+        themenu=sub
+        sub=nil
+    end
+    
+    local menu=ioncore.evalmenu(themenu, mplex, sub)
     local menuname=(type(themenu)=="string" and themenu or "?")
     
     if not menu then
@@ -1086,19 +1181,25 @@ function mod_query.query_menu(mplex, themenu, prompt)
     end
 
     local function xform_name(n, is_submenu)
-        return (string.lower(string.gsub(n, "[-%s]+", "-"))
-                ..(is_submenu and "/" or ""))
+        return string.lower(string.gsub(n, "[-/%s]+", "-"))
     end
 
     local function xform_menu(t, m, p)
         for _, v in ipairs(m) do
             if v.name then
                 local is_submenu=v.submenu_fn
-                local n=p..xform_name(v.name, is_submenu)
-                while t[n] do
+                local n=p..xform_name(v.name)
+                
+                while t[n] or t[n..'/'] do
                     n=n.."'"
                 end
+                
+                if is_submenu then
+                    n=n..'/'
+                end
+                
                 t[n]=v
+                
                 if is_submenu and not v.noautoexpand then
                     local sm=v.submenu_fn()
                     if sm then
@@ -1116,13 +1217,7 @@ function mod_query.query_menu(mplex, themenu, prompt)
     local ntab=xform_menu({}, menu, "")
     
     local function complete(str)
-        local results={}
-        for s, e in pairs(ntab) do
-            if string.find(s, str, 1, true) then
-                table.insert(results, s)
-            end
-        end
-        return results
+        return mod_query.complete_keys(ntab, str, true, true)
     end
     
     local function handle(mplex, str)
@@ -1188,14 +1283,16 @@ function mod_query.show_tree(mplex, reg, max_depth)
                       indent, reg:xid())
         end
         
-        if (not max_depth or max_depth > d) and reg.managed_list then
-            local mgd=reg:managed_list()
-            if #mgd > 0 then
-                s=s .. "\n" .. indent .. "---"
-                for k, v in pairs(mgd) do
-                    s=s .. "\n" .. get_info(v, indent, d+1)
-                end
-            end
+        if (not max_depth or max_depth > d) and reg.managed_i then
+            local first=true
+            reg:managed_i(function(sub)
+                              if first then
+                                  s=s .. "\n" .. indent .. "---"
+                                  first=false
+                              end
+                              s=s .. "\n" .. get_info(sub, indent, d+1)
+                              return true
+                          end)
         end
         
         return s