tag.lua: move() re-index tags
[awesome.git] / lib / awful / prompt.lua.in
blobf4dddf4c88882595e07508a2bacdc63e2ede5a02
1 ---------------------------------------------------------------------------
2 -- @author Julien Danjou <julien@danjou.info>
3 -- @copyright 2008 Julien Danjou
4 -- @release @AWESOME_VERSION@
5 ---------------------------------------------------------------------------
7 -- Grab environment we need
8 local assert = assert
9 local io = io
10 local table = table
11 local math = math
12 local ipairs = ipairs
13 local capi =
15 keygrabber = keygrabber,
16 selection = selection
18 local util = require("awful.util")
19 local beautiful = require("beautiful")
21 --- Prompt module for awful
22 module("awful.prompt")
24 --- Private data
25 local data = {}
26 data.history = {}
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)
32 if id and id ~= ""
33 and not data.history[id] then
34 data.history[id] = { max = 50, table = {} }
36 if max then
37 data.history[id].max = max
38 end
40 local f = io.open(id, "r")
42 -- Read history file
43 if f then
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
47 break
48 end
49 end
50 f:close()
51 end
52 end
53 end
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")
60 if not f then
61 local i = 0
62 for d in id:gmatch(".-/") do
63 i = i + #d
64 end
65 util.mkdir(id:sub(1, i - 1))
66 f = assert(io.open(id, "w"))
67 end
68 for i = 1, math.min(#data.history[id].table, data.history[id].max) do
69 f:write(data.history[id].table[i] .. "\n")
70 end
71 f:close()
72 end
73 end
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
81 else
82 return -1
83 end
84 end
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
91 if command ~= ""
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)
98 end
100 history_save(id)
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
124 spacer = " "
125 text_start = ""
126 text_end = ""
127 elseif #text < args.cursor_pos then
128 char = " "
129 spacer = ""
130 text_start = util.escape(text)
131 text_end = ""
132 else
133 char = util.escape(text:sub(args.cursor_pos, args.cursor_pos))
134 spacer = " "
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
141 return ret
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.
171 local ncomp = 1
172 if not textbox or not exe_callback then
173 return
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 }
180 capi.keygrabber.run(
181 function (modifiers, key, event)
182 if event ~= "press" then return true end
183 -- Convert index array to hash table
184 local mod = {}
185 for k, v in ipairs(modifiers) do mod[v] = true end
186 -- Get out cases
187 if (mod.Control and (key == "c" or key == "g"))
188 or (not mod.Control and key == "Escape") then
189 textbox.text = ""
190 if done_callback then done_callback() end
191 return false
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
195 textbox.text = ""
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.
202 return true
205 -- Control cases
206 if mod.Control then
207 selectall = nil
208 if key == "a" then
209 cur_pos = 1
210 elseif key == "b" then
211 if cur_pos > 1 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
225 if cur_pos > 1 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)
233 cur_pos = 1
234 elseif key == "w" then
235 local wstart = 1
236 local wend = 1
237 local cword_start = 1
238 local cword_end = 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
243 cword_start = wstart
244 cword_end = cur_pos - 1
245 break
247 wstart = wend + 1
249 command = command:sub(1, cword_start - 1) .. command:sub(cword_end + 1)
250 cur_pos = cword_start
252 else
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
257 if ncomp == 2 then
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 }
263 return true
266 ncomp = ncomp - 2
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)
272 ncomp = ncomp + 1
273 key = ""
274 else
275 ncomp = 1
279 -- Typin cases
280 if mod.Shift and key == "Insert" then
281 local selection = capi.selection()
282 if selection then
283 -- Remove \n
284 local n = selection:find("\n")
285 if n then
286 selection = selection:sub(1, n - 1)
288 command = command .. selection
289 cur_pos = cur_pos + #selection
291 elseif key == "Home" then
292 cur_pos = 1
293 elseif key == "End" then
294 cur_pos = #command + 1
295 elseif key == "BackSpace" then
296 if cur_pos > 1 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
322 command = ""
323 cur_pos = 1
325 else
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
334 if cur_pos < 1 then
335 cur_pos = 1
336 elseif cur_pos > #command + 1 then
337 cur_pos = #command + 1
339 selectall = nil
342 -- Update textbox
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 }
348 return true
349 end)
352 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80