awful.prompt add in history also on 'j' and 'm'
[awesome.git] / lib / awful / prompt.lua.in
blob2f009214474df4f9af5e71b21521a5e9a78891cc
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 capi =
14 keygrabber = keygrabber
16 local util = require("awful.util")
17 local beautiful = require("beautiful")
19 --- Prompt module for awful
20 module("awful.prompt")
22 --- Private data
23 local data = {}
24 data.history = {}
26 --- Load history file in history table
27 -- @param id The data.history identifier which is the path to the filename
28 -- @param max Optional parameter, the maximum number of entries in file
29 local function history_check_load(id, max)
30 if id and id ~= ""
31 and not data.history[id] then
32 data.history[id] = { max = 50, table = {} }
34 if max then
35 data.history[id].max = max
36 end
38 local f = io.open(id, "r")
40 -- Read history file
41 if f then
42 for line in f:lines() do
43 table.insert(data.history[id].table, line)
44 if #data.history[id].table >= data.history[id].max then
45 break
46 end
47 end
48 end
49 end
50 end
52 --- Save history table in history file
53 -- @param id The data.history identifier
54 local function history_save(id)
55 if data.history[id] then
56 local f = io.open(id, "w")
57 if not f then
58 local i = 0
59 for d in id:gmatch(".-/") do
60 i = i + #d
61 end
62 util.mkdir(id:sub(1, i - 1))
63 f = assert(io.open(id, "w"))
64 end
65 for i = 1, math.min(#data.history[id].table, data.history[id].max) do
66 f:write(data.history[id].table[i] .. "\n")
67 end
68 f:close()
69 end
70 end
72 --- Return the number of items in history table regarding the id
73 -- @param id The data.history identifier
74 -- @return the number of items in history table, -1 if history is disabled
75 local function history_items(id)
76 if data.history[id] then
77 return #data.history[id].table
78 else
79 return -1
80 end
81 end
83 --- Add an entry to the history file
84 -- @param id The data.history identifier
85 -- @param command The command to add
86 local function history_add(id, command)
87 if data.history[id] then
88 if command ~= ""
89 and command ~= data.history[id].table[#data.history[id].table] then
90 table.insert(data.history[id].table, command)
92 -- Do not exceed our max_cmd
93 if #data.history[id].table > data.history[id].max then
94 table.remove(data.history[id].table, 1)
95 end
97 history_save(id)
98 end
99 end
103 --- Draw the prompt text with a cursor.
104 -- @param text The text.
105 -- @param text_color The text color.
106 -- @param cursor_color The cursor color.
107 -- @param cursor_pos The cursor position.
108 -- @param cursor_pos The cursor underline style.
109 local function prompt_text_with_cursor(text, text_color, cursor_color, cursor_pos, cursor_ul)
110 local char
111 if not text then text = "" end
112 if #text < cursor_pos then
113 char = " "
114 else
115 char = util.escape(text:sub(cursor_pos, cursor_pos))
117 local underline = cursor_ul or "none"
118 local text_start = util.escape(text:sub(1, cursor_pos - 1))
119 local text_end = util.escape(text:sub(cursor_pos + 1))
120 return text_start .. "<span background=\"" .. util.color_strip_alpha(cursor_color) .. "\" foreground=\"" .. util.color_strip_alpha(text_color) .. "\" underline=\"" .. underline .. "\">" .. char .. "</span>" .. text_end
123 --- Run a prompt in a box.
124 -- @param args A table with optional arguments: fg_cursor, bg_cursor, ul_cursor, prompt.
125 -- @param textbox The textbox to use for the prompt.
126 -- @param exe_callback The callback function to call with command as argument when finished.
127 -- @param completion_callback The callback function to call to get completion.
128 -- @param history_path Optional parameter: file path where the history should be saved, set nil to disable history
129 -- @param history_max Optional parameter: set the maximum entries in history file, 50 by default
130 -- @param done_callback Optional parameter: the callback function to always call without arguments, regardless of whether the prompt was cancelled.
131 function run(args, textbox, exe_callback, completion_callback, history_path, history_max, done_callback)
132 local theme = beautiful.get()
133 if not args then args = {} end
134 local command = ""
135 local command_before_comp
136 local cur_pos_before_comp
137 local prettyprompt = args.prompt or ""
138 local inv_col = args.fg_cursor or theme.fg_focus or "black"
139 local cur_col = args.bg_cursor or theme.bg_focus or "white"
140 local cur_ul = args.ul_cursor
142 history_check_load(history_path, history_max)
143 local history_index = history_items(history_path) + 1
144 -- The cursor position
145 local cur_pos = 1
146 -- The completion element to use on completion request.
147 local ncomp = 1
148 if not textbox or not exe_callback then
149 return
151 textbox.text = prettyprompt .. prompt_text_with_cursor(text, inv_col, cur_col, cur_pos, cur_ul)
152 capi.keygrabber.run(
153 function (mod, key)
154 -- Get out cases
155 if mod.Control then
156 if key == "c" or key == "g" then
157 textbox.text = ""
158 if done_callback then done_callback() end
159 return false
160 elseif key == "j" or key == "m" then
161 textbox.text = ""
162 history_add(history_path, command)
163 exe_callback(command)
164 if done_callback then done_callback() end
165 return false
167 else
168 if key == "Return" then
169 textbox.text = ""
170 history_add(history_path, command)
171 exe_callback(command)
172 if done_callback then done_callback() end
173 return false
174 elseif key == "Escape" then
175 textbox.text = ""
176 if done_callback then done_callback() end
177 return false
181 -- Control cases
182 if mod.Control then
183 if key == "a" then
184 cur_pos = 1
185 elseif key == "b" then
186 if cur_pos > 1 then
187 cur_pos = cur_pos - 1
189 elseif key == "d" then
190 if cur_pos <= #command then
191 command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
193 elseif key == "e" then
194 cur_pos = #command + 1
195 elseif key == "f" then
196 if cur_pos <= #command then
197 cur_pos = cur_pos + 1
199 elseif key == "h" then
200 if cur_pos > 1 then
201 command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
202 cur_pos = cur_pos - 1
204 elseif key == "k" then
205 command = command:sub(1, cur_pos - 1)
206 elseif key == "u" then
207 command = command:sub(cur_pos, #command)
208 cur_pos = 1
209 elseif key == "w" then
210 local wstart = 1
211 local wend = 1
212 local cword_start = 1
213 local cword_end = 1
214 while wend < cur_pos do
215 wend = command:find(" ", wstart)
216 if not wend then wend = #command + 1 end
217 if cur_pos >= wstart and cur_pos <= wend + 1 then
218 cword_start = wstart
219 cword_end = cur_pos - 1
220 break
222 wstart = wend + 1
224 command = command:sub(1, cword_start - 1) .. command:sub(cword_end + 1)
225 cur_pos = cword_start
227 else
228 if completion_callback then
229 -- That's tab
230 if key:byte() == 9 or key == "ISO_Left_Tab" then
231 if key == "ISO_Left_Tab" then
232 if ncomp == 1 then return true end
233 if ncomp == 2 then
234 command = command_before_comp
235 textbox.text = prettyprompt .. prompt_text_with_cursor(command_before_comp, inv_col, cur_col, cur_pos)
236 return true
239 ncomp = ncomp - 2
240 elseif ncomp == 1 then
241 command_before_comp = command
242 cur_pos_before_comp = cur_pos
244 command, cur_pos = completion_callback(command_before_comp, cur_pos_before_comp, ncomp)
245 ncomp = ncomp + 1
246 key = ""
247 else
248 ncomp = 1
252 -- Typin cases
253 if key == "Home" then
254 cur_pos = 1
255 elseif key == "End" then
256 cur_pos = #command + 1
257 elseif key == "BackSpace" then
258 if cur_pos > 1 then
259 command = command:sub(1, cur_pos - 2) .. command:sub(cur_pos)
260 cur_pos = cur_pos - 1
262 -- That's DEL
263 elseif key:byte() == 127 then
264 command = command:sub(1, cur_pos - 1) .. command:sub(cur_pos + 1)
265 elseif key == "Left" then
266 cur_pos = cur_pos - 1
267 elseif key == "Right" then
268 cur_pos = cur_pos + 1
269 elseif key == "Up" then
270 if history_index > 1 then
271 history_index = history_index - 1
273 command = data.history[history_path].table[history_index]
274 cur_pos = #command + 2
276 elseif key == "Down" then
277 if history_index < history_items(history_path) then
278 history_index = history_index + 1
280 command = data.history[history_path].table[history_index]
281 cur_pos = #command + 2
282 elseif history_index == history_items(history_path) then
283 history_index = history_index + 1
285 command = ""
286 cur_pos = 1
288 else
289 -- len() is UTF-8 aware but #key is not,
290 -- so check that we have one UTF-8 char but advance the cursor of # position
291 if key:len() == 1 then
292 command = command:sub(1, cur_pos - 1) .. key .. command:sub(cur_pos)
293 cur_pos = cur_pos + #key
296 if cur_pos < 1 then
297 cur_pos = 1
298 elseif cur_pos > #command + 1 then
299 cur_pos = #command + 1
303 -- Update textbox
304 textbox.text = prettyprompt .. prompt_text_with_cursor(command, inv_col, cur_col, cur_pos, cur_ul)
306 return true
307 end)
310 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=80