2008-09-29 16:49:18 +02:00
|
|
|
---------------------------------------------------------------------------
|
2014-05-20 13:02:39 +02:00
|
|
|
--- Completion module.
|
|
|
|
--
|
|
|
|
-- This module store a set of function using shell to complete commands name.
|
|
|
|
--
|
2008-09-29 16:49:18 +02:00
|
|
|
-- @author Julien Danjou <julien@danjou.info>
|
2008-11-25 21:03:58 +01:00
|
|
|
-- @author Sébastien Gross <seb-awesome@chezwam.org>
|
|
|
|
-- @copyright 2008 Julien Danjou, Sébastien Gross
|
2014-05-20 13:02:39 +02:00
|
|
|
-- @module awful.completion
|
2008-09-29 16:49:18 +02:00
|
|
|
---------------------------------------------------------------------------
|
|
|
|
|
2017-07-02 16:14:53 +02:00
|
|
|
local gfs = require("gears.filesystem")
|
|
|
|
|
2008-09-29 16:49:18 +02:00
|
|
|
-- Grab environment we need
|
|
|
|
local io = io
|
2009-01-23 19:02:48 +01:00
|
|
|
local os = os
|
2008-09-29 16:49:18 +02:00
|
|
|
local table = table
|
2008-11-25 21:03:58 +01:00
|
|
|
local math = math
|
2008-12-11 22:14:07 +01:00
|
|
|
local print = print
|
2012-03-06 00:07:41 +01:00
|
|
|
local pairs = pairs
|
2012-05-06 23:35:07 +02:00
|
|
|
local string = string
|
2008-09-29 16:49:18 +02:00
|
|
|
|
2017-07-03 19:26:10 +02:00
|
|
|
local gears_debug = require("gears.debug")
|
|
|
|
|
2012-06-12 20:13:09 +02:00
|
|
|
local completion = {}
|
2008-09-29 16:49:18 +02:00
|
|
|
|
|
|
|
-- mapping of command/completion function
|
|
|
|
local bashcomp_funcs = {}
|
2009-01-05 18:07:06 +01:00
|
|
|
local bashcomp_src = "@SYSCONFDIR@/bash_completion"
|
2008-09-29 16:49:18 +02:00
|
|
|
|
|
|
|
--- Enable programmable bash completion in awful.completion.bash at the price of
|
2009-08-28 15:32:38 +02:00
|
|
|
-- a slight overhead.
|
2008-11-07 14:27:54 +01:00
|
|
|
-- @param src The bash completion source file, /etc/bash_completion by default.
|
2012-06-12 20:13:09 +02:00
|
|
|
function completion.bashcomp_load(src)
|
2008-09-29 16:49:18 +02:00
|
|
|
if src then bashcomp_src = src end
|
2008-12-11 22:14:07 +01:00
|
|
|
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
|
2008-09-29 16:49:18 +02:00
|
|
|
end
|
2008-12-11 22:14:07 +01:00
|
|
|
c:close()
|
|
|
|
else
|
|
|
|
print(err)
|
2008-09-29 16:49:18 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-12-25 18:57:39 +01:00
|
|
|
local function bash_escape(str)
|
2009-01-10 14:13:07 +01:00
|
|
|
str = str:gsub(" ", "\\ ")
|
|
|
|
str = str:gsub("%[", "\\[")
|
|
|
|
str = str:gsub("%]", "\\]")
|
|
|
|
str = str:gsub("%(", "\\(")
|
|
|
|
str = str:gsub("%)", "\\)")
|
|
|
|
return str
|
2008-12-25 18:57:39 +01:00
|
|
|
end
|
|
|
|
|
2017-07-03 19:26:10 +02:00
|
|
|
completion.default_shell = nil
|
|
|
|
|
2017-06-26 20:40:56 +02:00
|
|
|
--- Use shell completion system to complete commands and filenames.
|
|
|
|
-- @tparam string command The command line.
|
|
|
|
-- @tparam number cur_pos The cursor position.
|
|
|
|
-- @tparam number ncomp The element number to complete.
|
|
|
|
-- @tparam[opt=based on SHELL] string shell The shell to use for completion.
|
|
|
|
-- Supports "bash" and "zsh".
|
|
|
|
-- @treturn string The new command.
|
|
|
|
-- @treturn number The new cursor position.
|
|
|
|
-- @treturn table The table with all matches.
|
2012-06-12 20:13:09 +02:00
|
|
|
function completion.shell(command, cur_pos, ncomp, shell)
|
2008-09-29 16:49:18 +02:00
|
|
|
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"
|
|
|
|
|
2017-07-02 16:15:48 +02:00
|
|
|
local function str_starts(str, start)
|
|
|
|
return string.sub(str, 1, string.len(start)) == start
|
|
|
|
end
|
|
|
|
|
2008-09-29 16:49:18 +02:00
|
|
|
-- 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
|
|
|
|
|
2017-04-17 14:10:38 +02:00
|
|
|
if cword_index == 1 then
|
2008-09-29 16:49:18 +02:00
|
|
|
comptype = "command"
|
|
|
|
end
|
|
|
|
|
2009-02-26 12:20:13 +01:00
|
|
|
local shell_cmd
|
2017-07-03 19:26:10 +02:00
|
|
|
if not shell then
|
|
|
|
if not completion.default_shell then
|
|
|
|
local env_shell = os.getenv('SHELL')
|
|
|
|
if not env_shell then
|
|
|
|
gears_debug.print_warning('SHELL not set in environment, falling back to bash.')
|
|
|
|
completion.default_shell = 'bash'
|
|
|
|
elseif env_shell:match('zsh$') then
|
|
|
|
completion.default_shell = 'zsh'
|
|
|
|
else
|
|
|
|
completion.default_shell = 'bash'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
shell = completion.default_shell
|
|
|
|
end
|
|
|
|
if shell == 'zsh' then
|
2009-02-26 12:20:13 +01:00
|
|
|
if comptype == "file" then
|
2015-03-31 00:11:20 +02:00
|
|
|
-- 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=( ${~:-"
|
2017-04-17 13:28:40 +02:00
|
|
|
.. string.format('%q', words[cword_index]) .. "}*(N) ); "
|
2015-03-31 00:11:20 +02:00
|
|
|
.. "print -ln -- ${res[@]}'"
|
2009-02-26 12:20:13 +01:00
|
|
|
else
|
2017-04-17 13:28:40 +02:00
|
|
|
-- Check commands, aliases, builtins, functions and reswords.
|
|
|
|
-- Adds executables and non-empty dirs from $PWD (pwd_exe).
|
|
|
|
shell_cmd = "/usr/bin/env zsh -c 'local -a res pwd_exe; "..
|
|
|
|
"pwd_exe=(*(N*:t) *(NF:t)); "..
|
2009-02-26 12:20:13 +01:00
|
|
|
"res=( "..
|
2017-03-03 23:11:06 +01:00
|
|
|
"\"${(k)commands[@]}\" \"${(k)aliases[@]}\" \"${(k)builtins[@]}\" \"${(k)functions[@]}\" "..
|
|
|
|
"\"${(k)reswords[@]}\" "..
|
2017-04-17 13:28:40 +02:00
|
|
|
"./${^${pwd_exe}} "..
|
2009-02-26 12:20:13 +01:00
|
|
|
"); "..
|
2015-01-21 09:23:47 +01:00
|
|
|
"print -ln -- ${(M)res[@]:#" .. string.format('%q', words[cword_index]) .. "*}'"
|
2009-02-26 12:20:13 +01:00
|
|
|
end
|
2008-09-29 16:49:18 +02:00
|
|
|
else
|
2009-02-26 12:20:13 +01:00
|
|
|
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 .. "; " ..
|
2009-09-22 17:15:49 +02:00
|
|
|
bashcomp_funcs[words[1]] .. "; __print_completions'"
|
2009-02-26 12:20:13 +01:00
|
|
|
else
|
2015-01-21 09:23:47 +01:00
|
|
|
shell_cmd = "/usr/bin/env bash -c 'compgen -A " .. comptype .. " "
|
|
|
|
.. string.format('%q', words[cword_index]) .. "'"
|
2009-02-26 12:20:13 +01:00
|
|
|
end
|
2008-09-29 16:49:18 +02:00
|
|
|
end
|
2009-09-22 17:15:49 +02:00
|
|
|
local c, err = io.popen(shell_cmd .. " | sort -u")
|
2008-09-29 16:49:18 +02:00
|
|
|
local output = {}
|
2008-12-11 22:14:07 +01:00
|
|
|
if c then
|
|
|
|
while true do
|
|
|
|
local line = c:read("*line")
|
|
|
|
if not line then break end
|
2017-07-10 20:50:04 +02:00
|
|
|
if str_starts(line, "./") and gfs.is_dir(line) then
|
2009-01-23 19:02:48 +01:00
|
|
|
line = line .. "/"
|
|
|
|
end
|
2008-12-25 18:57:39 +01:00
|
|
|
table.insert(output, bash_escape(line))
|
2008-12-11 22:14:07 +01:00
|
|
|
end
|
2008-09-29 16:49:18 +02:00
|
|
|
|
2008-12-11 22:14:07 +01:00
|
|
|
c:close()
|
|
|
|
else
|
|
|
|
print(err)
|
|
|
|
end
|
2008-09-29 16:49:18 +02:00
|
|
|
|
|
|
|
-- no completion, return
|
|
|
|
if #output == 0 then
|
|
|
|
return command, cur_pos
|
|
|
|
end
|
|
|
|
|
2009-09-22 17:15:49 +02:00
|
|
|
-- cycle
|
2008-09-29 16:49:18 +02:00
|
|
|
while ncomp > #output do
|
|
|
|
ncomp = ncomp - #output
|
|
|
|
end
|
|
|
|
|
|
|
|
local str = command:sub(1, cword_start - 1) .. output[ncomp] .. command:sub(cword_end)
|
2017-04-18 00:05:33 +02:00
|
|
|
cur_pos = cword_start + #output[ncomp]
|
2008-09-29 16:49:18 +02:00
|
|
|
|
2010-10-11 12:34:58 +02:00
|
|
|
return str, cur_pos, output
|
2008-09-29 16:49:18 +02:00
|
|
|
end
|
|
|
|
|
2008-11-25 21:03:58 +01:00
|
|
|
--- 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.
|
awful.completion: remove keywords global variable
* move keywords global variable to generic() last parameter.
This prevents from having table clash.
Please udate you configuration according this feature in your
awful.prompt.run() calls.
If keywords parameter is missing then no completion would be
done.
Before:
awful.completion.keywords = kw
awful.prompt.run( [ ... ],
function(t, p, n) return awful.completion.generic(t, p, n) end,
[ ... ] )
Now:
awful.prompt.run( [ ... ],
function(t, p, n) return awful.completion.generic(t, p, n, kw) end,
[ ... ] )
Signed-off-by: Sébastien Gross <seb-awesome@chezwam.org>
Signed-off-by: Julien Danjou <julien@danjou.info>
2008-12-18 13:18:07 +01:00
|
|
|
-- @param keywords The keywords table uised for completion.
|
2010-10-11 12:34:58 +02:00
|
|
|
-- @return The new match, the new cursor position, the table of all matches.
|
2016-02-07 15:02:25 +01:00
|
|
|
function completion.generic(text, cur_pos, ncomp, keywords) -- luacheck: no unused args
|
2008-11-25 21:03:58 +01:00
|
|
|
-- 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
|
2012-06-14 22:48:18 +02:00
|
|
|
ncomp = math.fmod(ncomp - 1, #keywords) + 1
|
2008-11-25 21:03:58 +01:00
|
|
|
return keywords[ncomp], #keywords[ncomp] + 2
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Filter out only keywords starting with text
|
|
|
|
local matches = {}
|
2012-03-06 00:07:41 +01:00
|
|
|
for _, x in pairs(keywords) do
|
|
|
|
if x:sub(1, #text) == text then
|
2008-11-25 21:03:58 +01:00
|
|
|
table.insert(matches, x)
|
|
|
|
end
|
2012-03-06 00:07:41 +01:00
|
|
|
end
|
2008-11-25 21:03:58 +01:00
|
|
|
|
|
|
|
-- if there are no matches just leave out with the current text and position
|
|
|
|
if #matches == 0 then
|
2010-10-11 12:34:58 +02:00
|
|
|
return text, #text + 1, matches
|
2008-11-25 21:03:58 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- cycle around all matches
|
2012-06-14 22:48:18 +02:00
|
|
|
ncomp = math.fmod(ncomp - 1, #matches) + 1
|
2010-10-11 12:34:58 +02:00
|
|
|
return matches[ncomp], #matches[ncomp] + 1, matches
|
2008-11-25 21:03:58 +01:00
|
|
|
end
|
|
|
|
|
2012-06-12 20:13:09 +02:00
|
|
|
return completion
|
|
|
|
|
2011-09-11 16:50:01 +02:00
|
|
|
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80
|