1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
|