1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
15 keygrabber
= keygrabber
,
18 local util
= require("awful.util")
19 local beautiful
= require("beautiful")
21 --- Prompt module for awful
22 module("awful.prompt")
28 -- Load history file in history table
29 -- @param id The data.history identifier which is the path to the filename
30 -- @param max Optional parameter, the maximum number of entries in file
31 local function history_check_load(id
, max)
33 and not data
.history
[id
] then
34 data
.history
[id
] = { max = 50, table = {} }
37 data
.history
[id
].max = max
40 local f
= io
.open(id
, "r")
44 for line
in f
:lines() do
45 table.insert(data
.history
[id
].table, line
)
46 if #data
.history
[id
].table >= data
.history
[id
].max then
55 -- Save history table in history file
56 -- @param id The data.history identifier
57 local function history_save(id
)
58 if data
.history
[id
] then
59 local f
= io
.open(id
, "w")
62 for d
in id
:gmatch(".-/") do
65 util
.mkdir(id
:sub(1, i
- 1))
66 f
= assert(io
.open(id
, "w"))
68 for i
= 1, math
.min(#data
.history
[id
].table, data
.history
[id
].max) do
69 f
:write(data
.history
[id
].table[i
] .. "\n")
75 -- Return the number of items in history table regarding the id
76 -- @param id The data.history identifier
77 -- @return the number of items in history table, -1 if history is disabled
78 local function history_items(id
)
79 if data
.history
[id
] then
80 return #data
.history
[id
].table
86 -- Add an entry to the history file
87 -- @param id The data.history identifier
88 -- @param command The command to add
89 local function history_add(id
, command
)
90 if data
.history
[id
] then
92 and command
~= data
.history
[id
].table[#data
.history
[id
].table] then
93 table.insert(data
.history
[id
].table, command
)
95 -- Do not exceed our max_cmd
96 if #data
.history
[id
].table > data
.history
[id
].max then
97 table.remove(data
.history
[id
].table, 1)
106 -- Draw the prompt text with a cursor.
107 -- @param args The table of arguments.
108 -- @param text The text.
109 -- @param font The font.
110 -- @param prompt The text prefix.
111 -- @param text_color The text color.
112 -- @param cursor_color The cursor color.
113 -- @param cursor_pos The cursor position.
114 -- @param cursor_ul The cursor underline style.
115 -- @param selectall If true cursor is rendered on the entire text.
116 local function prompt_text_with_cursor(args
)
117 local char
, spacer
, text_start
, text_end
, ret
118 local text
= args
.text
or ""
119 local prompt
= args
.prompt
or ""
120 local underline
= args
.cursor_ul
or "none"
122 if args
.selectall
then
123 if #text
== 0 then char
= " " else char
= util
.escape(text
) end
127 elseif #text
< args
.cursor_pos
then
130 text_start
= util
.escape(text
)
133 char
= util
.escape(text
:sub(args
.cursor_pos
, args
.cursor_pos
))
135 text_start
= util
.escape(text
:sub(1, args
.cursor_pos
- 1))
136 text_end
= util
.escape(text
:sub(args
.cursor_pos
+ 1))
139 ret
= prompt
.. text_start
.. "<span background=\"" .. util
.color_strip_alpha(args
.cursor_color
) .. "\" foreground=\"" .. util
.color_strip_alpha(args
.text_color
) .. "\" underline=\"" .. underline
.. "\">" .. char
.. "</span>" .. text_end
.. spacer
140 if args
.font
then ret
= "<span font_desc='" .. args
.font
.. "'>" .. ret
.. "</span>" end
144 --- Run a prompt in a box.
145 -- @param args A table with optional arguments: fg_cursor, bg_cursor, ul_cursor, prompt, text, selectall, font.
146 -- @param textbox The textbox to use for the prompt.
147 -- @param exe_callback The callback function to call with command as argument when finished.
148 -- @param completion_callback The callback function to call to get completion.
149 -- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history
150 -- @param history_max Optional parameter: set the maximum entries in history file, 50 by default
151 -- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled.
152 function run(args
, textbox
, exe_callback
, completion_callback
, history_path
, history_max
, done_callback
)
153 local theme
= beautiful
.get()
154 if not args
then args
= {} end
155 local command
= args
.text
or ""
156 local command_before_comp
157 local cur_pos_before_comp
158 local prettyprompt
= args
.prompt
or ""
159 local inv_col
= args
.fg_cursor
or theme
.fg_focus
or "black"
160 local cur_col
= args
.bg_cursor
or theme
.bg_focus
or "white"
161 local cur_ul
= args
.ul_cursor
162 local text
= args
.text
or ""
163 local font
= args
.font
or theme
.font
164 local selectall
= args
.selectall
166 history_check_load(history_path
, history_max
)
167 local history_index
= history_items(history_path
) + 1
168 -- The cursor position
169 local cur_pos
= (selectall
and 1) or text
:wlen() + 1
170 -- The completion element to use on completion request.
172 if not textbox
or not exe_callback
then
175 textbox
.text
= prompt_text_with_cursor
{
176 text
= text
, text_color
= inv_col
, cursor_color
= cur_col
,
177 cursor_pos
= cur_pos
, cursor_ul
= cur_ul
, selectall
= selectall
,
178 font
= font
, prompt
= prettyprompt
}
181 function (modifiers
, key
, event
)
182 if event
~= "press" then return true end
183 -- Convert index array to hash table
185 for k
, v
in ipairs(modifiers
) do mod[v
] = true end
187 if (mod.Control
and (key
== "c" or key
== "g"))
188 or (not mod.Control
and key
== "Escape") then
190 if done_callback
then done_callback() end
192 elseif (mod.Control
and (key
== "j" or key
== "m"))
193 or (not mod.Control
and key
== "Return")
194 or (not mod.Control
and key
== "KP_Enter") then
196 history_add(history_path
, command
)
197 capi
.keygrabber
.stop()
198 exe_callback(command
)
199 if done_callback
then done_callback() end
200 -- We already unregistered ourselves so we don't want to return
201 -- true, otherwise we may unregister someone else.
210 elseif key
== "b" then
212 cur_pos
= cur_pos
- 1
214 elseif key
== "d" then
215 if cur_pos
<= #command
then
216 command
= command
:sub(1, cur_pos
- 1) .. command
:sub(cur_pos
+ 1)
218 elseif key
== "e" then
219 cur_pos
= #command
+ 1
220 elseif key
== "f" then
221 if cur_pos
<= #command
then
222 cur_pos
= cur_pos
+ 1
224 elseif key
== "h" then
226 command
= command
:sub(1, cur_pos
- 2) .. command
:sub(cur_pos
)
227 cur_pos
= cur_pos
- 1
229 elseif key
== "k" then
230 command
= command
:sub(1, cur_pos
- 1)
231 elseif key
== "u" then
232 command
= command
:sub(cur_pos
, #command
)
234 elseif key
== "w" then
237 local cword_start
= 1
239 while wend
< cur_pos
do
240 wend
= command
:find("[{[(,.:;_-+=@/ ]", wstart
)
241 if not wend
then wend
= #command
+ 1 end
242 if cur_pos
>= wstart
and cur_pos
<= wend
+ 1 then
244 cword_end
= cur_pos
- 1
249 command
= command
:sub(1, cword_start
- 1) .. command
:sub(cword_end
+ 1)
250 cur_pos
= cword_start
253 if completion_callback
then
254 if key
== "Tab" or key
== "ISO_Left_Tab" then
255 if key
== "ISO_Left_Tab" then
256 if ncomp
== 1 then return true end
258 command
= command_before_comp
259 textbox
.text
= prompt_text_with_cursor
{
260 text
= command_before_comp
, text_color
= inv_col
, cursor_color
= cur_col
,
261 cursor_pos
= cur_pos
, cursor_ul
= cur_ul
, selectall
= selectall
,
262 font
= font
, prompt
= prettyprompt
}
267 elseif ncomp
== 1 then
268 command_before_comp
= command
269 cur_pos_before_comp
= cur_pos
271 command
, cur_pos
= completion_callback(command_before_comp
, cur_pos_before_comp
, ncomp
)
280 if mod.Shift
and key
== "Insert" then
281 local selection
= capi
.selection()
284 local n
= selection
:find("\n")
286 selection
= selection
:sub(1, n
- 1)
288 command
= command
.. selection
289 cur_pos
= cur_pos
+ #selection
291 elseif key
== "Home" then
293 elseif key
== "End" then
294 cur_pos
= #command
+ 1
295 elseif key
== "BackSpace" then
297 command
= command
:sub(1, cur_pos
- 2) .. command
:sub(cur_pos
)
298 cur_pos
= cur_pos
- 1
300 elseif key
== "Delete" then
301 command
= command
:sub(1, cur_pos
- 1) .. command
:sub(cur_pos
+ 1)
302 elseif key
== "Left" then
303 cur_pos
= cur_pos
- 1
304 elseif key
== "Right" then
305 cur_pos
= cur_pos
+ 1
306 elseif key
== "Up" then
307 if history_index
> 1 then
308 history_index
= history_index
- 1
310 command
= data
.history
[history_path
].table[history_index
]
311 cur_pos
= #command
+ 2
313 elseif key
== "Down" then
314 if history_index
< history_items(history_path
) then
315 history_index
= history_index
+ 1
317 command
= data
.history
[history_path
].table[history_index
]
318 cur_pos
= #command
+ 2
319 elseif history_index
== history_items(history_path
) then
320 history_index
= history_index
+ 1
326 -- wlen() is UTF-8 aware but #key is not,
327 -- so check that we have one UTF-8 char but advance the cursor of # position
328 if key
:wlen() == 1 then
329 if selectall
then command
= "" end
330 command
= command
:sub(1, cur_pos
- 1) .. key
.. command
:sub(cur_pos
)
331 cur_pos
= cur_pos
+ #key
336 elseif cur_pos
> #command
+ 1 then
337 cur_pos
= #command
+ 1
343 textbox
.text
= prompt_text_with_cursor
{
344 text
= command
, text_color
= inv_col
, cursor_color
= cur_col
,
345 cursor_pos
= cur_pos
, cursor_ul
= cur_ul
, selectall
= selectall
,
346 font
= font
, prompt
= prettyprompt
}
352 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80