summaryrefslogtreecommitdiff
path: root/lib/awful/completion.lua
diff options
context:
space:
mode:
Diffstat (limited to 'lib/awful/completion.lua')
-rw-r--r--lib/awful/completion.lua201
1 files changed, 201 insertions, 0 deletions
diff --git a/lib/awful/completion.lua b/lib/awful/completion.lua
new file mode 100644
index 0000000..3462ed1
--- /dev/null
+++ b/lib/awful/completion.lua
@@ -0,0 +1,201 @@
+---------------------------------------------------------------------------
+--- Completion module.
+--
+-- This module store a set of function using shell to complete commands name.
+--
+-- @author Julien Danjou <julien@danjou.info>
+-- @author Sébastien Gross <seb-awesome@chezwam.org>
+-- @copyright 2008 Julien Danjou, Sébastien Gross
+-- @module awful.completion
+---------------------------------------------------------------------------
+
+-- Grab environment we need
+local io = io
+local os = os
+local table = table
+local math = math
+local print = print
+local pairs = pairs
+local string = string
+
+local completion = {}
+
+-- mapping of command/completion function
+local bashcomp_funcs = {}
+local bashcomp_src = "/etc/bash_completion"
+
+--- Enable programmable bash completion in awful.completion.bash at the price of
+-- a slight overhead.
+-- @param src The bash completion source file, /etc/bash_completion by default.
+function completion.bashcomp_load(src)
+ if src then bashcomp_src = src end
+ local c, err = io.popen("/usr/bin/env bash -c 'source " .. bashcomp_src .. "; complete -p'")
+ if c then
+ while true do
+ local line = c:read("*line")
+ if not line then break end
+ -- if a bash function is used for completion, register it
+ if line:match(".* -F .*") then
+ bashcomp_funcs[line:gsub(".* (%S+)$","%1")] = line:gsub(".*-F +(%S+) .*$", "%1")
+ end
+ end
+ c:close()
+ else
+ print(err)
+ end
+end
+
+local function bash_escape(str)
+ str = str:gsub(" ", "\\ ")
+ str = str:gsub("%[", "\\[")
+ str = str:gsub("%]", "\\]")
+ str = str:gsub("%(", "\\(")
+ str = str:gsub("%)", "\\)")
+ return str
+end
+
+--- Use shell completion system to complete command and filename.
+-- @param command The command line.
+-- @param cur_pos The cursor position.
+-- @param ncomp The element number to complete.
+-- @param shell The shell to use for completion (bash (default) or zsh).
+-- @return The new command, the new cursor position, the table of all matches.
+function completion.shell(command, cur_pos, ncomp, shell)
+ local wstart = 1
+ local wend = 1
+ local words = {}
+ local cword_index = 0
+ local cword_start = 0
+ local cword_end = 0
+ local i = 1
+ local comptype = "file"
+
+ -- do nothing if we are on a letter, i.e. not at len + 1 or on a space
+ if cur_pos ~= #command + 1 and command:sub(cur_pos, cur_pos) ~= " " then
+ return command, cur_pos
+ elseif #command == 0 then
+ return command, cur_pos
+ end
+
+ while wend <= #command do
+ wend = command:find(" ", wstart)
+ if not wend then wend = #command + 1 end
+ table.insert(words, command:sub(wstart, wend - 1))
+ if cur_pos >= wstart and cur_pos <= wend + 1 then
+ cword_start = wstart
+ cword_end = wend
+ cword_index = i
+ end
+ wstart = wend + 1
+ i = i + 1
+ end
+
+ if cword_index == 1 and not string.find(words[cword_index], "/") then
+ comptype = "command"
+ end
+
+ local shell_cmd
+ if shell == "zsh" or (not shell and os.getenv("SHELL"):match("zsh$")) then
+ if comptype == "file" then
+ -- NOTE: ${~:-"..."} turns on GLOB_SUBST, useful for expansion of
+ -- "~/" ($HOME). ${:-"foo"} is the string "foo" as var.
+ shell_cmd = "/usr/bin/env zsh -c 'local -a res; res=( ${~:-"
+ .. string.format('%q', words[cword_index]) .. "}* ); "
+ .. "print -ln -- ${res[@]}'"
+ else
+ -- check commands, aliases, builtins, functions and reswords
+ shell_cmd = "/usr/bin/env zsh -c 'local -a res; "..
+ "res=( "..
+ "\"${(k)commands[@]}\" \"${(k)aliases[@]}\" \"${(k)builtins[@]}\" \"${(k)functions[@]}\" \"${(k)reswords[@]}\" "..
+ "${PWD}/*(:t)"..
+ "); "..
+ "print -ln -- ${(M)res[@]:#" .. string.format('%q', words[cword_index]) .. "*}'"
+ end
+ else
+ if bashcomp_funcs[words[1]] then
+ -- fairly complex command with inline bash script to get the possible completions
+ shell_cmd = "/usr/bin/env bash -c 'source " .. bashcomp_src .. "; " ..
+ "__print_completions() { for ((i=0;i<${#COMPREPLY[*]};i++)); do echo ${COMPREPLY[i]}; done }; " ..
+ "COMP_WORDS=(" .. command .."); COMP_LINE=\"" .. command .. "\"; " ..
+ "COMP_COUNT=" .. cur_pos .. "; COMP_CWORD=" .. cword_index-1 .. "; " ..
+ bashcomp_funcs[words[1]] .. "; __print_completions'"
+ else
+ shell_cmd = "/usr/bin/env bash -c 'compgen -A " .. comptype .. " "
+ .. string.format('%q', words[cword_index]) .. "'"
+ end
+ end
+ local c, err = io.popen(shell_cmd .. " | sort -u")
+ local output = {}
+ if c then
+ while true do
+ local line = c:read("*line")
+ if not line then break end
+ if os.execute("test -d " .. string.format('%q', line)) == 0 then
+ line = line .. "/"
+ end
+ table.insert(output, bash_escape(line))
+ end
+
+ c:close()
+ else
+ print(err)
+ end
+
+ -- no completion, return
+ if #output == 0 then
+ return command, cur_pos
+ end
+
+ -- cycle
+ while ncomp > #output do
+ ncomp = ncomp - #output
+ end
+
+ local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end)
+ cur_pos = cword_end + #output[ncomp] + 1
+
+ return str, cur_pos, output
+end
+
+--- Run a generic completion.
+-- For this function to run properly the awful.completion.keyword table should
+-- be fed up with all keywords. The completion is run against these keywords.
+-- @param text The current text the user had typed yet.
+-- @param cur_pos The current cursor position.
+-- @param ncomp The number of yet requested completion using current text.
+-- @param keywords The keywords table uised for completion.
+-- @return The new match, the new cursor position, the table of all matches.
+function completion.generic(text, cur_pos, ncomp, keywords) -- luacheck: no unused args
+ -- The keywords table may be empty
+ if #keywords == 0 then
+ return text, #text + 1
+ end
+
+ -- if no text had been typed yet, then we could start cycling around all
+ -- keywords with out filtering and move the cursor at the end of keyword
+ if text == nil or #text == 0 then
+ ncomp = math.fmod(ncomp - 1, #keywords) + 1
+ return keywords[ncomp], #keywords[ncomp] + 2
+ end
+
+ -- Filter out only keywords starting with text
+ local matches = {}
+ for _, x in pairs(keywords) do
+ if x:sub(1, #text) == text then
+ table.insert(matches, x)
+ end
+ end
+
+ -- if there are no matches just leave out with the current text and position
+ if #matches == 0 then
+ return text, #text + 1, matches
+ end
+
+ -- cycle around all matches
+ ncomp = math.fmod(ncomp - 1, #matches) + 1
+ return matches[ncomp], #matches[ncomp] + 1, matches
+end
+
+return completion
+
+-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80