1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
16 keygrabber
= keygrabber
,
19 local util
= require("awful.util")
20 local beautiful
= require("beautiful")
22 --- Prompt module for awful
23 module("awful.prompt")
29 -- Load history file in history table
30 -- @param id The data.history identifier which is the path to the filename
31 -- @param max Optional parameter, the maximum number of entries in file
32 local function history_check_load(id
, max)
34 and not data
.history
[id
] then
35 data
.history
[id
] = { max = 50, table = {} }
38 data
.history
[id
].max = max
41 local f
= io
.open(id
, "r")
45 for line
in f
:lines() do
46 table.insert(data
.history
[id
].table, line
)
47 if #data
.history
[id
].table >= data
.history
[id
].max then
56 -- Save history table in history file
57 -- @param id The data.history identifier
58 local function history_save(id
)
59 if data
.history
[id
] then
60 local f
= io
.open(id
, "w")
63 for d
in id
:gmatch(".-/") do
66 util
.mkdir(id
:sub(1, i
- 1))
67 f
= assert(io
.open(id
, "w"))
69 for i
= 1, math
.min(#data
.history
[id
].table, data
.history
[id
].max) do
70 f
:write(data
.history
[id
].table[i
] .. "\n")
76 -- Return the number of items in history table regarding the id
77 -- @param id The data.history identifier
78 -- @return the number of items in history table, -1 if history is disabled
79 local function history_items(id
)
80 if data
.history
[id
] then
81 return #data
.history
[id
].table
87 -- Add an entry to the history file
88 -- @param id The data.history identifier
89 -- @param command The command to add
90 local function history_add(id
, command
)
91 if data
.history
[id
] then
93 and command
~= data
.history
[id
].table[#data
.history
[id
].table] then
94 table.insert(data
.history
[id
].table, command
)
96 -- Do not exceed our max_cmd
97 if #data
.history
[id
].table > data
.history
[id
].max then
98 table.remove(data
.history
[id
].table, 1)
107 -- Draw the prompt text with a cursor.
108 -- @param args The table of arguments.
109 -- @param text The text.
110 -- @param font The font.
111 -- @param prompt The text prefix.
112 -- @param text_color The text color.
113 -- @param cursor_color The cursor color.
114 -- @param cursor_pos The cursor position.
115 -- @param cursor_ul The cursor underline style.
116 -- @param selectall If true cursor is rendered on the entire text.
117 local function prompt_text_with_cursor(args
)
118 local char
, spacer
, text_start
, text_end
, ret
119 local text
= args
.text
or ""
120 local prompt
= args
.prompt
or ""
121 local underline
= args
.cursor_ul
or "none"
123 if args
.selectall
then
124 if #text
== 0 then char
= " " else char
= util
.escape(text
) end
128 elseif #text
< args
.cursor_pos
then
131 text_start
= util
.escape(text
)
134 char
= util
.escape(text
:sub(args
.cursor_pos
, args
.cursor_pos
))
136 text_start
= util
.escape(text
:sub(1, args
.cursor_pos
- 1))
137 text_end
= util
.escape(text
:sub(args
.cursor_pos
+ 1))
140 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
141 if args
.font
then ret
= "<span font_desc='" .. args
.font
.. "'>" .. ret
.. "</span>" end
145 --- Run a prompt in a box.
146 -- @param args A table with optional arguments: fg_cursor, bg_cursor, ul_cursor, prompt, text, selectall, font.
147 -- @param textbox The textbox to use for the prompt.
148 -- @param exe_callback The callback function to call with command as argument when finished.
149 -- @param completion_callback The callback function to call to get completion.
150 -- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history
151 -- @param history_max Optional parameter: set the maximum entries in history file, 50 by default
152 -- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled.
153 function run(args
, textbox
, exe_callback
, completion_callback
, history_path
, history_max
, done_callback
)
154 local theme
= beautiful
.get()
155 if not args
then args
= {} end
156 local command
= args
.text
or ""
157 local command_before_comp
158 local cur_pos_before_comp
159 local prettyprompt
= args
.prompt
or ""
160 local inv_col
= args
.fg_cursor
or theme
.fg_focus
or "black"
161 local cur_col
= args
.bg_cursor
or theme
.bg_focus
or "white"
162 local cur_ul
= args
.ul_cursor
163 local text
= args
.text
or ""
164 local font
= args
.font
or theme
.font
165 local selectall
= args
.selectall
167 history_check_load(history_path
, history_max
)
168 local history_index
= history_items(history_path
) + 1
169 -- The cursor position
170 local cur_pos
= (selectall
and 1) or text
:wlen() + 1
171 -- The completion element to use on completion request.
173 if not textbox
or not exe_callback
then
176 textbox
.text
= prompt_text_with_cursor
{
177 text
= text
, text_color
= inv_col
, cursor_color
= cur_col
,
178 cursor_pos
= cur_pos
, cursor_ul
= cur_ul
, selectall
= selectall
,
179 font
= font
, prompt
= prettyprompt
}
182 function (modifiers
, key
, event
)
183 if event
~= "press" then return true end
184 -- Convert index array to hash table
186 for k
, v
in ipairs(modifiers
) do mod[v
] = true end
188 if (mod.Control
and (key
== "c" or key
== "g"))
189 or (not mod.Control
and key
== "Escape") then
191 if done_callback
then done_callback() end
193 elseif (mod.Control
and (key
== "j" or key
== "m"))
194 or (not mod.Control
and key
== "Return")
195 or (not mod.Control
and key
== "KP_Enter") then
197 history_add(history_path
, command
)
198 capi
.keygrabber
.stop()
199 exe_callback(command
)
200 if done_callback
then done_callback() end
201 -- We already unregistered ourselves so we don't want to return
202 -- true, otherwise we may unregister someone else.
211 elseif key
== "b" then
213 cur_pos
= cur_pos
- 1
215 elseif key
== "d" then
216 if cur_pos
<= #command
then
217 command
= command
:sub(1, cur_pos
- 1) .. command
:sub(cur_pos
+ 1)
219 elseif key
== "e" then
220 cur_pos
= #command
+ 1
221 elseif key
== "f" then
222 if cur_pos
<= #command
then
223 cur_pos
= cur_pos
+ 1
225 elseif key
== "h" then
227 command
= command
:sub(1, cur_pos
- 2) .. command
:sub(cur_pos
)
228 cur_pos
= cur_pos
- 1
230 elseif key
== "k" then
231 command
= command
:sub(1, cur_pos
- 1)
232 elseif key
== "u" then
233 command
= command
:sub(cur_pos
, #command
)
235 elseif key
== "w" then
238 local cword_start
= 1
240 while wend
< cur_pos
do
241 wend
= command
:find("[{[(,.:;_-+=@/ ]", wstart
)
242 if not wend
then wend
= #command
+ 1 end
243 if cur_pos
>= wstart
and cur_pos
<= wend
+ 1 then
245 cword_end
= cur_pos
- 1
250 command
= command
:sub(1, cword_start
- 1) .. command
:sub(cword_end
+ 1)
251 cur_pos
= cword_start
254 if completion_callback
then
255 if key
== "Tab" or key
== "ISO_Left_Tab" then
256 if key
== "ISO_Left_Tab" then
257 if ncomp
== 1 then return true end
259 command
= command_before_comp
260 textbox
.text
= prompt_text_with_cursor
{
261 text
= command_before_comp
, text_color
= inv_col
, cursor_color
= cur_col
,
262 cursor_pos
= cur_pos
, cursor_ul
= cur_ul
, selectall
= selectall
,
263 font
= font
, prompt
= prettyprompt
}
268 elseif ncomp
== 1 then
269 command_before_comp
= command
270 cur_pos_before_comp
= cur_pos
272 command
, cur_pos
= completion_callback(command_before_comp
, cur_pos_before_comp
, ncomp
)
281 if mod.Shift
and key
== "Insert" then
282 local selection
= capi
.selection()
285 local n
= selection
:find("\n")
287 selection
= selection
:sub(1, n
- 1)
289 command
= command
.. selection
290 cur_pos
= cur_pos
+ #selection
292 elseif key
== "Home" then
294 elseif key
== "End" then
295 cur_pos
= #command
+ 1
296 elseif key
== "BackSpace" then
298 command
= command
:sub(1, cur_pos
- 2) .. command
:sub(cur_pos
)
299 cur_pos
= cur_pos
- 1
301 elseif key
== "Delete" then
302 command
= command
:sub(1, cur_pos
- 1) .. command
:sub(cur_pos
+ 1)
303 elseif key
== "Left" then
304 cur_pos
= cur_pos
- 1
305 elseif key
== "Right" then
306 cur_pos
= cur_pos
+ 1
307 elseif key
== "Up" then
308 if history_index
> 1 then
309 history_index
= history_index
- 1
311 command
= data
.history
[history_path
].table[history_index
]
312 cur_pos
= #command
+ 2
314 elseif key
== "Down" then
315 if history_index
< history_items(history_path
) then
316 history_index
= history_index
+ 1
318 command
= data
.history
[history_path
].table[history_index
]
319 cur_pos
= #command
+ 2
320 elseif history_index
== history_items(history_path
) then
321 history_index
= history_index
+ 1
327 -- wlen() is UTF-8 aware but #key is not,
328 -- so check that we have one UTF-8 char but advance the cursor of # position
329 if key
:wlen() == 1 then
330 if selectall
then command
= "" end
331 command
= command
:sub(1, cur_pos
- 1) .. key
.. command
:sub(cur_pos
)
332 cur_pos
= cur_pos
+ #key
337 elseif cur_pos
> #command
+ 1 then
338 cur_pos
= #command
+ 1
344 local function update()
345 textbox
.text
= prompt_text_with_cursor
{
346 text
= command
, text_color
= inv_col
, cursor_color
= cur_col
,
347 cursor_pos
= cur_pos
, cursor_ul
= cur_ul
, selectall
= selectall
,
348 font
= font
, prompt
= prettyprompt
}
351 local success
= pcall(update
)
353 -- TODO UGLY HACK TODO
354 -- Setting the text failed. Most likely reason is that the user
355 -- entered a multibyte character and pressed backspace which only
356 -- removed the last byte. Let's remove another byte.
362 command
= command
:sub(1, cur_pos
- 2) .. command
:sub(cur_pos
)
363 cur_pos
= cur_pos
- 1
364 success
= pcall(update
)
371 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80