656 lines
26 KiB
Lua
656 lines
26 KiB
Lua
|
---------------------------------------------------------------------------
|
||
|
--- Modified Prompt module.
|
||
|
-- @author Julien Danjou <julien@danjou.info>
|
||
|
-- @copyright 2008 Julien Danjou
|
||
|
---------------------------------------------------------------------------
|
||
|
|
||
|
local akey = require("awful.key")
|
||
|
local keygrabber = require("awful.keygrabber")
|
||
|
local gobject = require("gears.object")
|
||
|
local gdebug = require('gears.debug')
|
||
|
local gtable = require("gears.table")
|
||
|
local gcolor = require("gears.color")
|
||
|
local gstring = require("gears.string")
|
||
|
local gfs = require("gears.filesystem")
|
||
|
local wibox = require("wibox")
|
||
|
local beautiful = require("beautiful")
|
||
|
local io = io
|
||
|
local table = table
|
||
|
local math = math
|
||
|
local ipairs = ipairs
|
||
|
local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility with Lua 5.1)
|
||
|
local capi = { selection = selection }
|
||
|
|
||
|
local prompt = { mt = {} }
|
||
|
|
||
|
--- Private data
|
||
|
local data = {}
|
||
|
data.history = {}
|
||
|
|
||
|
local function itera(inc,a, i)
|
||
|
i = i + inc
|
||
|
local v = a[i]
|
||
|
if v then return i,v end
|
||
|
end
|
||
|
|
||
|
local function history_check_load(id, max)
|
||
|
if id and id ~= "" and not data.history[id] then
|
||
|
data.history[id] = { max = 50, table = {} }
|
||
|
|
||
|
if max then
|
||
|
data.history[id].max = max
|
||
|
end
|
||
|
|
||
|
local f = io.open(id, "r")
|
||
|
if not f then return end
|
||
|
|
||
|
-- Read history file
|
||
|
for line in f:lines() do
|
||
|
if gtable.hasitem(data.history[id].table, line) == nil then
|
||
|
table.insert(data.history[id].table, line)
|
||
|
if #data.history[id].table >= data.history[id].max then
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
f:close()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function is_word_char(c)
|
||
|
if string.find(c, "[{[(,.:;_-+=@/ ]") then
|
||
|
return false
|
||
|
else
|
||
|
return true
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function cword_start(s, pos)
|
||
|
local i = pos
|
||
|
if i > 1 then
|
||
|
i = i - 1
|
||
|
end
|
||
|
while i >= 1 and not is_word_char(s:sub(i, i)) do
|
||
|
i = i - 1
|
||
|
end
|
||
|
while i >= 1 and is_word_char(s:sub(i, i)) do
|
||
|
i = i - 1
|
||
|
end
|
||
|
if i <= #s then
|
||
|
i = i + 1
|
||
|
end
|
||
|
return i
|
||
|
end
|
||
|
|
||
|
local function cword_end(s, pos)
|
||
|
local i = pos
|
||
|
while i <= #s and not is_word_char(s:sub(i, i)) do
|
||
|
i = i + 1
|
||
|
end
|
||
|
while i <= #s and is_word_char(s:sub(i, i)) do
|
||
|
i = i + 1
|
||
|
end
|
||
|
return i
|
||
|
end
|
||
|
|
||
|
local function history_save(id)
|
||
|
if data.history[id] then
|
||
|
gfs.make_parent_directories(id)
|
||
|
local f = io.open(id, "w")
|
||
|
if not f then
|
||
|
gdebug.print_warning("Failed to write the history to "..id)
|
||
|
return
|
||
|
end
|
||
|
for i = 1, math.min(#data.history[id].table, data.history[id].max) do
|
||
|
f:write(data.history[id].table[i] .. "\n")
|
||
|
end
|
||
|
f:close()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function history_items(id)
|
||
|
if data.history[id] then
|
||
|
return #data.history[id].table
|
||
|
else
|
||
|
return -1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function history_add(id, command)
|
||
|
if data.history[id] and command ~= "" then
|
||
|
local index = gtable.hasitem(data.history[id].table, command)
|
||
|
if index == nil then
|
||
|
table.insert(data.history[id].table, command)
|
||
|
|
||
|
-- Do not exceed our max_cmd
|
||
|
if #data.history[id].table > data.history[id].max then
|
||
|
table.remove(data.history[id].table, 1)
|
||
|
end
|
||
|
|
||
|
history_save(id)
|
||
|
else
|
||
|
-- Bump this command to the end of history
|
||
|
table.remove(data.history[id].table, index)
|
||
|
table.insert(data.history[id].table, command)
|
||
|
history_save(id)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function have_multibyte_char_at(text, position)
|
||
|
return text:sub(position, position):wlen() == -1
|
||
|
end
|
||
|
|
||
|
local function prompt_text_with_cursor(args)
|
||
|
local char, spacer, text_start, text_end, ret
|
||
|
local text = args.text or ""
|
||
|
local _prompt = args.prompt or ""
|
||
|
local underline = args.cursor_ul or "none"
|
||
|
|
||
|
if args.select_all then
|
||
|
if #text == 0 then char = " " else char = gstring.xml_escape(text) end
|
||
|
spacer = " "
|
||
|
text_start = ""
|
||
|
text_end = ""
|
||
|
elseif #text < args.cursor_pos then
|
||
|
char = " "
|
||
|
spacer = ""
|
||
|
text_start = gstring.xml_escape(text)
|
||
|
text_end = ""
|
||
|
else
|
||
|
local offset = 0
|
||
|
if have_multibyte_char_at(text, args.cursor_pos) then
|
||
|
offset = 1
|
||
|
end
|
||
|
char = gstring.xml_escape(text:sub(args.cursor_pos, args.cursor_pos + offset))
|
||
|
spacer = " "
|
||
|
text_start = gstring.xml_escape(text:sub(1, args.cursor_pos - 1))
|
||
|
text_end = gstring.xml_escape(text:sub(args.cursor_pos + 1 + offset))
|
||
|
end
|
||
|
|
||
|
local cursor_color = gcolor.ensure_pango_color(args.cursor_color)
|
||
|
local text_color = gcolor.ensure_pango_color(args.text_color)
|
||
|
|
||
|
if args.highlighter then
|
||
|
text_start, text_end = args.highlighter(text_start, text_end)
|
||
|
end
|
||
|
|
||
|
ret = _prompt .. text_start .. "<span background=\"" .. cursor_color ..
|
||
|
"\" foreground=\"" .. text_color .. "\" underline=\"" .. underline ..
|
||
|
"\">" .. char .. "</span>" .. text_end .. spacer
|
||
|
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
local function update(self)
|
||
|
self.textbox:set_font(self.font)
|
||
|
self.textbox:set_markup(prompt_text_with_cursor{
|
||
|
text = self.command, text_color = self.fg_cursor, cursor_color = self.bg_cursor,
|
||
|
cursor_pos = self._private_cur_pos, cursor_ul = self.ul_cursor, select_all = self.select_all,
|
||
|
prompt = self.prompt, highlighter = self.highlighter })
|
||
|
end
|
||
|
|
||
|
local function exec(self, cb, command_to_history)
|
||
|
self.textbox:set_markup("")
|
||
|
history_add(self.history_path, command_to_history)
|
||
|
keygrabber.stop(self._private.grabber)
|
||
|
if cb then cb(self.command) end
|
||
|
if self.done_callback then
|
||
|
self.done_callback()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function prompt:start()
|
||
|
-- The cursor position
|
||
|
if self.reset_on_stop == true or self._private_cur_pos == nil then
|
||
|
self._private_cur_pos = (self.select_all and 1) or self.text:wlen() + 1
|
||
|
end
|
||
|
if self.reset_on_stop == true then self.text = "" self.command = "" end
|
||
|
|
||
|
self.textbox:set_font(self.font)
|
||
|
self.textbox:set_markup(prompt_text_with_cursor{
|
||
|
text = self.reset_on_stop and self.text or self.command, text_color = self.fg_cursor, cursor_color = self.bg_cursor,
|
||
|
cursor_pos = self._private_cur_pos, cursor_ul = self.ul_cursor, select_all = self.select_all,
|
||
|
prompt = self.prompt, highlighter = self.highlighter})
|
||
|
|
||
|
self._private.search_term = nil
|
||
|
|
||
|
history_check_load(self.history_path, self.history_max)
|
||
|
local history_index = history_items(self.history_path) + 1
|
||
|
|
||
|
-- The completion element to use on completion request.
|
||
|
local ncomp = 1
|
||
|
|
||
|
local command_before_comp
|
||
|
local cur_pos_before_comp
|
||
|
|
||
|
self._private.grabber = keygrabber.run(function(modifiers, key, event)
|
||
|
-- Convert index array to hash table
|
||
|
local mod = {}
|
||
|
for _, v in ipairs(modifiers) do mod[v] = true end
|
||
|
|
||
|
if event ~= "press" then
|
||
|
if self.keyreleased_callback then
|
||
|
self.keyreleased_callback(mod, key, self.command)
|
||
|
end
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- Call the user specified callback. If it returns true as
|
||
|
-- the first result then return from the function. Treat the
|
||
|
-- second and third results as a new command and new prompt
|
||
|
-- to be set (if provided)
|
||
|
if self.keypressed_callback then
|
||
|
local user_catched, new_command, new_prompt =
|
||
|
self.keypressed_callback(mod, key, self.command)
|
||
|
if new_command or new_prompt then
|
||
|
if new_command then
|
||
|
self.command = new_command
|
||
|
end
|
||
|
if new_prompt then
|
||
|
self.prompt = new_prompt
|
||
|
end
|
||
|
update(self)
|
||
|
end
|
||
|
if user_catched then
|
||
|
if self.changed_callback then
|
||
|
self.changed_callback(self.command)
|
||
|
end
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local filtered_modifiers = {}
|
||
|
|
||
|
-- User defined cases
|
||
|
if self.hooks[key] then
|
||
|
-- Remove caps and num lock
|
||
|
for _, m in ipairs(modifiers) do
|
||
|
if not gtable.hasitem(akey.ignore_modifiers, m) then
|
||
|
table.insert(filtered_modifiers, m)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
for _,v in ipairs(self.hooks[key]) do
|
||
|
if #filtered_modifiers == #v[1] then
|
||
|
local match = true
|
||
|
for _,v2 in ipairs(v[1]) do
|
||
|
match = match and mod[v2]
|
||
|
end
|
||
|
if match then
|
||
|
local cb
|
||
|
local ret, quit = v[3](self.command)
|
||
|
local original_command = self.command
|
||
|
|
||
|
-- Support both a "simple" and a "complex" way to
|
||
|
-- control if the prompt should quit.
|
||
|
quit = quit == nil and (ret ~= true) or (quit~=false)
|
||
|
|
||
|
-- Allow the callback to change the command
|
||
|
self.command = (ret ~= true) and ret or self.command
|
||
|
|
||
|
-- Quit by default, but allow it to be disabled
|
||
|
if ret and type(ret) ~= "boolean" then
|
||
|
cb = self.exe_callback
|
||
|
if not quit then
|
||
|
self._private_cur_pos = ret:wlen() + 1
|
||
|
update(self)
|
||
|
end
|
||
|
elseif quit then
|
||
|
-- No callback.
|
||
|
cb = function() end
|
||
|
end
|
||
|
|
||
|
-- Execute the callback
|
||
|
if cb then
|
||
|
exec(self, cb, original_command)
|
||
|
end
|
||
|
|
||
|
return
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Get out cases
|
||
|
if (mod.Control and (key == "c" or key == "g"))
|
||
|
or (not mod.Control and key == "Escape") then
|
||
|
self:stop()
|
||
|
return false
|
||
|
elseif (mod.Control and (key == "j" or key == "m"))
|
||
|
-- or (not mod.Control and key == "Return")
|
||
|
-- or (not mod.Control and key == "KP_Enter")
|
||
|
then
|
||
|
exec(self, self.exe_callback, self.command)
|
||
|
-- We already unregistered ourselves so we don't want to return
|
||
|
-- true, otherwise we may unregister someone else.
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- Control cases
|
||
|
if mod.Control then
|
||
|
self.select_all = nil
|
||
|
if key == "v" then
|
||
|
local selection = capi.selection()
|
||
|
if selection then
|
||
|
-- Remove \n
|
||
|
local n = selection:find("\n")
|
||
|
if n then
|
||
|
selection = selection:sub(1, n - 1)
|
||
|
end
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1) .. selection .. self.command:sub(self._private_cur_pos)
|
||
|
self._private_cur_pos = self._private_cur_pos + #selection
|
||
|
end
|
||
|
elseif key == "a" then
|
||
|
self._private_cur_pos = 1
|
||
|
elseif key == "b" then
|
||
|
if self._private_cur_pos > 1 then
|
||
|
self._private_cur_pos = self._private_cur_pos - 1
|
||
|
if have_multibyte_char_at(self.command, self._private_cur_pos) then
|
||
|
self._private_cur_pos = self._private_cur_pos - 1
|
||
|
end
|
||
|
end
|
||
|
elseif key == "d" then
|
||
|
if self._private_cur_pos <= #self.command then
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1) .. self.command:sub(self._private_cur_pos + 1)
|
||
|
end
|
||
|
elseif key == "p" then
|
||
|
if history_index > 1 then
|
||
|
history_index = history_index - 1
|
||
|
|
||
|
self.command = data.history[self.history_path].table[history_index]
|
||
|
self._private_cur_pos = #self.command + 2
|
||
|
end
|
||
|
elseif key == "n" then
|
||
|
if history_index < history_items(self.history_path) then
|
||
|
history_index = history_index + 1
|
||
|
|
||
|
self.command = data.history[self.history_path].table[history_index]
|
||
|
self._private_cur_pos = #self.command + 2
|
||
|
elseif history_index == history_items(self.history_path) then
|
||
|
history_index = history_index + 1
|
||
|
|
||
|
self.command = ""
|
||
|
self._private_cur_pos = 1
|
||
|
end
|
||
|
elseif key == "e" then
|
||
|
self._private_cur_pos = #self.command + 1
|
||
|
elseif key == "r" then
|
||
|
self._private.search_term = self._private.search_term or self.command:sub(1, self._private_cur_pos - 1)
|
||
|
for i,v in (function(a,i) return itera(-1,a,i) end), data.history[self.history_path].table, history_index do
|
||
|
if v:find(self._private.search_term,1,true) ~= nil then
|
||
|
self.command=v
|
||
|
history_index=i
|
||
|
self._private_cur_pos=#self.command+1
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
elseif key == "s" then
|
||
|
self._private.search_term = self._private.search_term or self.command:sub(1, self._private_cur_pos - 1)
|
||
|
for i,v in (function(a,i) return itera(1,a,i) end), data.history[self.history_path].table, history_index do
|
||
|
if v:find(self._private.search_term,1,true) ~= nil then
|
||
|
self.command=v
|
||
|
history_index=i
|
||
|
self._private_cur_pos=#self.command+1
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
elseif key == "f" then
|
||
|
if self._private_cur_pos <= #self.command then
|
||
|
if have_multibyte_char_at(self.command, self._private_cur_pos) then
|
||
|
self._private_cur_pos = self._private_cur_pos + 2
|
||
|
else
|
||
|
self._private_cur_pos = self._private_cur_pos + 1
|
||
|
end
|
||
|
end
|
||
|
elseif key == "h" then
|
||
|
if self._private_cur_pos > 1 then
|
||
|
local offset = 0
|
||
|
if have_multibyte_char_at(self.command, self._private_cur_pos - 1) then
|
||
|
offset = 1
|
||
|
end
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 2 - offset) .. self.command:sub(self._private_cur_pos)
|
||
|
self._private_cur_pos = self._private_cur_pos - 1 - offset
|
||
|
end
|
||
|
elseif key == "k" then
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1)
|
||
|
elseif key == "u" then
|
||
|
self.command = self.command:sub(self._private_cur_pos, #self.command)
|
||
|
self._private_cur_pos = 1
|
||
|
elseif key == "Prior" then
|
||
|
self._private.search_term = self.command:sub(1, self._private_cur_pos - 1) or ""
|
||
|
for i,v in (function(a,i) return itera(-1,a,i) end), data.history[self.history_path].table, history_index do
|
||
|
if v:find(self._private.search_term,1,true) == 1 then
|
||
|
self.command=v
|
||
|
history_index=i
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
elseif key == "Next" then
|
||
|
self._private.search_term = self.command:sub(1, self._private_cur_pos - 1) or ""
|
||
|
for i,v in (function(a,i) return itera(1,a,i) end), data.history[self.history_path].table, history_index do
|
||
|
if v:find(self._private.search_term,1,true) == 1 then
|
||
|
self.command=v
|
||
|
history_index=i
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
elseif key == "w" or key == "BackSpace" then
|
||
|
local wstart = 1
|
||
|
local wend = 1
|
||
|
local cword_start_pos = 1
|
||
|
local cword_end_pos = 1
|
||
|
while wend < self._private_cur_pos do
|
||
|
wend = self.command:find("[{[(,.:;_-+=@/ ]", wstart)
|
||
|
if not wend then wend = #self.command + 1 end
|
||
|
if self._private_cur_pos >= wstart and self._private_cur_pos <= wend + 1 then
|
||
|
cword_start_pos = wstart
|
||
|
cword_end_pos = self._private_cur_pos - 1
|
||
|
break
|
||
|
end
|
||
|
wstart = wend + 1
|
||
|
end
|
||
|
self.command = self.command:sub(1, cword_start_pos - 1) .. self.command:sub(cword_end_pos + 1)
|
||
|
self._private_cur_pos = cword_start_pos
|
||
|
elseif key == "Delete" then
|
||
|
-- delete from history only if:
|
||
|
-- we are not dealing with a new command
|
||
|
-- the user has not edited an existing entry
|
||
|
if self.command == data.history[self.history_path].table[history_index] then
|
||
|
table.remove(data.history[self.history_path].table, history_index)
|
||
|
if history_index <= history_items(self.history_path) then
|
||
|
self.command = data.history[self.history_path].table[history_index]
|
||
|
self._private_cur_pos = #self.command + 2
|
||
|
elseif history_index > 1 then
|
||
|
history_index = history_index - 1
|
||
|
|
||
|
self.command = data.history[self.history_path].table[history_index]
|
||
|
self._private_cur_pos = #self.command + 2
|
||
|
else
|
||
|
self.command = ""
|
||
|
self._private_cur_pos = 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
elseif mod.Mod1 or mod.Mod3 then
|
||
|
if key == "b" then
|
||
|
self._private_cur_pos = cword_start(self.command, self._private_cur_pos)
|
||
|
elseif key == "f" then
|
||
|
self._private_cur_pos = cword_end(self.command, self._private_cur_pos)
|
||
|
elseif key == "d" then
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1) .. self.command:sub(cword_end(self.command, self._private_cur_pos))
|
||
|
elseif key == "BackSpace" then
|
||
|
local wstart = cword_start(self.command, self._private_cur_pos)
|
||
|
self.command = self.command:sub(1, wstart - 1) .. self.command:sub(self._private_cur_pos)
|
||
|
self._private_cur_pos = wstart
|
||
|
end
|
||
|
else
|
||
|
if self.completion_callback then
|
||
|
if key == "Tab" or key == "ISO_Left_Tab" then
|
||
|
if key == "ISO_Left_Tab" or mod.Shift then
|
||
|
if ncomp == 1 then return end
|
||
|
if ncomp == 2 then
|
||
|
self.command = command_before_comp
|
||
|
self.textbox:set_font(self.font)
|
||
|
self.textbox:set_markup(prompt_text_with_cursor{
|
||
|
text = command_before_comp, text_color = self.fg_cursor, cursor_color = self.bg_cursor,
|
||
|
cursor_pos = self._private_cur_pos, cursor_ul = self.ul_cursor, select_all = self.select_all,
|
||
|
prompt = self.prompt })
|
||
|
self._private_cur_pos = cur_pos_before_comp
|
||
|
ncomp = 1
|
||
|
return
|
||
|
end
|
||
|
|
||
|
ncomp = ncomp - 2
|
||
|
elseif ncomp == 1 then
|
||
|
command_before_comp = self.command
|
||
|
cur_pos_before_comp = self._private_cur_pos
|
||
|
end
|
||
|
local matches
|
||
|
self.command, self._private_cur_pos, matches = self.completion_callback(command_before_comp, cur_pos_before_comp, ncomp)
|
||
|
ncomp = ncomp + 1
|
||
|
key = ""
|
||
|
-- execute if only one match found and autoexec flag set
|
||
|
if matches and #matches == 1 and args.autoexec then
|
||
|
exec(self, self.exe_callback)
|
||
|
return
|
||
|
end
|
||
|
elseif key ~= "Shift_L" and key ~= "Shift_R" then
|
||
|
ncomp = 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Typin cases
|
||
|
if mod.Shift and key == "Insert" then
|
||
|
local selection = capi.selection()
|
||
|
if selection then
|
||
|
-- Remove \n
|
||
|
local n = selection:find("\n")
|
||
|
if n then
|
||
|
selection = selection:sub(1, n - 1)
|
||
|
end
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1) .. selection .. self.command:sub(self._private_cur_pos)
|
||
|
self._private_cur_pos = self._private_cur_pos + #selection
|
||
|
end
|
||
|
elseif key == "Home" then
|
||
|
self._private_cur_pos = 1
|
||
|
elseif key == "End" then
|
||
|
self._private_cur_pos = #self.command + 1
|
||
|
elseif key == "BackSpace" then
|
||
|
if self._private_cur_pos > 1 then
|
||
|
local offset = 0
|
||
|
if have_multibyte_char_at(self.command, self._private_cur_pos - 1) then
|
||
|
offset = 1
|
||
|
end
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 2 - offset) .. self.command:sub(self._private_cur_pos)
|
||
|
self._private_cur_pos = self._private_cur_pos - 1 - offset
|
||
|
end
|
||
|
elseif key == "Delete" then
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1) .. self.command:sub(self._private_cur_pos + 1)
|
||
|
elseif key == "Left" then
|
||
|
self._private_cur_pos = self._private_cur_pos - 1
|
||
|
elseif key == "Right" then
|
||
|
self._private_cur_pos = self._private_cur_pos + 1
|
||
|
elseif key == "Prior" then
|
||
|
if history_index > 1 then
|
||
|
history_index = history_index - 1
|
||
|
|
||
|
self.command = data.history[self.history_path].table[history_index]
|
||
|
self._private_cur_pos = #self.command + 2
|
||
|
end
|
||
|
elseif key == "Next" then
|
||
|
if history_index < history_items(self.history_path) then
|
||
|
history_index = history_index + 1
|
||
|
|
||
|
self.command = data.history[self.history_path].table[history_index]
|
||
|
self._private_cur_pos = #self.command + 2
|
||
|
elseif history_index == history_items(self.history_path) then
|
||
|
history_index = history_index + 1
|
||
|
|
||
|
self.command = ""
|
||
|
self._private_cur_pos = 1
|
||
|
end
|
||
|
else
|
||
|
-- wlen() is UTF-8 aware but #key is not,
|
||
|
-- so check that we have one UTF-8 char but advance the cursor of # position
|
||
|
if key:wlen() == 1 then
|
||
|
if self.select_all then self.command = "" end
|
||
|
self.command = self.command:sub(1, self._private_cur_pos - 1) .. key .. self.command:sub(self._private_cur_pos)
|
||
|
self._private_cur_pos = self._private_cur_pos + #key
|
||
|
end
|
||
|
end
|
||
|
if self._private_cur_pos < 1 then
|
||
|
self._private_cur_pos = 1
|
||
|
elseif self._private_cur_pos > #self.command + 1 then
|
||
|
self._private_cur_pos = #self.command + 1
|
||
|
end
|
||
|
self.select_all = nil
|
||
|
end
|
||
|
|
||
|
update(self)
|
||
|
if self.changed_callback then
|
||
|
self.changed_callback(self.command)
|
||
|
end
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
function prompt:stop()
|
||
|
keygrabber.stop(self._private.grabber)
|
||
|
history_save(self.history_path)
|
||
|
if self.done_callback then self.done_callback() end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local function new(args)
|
||
|
args = args or {}
|
||
|
|
||
|
args.command = args.text or ""
|
||
|
args.prompt = args.prompt or ""
|
||
|
args.text = args.text or ""
|
||
|
args.font = args.font or beautiful.prompt_font or beautiful.font
|
||
|
args.bg_cursor = args.bg_cursor or beautiful.prompt_bg_cursor or beautiful.bg_focus or "white"
|
||
|
args.fg_cursor = args.fg_cursor or beautiful.prompt_fg_cursor or beautiful.fg_focus or "black"
|
||
|
args.ul_cursor = args.ul_cursor or nil
|
||
|
args.reset_on_stop = args.reset_on_stop == nil and true or args.reset_on_stop
|
||
|
args.select_all = args.select_all or nil
|
||
|
args.highlighter = args.highlighter or nil
|
||
|
args.hooks = args.hooks or {}
|
||
|
args.keypressed_callback = args.keypressed_callback or nil
|
||
|
args.changed_callback = args.changed_callback or nil
|
||
|
args.done_callback = args.done_callback or nil
|
||
|
args.history_max = args.history_max or nil
|
||
|
args.history_path = args.history_path or nil
|
||
|
args.completion_callback = args.completion_callback or nil
|
||
|
args.exe_callback = args.exe_callback or nil
|
||
|
args.textbox = args.textbox or wibox.widget.textbox()
|
||
|
|
||
|
-- Build the hook map
|
||
|
local hooks = {}
|
||
|
for _,v in ipairs(args.hooks) do
|
||
|
if #v == 3 then
|
||
|
local _,key,callback = unpack(v)
|
||
|
if type(callback) == "function" then
|
||
|
hooks[key] = hooks[key] or {}
|
||
|
hooks[key][#hooks[key]+1] = v
|
||
|
else
|
||
|
gdebug.print_warning("The hook's 3rd parameter has to be a function.")
|
||
|
end
|
||
|
else
|
||
|
gdebug.print_warning("The hook has to have 3 parameters.")
|
||
|
end
|
||
|
end
|
||
|
args.hooks = hooks
|
||
|
|
||
|
local ret = gobject({})
|
||
|
ret._private = {}
|
||
|
gtable.crush(ret, prompt)
|
||
|
gtable.crush(ret, args)
|
||
|
|
||
|
return ret
|
||
|
end
|
||
|
|
||
|
function prompt.mt:__call(...)
|
||
|
return new(...)
|
||
|
end
|
||
|
|
||
|
return setmetatable(prompt, prompt.mt)
|