1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
|
--- Default mode configuration for luakit.
--
-- This module defines a core set of modes each luakit window can be in.
-- Different modes recognize different keybindings.
--
-- @module modes
-- @author Aidan Holm <aidanholm@gmail.com>
-- @author Mason Larobina <mason.larobina@gmail.com>
-- @copyright 2017 Aidan Holm <aidanholm@gmail.com>
-- @copyright 2010 Mason Larobina <mason.larobina@gmail.com>
local _M = {}
local window = require("window")
local webview = require("webview")
-- Table of modes and their callback hooks
local modes = {}
local lousy = require "lousy"
local join = lousy.util.table.join
local order = 0
--- Add a new mode table (optionally merges with original mode)
-- @tparam string name The name of the mode.
-- @tparam[opt] string desc The description of the mode.
-- @tparam table mode A table that defines the mode.
-- @tparam[opt] boolean replace `true` if any existing mode with the same name should
-- be replaced, and `false` if the pre-existing mode should be extended.
-- @default `false`
_M.new_mode = function (name, desc, mode, replace)
assert(string.match(name, "^[%w-_]+$"), "invalid mode name: " .. name)
-- Detect optional description
if type(desc) == "table" then
desc, mode, replace = nil, desc, mode
end
local traceback = debug.traceback("Creation traceback:", 2)
order = order + 1
modes[name] = join({ order = order, traceback = traceback },
(not replace and modes[name]) or {}, mode or {},
{ name = name, desc = desc })
end
--- Get a mode by name.
-- @tparam string name The name of the mode to retrieve.
-- @treturn table The mode table for the named mode.
_M.get_mode = function(name) return modes[name] end
--- Get all modes.
-- @treturn table A clone of the full table of modes.
_M.get_modes = function () return lousy.util.table.clone(modes) end
-- Attach window & input bar signals for mode hooks
window.add_signal("init", function (w)
-- Calls the `enter` and `leave` mode hooks.
w:add_signal("mode-changed", function (_, name, ...)
local leave = (w.mode or {}).leave
-- Get new modes functions/hooks/data
local mode = assert(modes[name], "invalid mode: " .. name)
-- Call last modes leave hook.
if leave then leave(w) end
-- Create w.mode object
w.mode = mode
-- Update window binds
w:update_binds(name)
-- Call new modes enter hook.
if mode.enter then mode.enter(w, ...) end
w.last_mode_entered = mode
w:emit_signal("mode-entered", mode)
end)
local input = w.ibar.input
-- Calls the changed hook on input widget changed.
input:add_signal("changed", function ()
local changed = w.mode.changed
-- the w:set_input() in normal mode's enter function would create a
-- changed signal which would run before the next mode's enter
-- function, usually causing a change back to normal mode before the
-- next mode's enter function actually ran.
-- here, we only run the changed callback if the current mode matches
-- the last mode entered.
if changed and w.last_mode_entered == w.mode then
changed(w, input.text)
end
end)
input:add_signal("property::position", function ()
local move_cursor = w.mode.move_cursor
if move_cursor then move_cursor(w, input.position) end
end)
-- Calls the `activate` hook on input widget activate.
input:add_signal("activate", function ()
local mode = w.mode
if mode and mode.activate then
local text = input.text
-- Activates the mode.
if mode.activate(w, text) == false then return end
-- Prevents recording command history if in a private tab, or
-- recording the `:priv-tabopen` command itself.
if w.view and w.view.private then return end
local hist = mode.history
-- Check if last history is identical
if hist and hist.items and hist.items[hist.len or -1] ~= text then
table.insert(hist.items, text)
-- Dump history
local t = {}
for k, v in pairs(modes) do
if v.history then
t[k] = v.history.items
end
end
local f = io.open(luakit.data_dir .. "/command-history", "w")
if f then
f:write(lousy.pickle.pickle(t))
f:close()
end
end
end
end)
end)
-- Add mode related window methods
window.methods.set_mode = lousy.mode.set
local mget = lousy.mode.get
window.methods.is_mode = function (w, name) return name == mget(w) end
webview.add_signal("init", function (view)
view.can_focus = false
end)
-- Setup normal mode
_M.new_mode("normal", [[When luakit first starts you will find yourself in this
mode.]], {
enter = function (w)
w:set_prompt()
w:set_input()
if w.view then w.view.can_focus = false end
w.win:focus()
end,
})
_M.new_mode("all", "Special meta-mode in which the bindings for this mode are present in all modes.")
-- Setup insert mode
_M.new_mode("insert", [[When clicking on form fields luakit will enter the insert
mode which allows you to enter text in form fields without accidentally
triggering normal mode bindings.]], {
enter = function (w)
w:set_prompt("-- INSERT --")
w:set_input()
w.view.can_focus = true
w.view:focus()
end,
-- Send key events to webview
passthrough = true,
})
_M.new_mode("passthrough", [[Luakit will pass every key event to the WebView
until the user presses Escape.]], {
enter = function (w)
w:set_prompt("-- PASS THROUGH --")
w:set_input()
w.view.can_focus = true
w.view:focus()
end,
-- Send key events to webview
passthrough = true,
-- Don't exit mode when clicking outside of form fields
reset_on_focus = false,
-- Don't exit mode on navigation
reset_on_navigation = false,
})
-- Setup command mode
_M.new_mode("command", [[Enter commands.]], {
enter = function (w)
w:set_prompt()
w:set_input(":")
end,
changed = function (w, text)
-- Auto-exit command mode if user backspaces ":" in the input bar.
if not string.match(text, "^:") then w:set_mode() end
end,
activate = function (w, text)
w:set_mode()
local cmd = string.sub(text, 2)
if not string.find(cmd, "%S") then return end
local success, match = xpcall(
function () return w:match_cmd(cmd) end,
function (err) w:error(debug.traceback(err, 3)) end)
if success and not match then
w:error(string.format("Not a browser command: %q", cmd))
end
end,
history = {maxlen = 50},
})
_M.new_mode("lua", [[Execute arbitrary Lua commands within the luakit
environment.]], {
enter = function (w)
w:set_prompt(">")
w:set_input("")
end,
activate = function (w, text)
w:set_input("")
local ret = assert(loadstring("return function(w) return "..text.." end"))()(w)
if ret then print(ret) end
end,
history = {maxlen = 50},
})
--- Callback type for a bind action.
-- @callback bind_action_cb
-- @tparam table w The window table for the window on which the binding was
-- activated.
-- @tparam table opts Combined table of bind-time and invocation-time options.
-- @tparam table bind_opts Table of invocation-time options.
-- @treturn boolean `false` if bind matching should be continued, as if this
-- action were not bound. Any other value stops bind matching, which is usually
-- what you want.
--- Add a set of binds to one or more modes. Any pre-existing binds with the
-- same trigger will be removed automatically.
--
-- # Example
--
-- The following code snippet will rebind `Control-c` to copy text selected with
-- the mouse, and the default binding for `Control-c` will be removed.
--
-- modes.add_binds("normal", {
-- { "<Control-c>", "Copy selected text.", function ()
-- luakit.selection.clipboard = luakit.selection.primary
-- end},
-- })
--
-- # Bind format
--
-- Every item in the `binds` array must be a table that defines a single binding
-- between a trigger and an action. Each entry must have the following form:
--
-- { trigger, description, action, options }
--
-- - `trigger` is a string describing the combination of keys/modifiers/buttons
-- that will trigger the associated action.
-- - `description` is an optional string. Any description will show up in the
-- introspector.
-- - `action` is a function that will be called when the binding is
-- activated, of type @ref{bind_action_cb}.
-- - `options` is a table of bind-time options passed to the `action` callback.
--
-- @tparam table|string mode The name of the mode, or an array of mode names.
-- @tparam table binds An array of binds to add to each of the named modes.
_M.add_binds = function (mode, binds)
mode = type(mode) ~= "table" and {mode} or mode
for _, name in ipairs(mode) do
local mdata = assert(_M.get_mode(name), "mode '"..name.."' doesn't exist")
mdata.binds = mdata.binds or {}
for _, m in ipairs(binds) do
local bind, desc, action, opts = unpack(m)
if type(desc) ~= "string" then
desc, action, opts = nil, desc, action
end
if type(desc) == "string" or type(action) == "function" then -- Make ad-hoc action
action = type(action) == "table" and lousy.util.table.clone(action) or { func = action }
action.desc = desc
end
lousy.bind.add_bind(mdata.binds, bind, action, opts)
end
end
end
--- Remove a set of binds from one or more modes.
--
-- # Example
--
-- -- Disable extra zooming commands
-- modes.remove_binds("normal", { "zi", "zo", "zz" })
--
-- -- Disable passthrough mode
-- modes.remove_binds({"normal", "insert"}, { "<Control-z>" })
--
-- @tparam table|string mode The name of the mode, or an array of mode names.
-- @tparam table binds An array of binds to remove from each of the named modes.
_M.remove_binds = function (mode, binds)
mode = type(mode) ~= "table" and {mode} or mode
for _, name in ipairs(mode) do
local mdata = assert(_M.get_mode(name), "mode '"..name.."' doesn't exist")
for _, bind in ipairs(binds) do
lousy.bind.remove_bind(mdata.binds or {} , bind)
end
end
end
--- Bind an existing key or command to a new binding.
--
-- # Example
--
-- -- Add an additional zooming command binding
-- modes.remap_binds("normal", {
-- { "<Control-=>", "zi", true },
-- })
--
-- # Bind format
--
-- Every item in the `binds` array must be a table that defines a single rebind
-- from an existing trigger to a new one. Each entry must have the following form:
--
-- { new, old, keep }
--
-- - `new` is a string describing the combination of keys/modifiers/buttons
-- that will trigger the associated action.
-- - `old` is a string describing the previous trigger of the action.
-- - `keep` is an optional argument that determines whether the existing binding
-- should remain. Defaults to `false` if not present.
--
-- @tparam table|string mode The name of the mode, or an array of mode names.
-- @tparam table binds An array of binds to remap
_M.remap_binds = function(mode, binds)
mode = type(mode) ~= "table" and {mode} or mode
for _, name in ipairs(mode) do
local mdata = assert(_M.get_mode(name), "mode '"..name.."' doesn't exist")
for _, bind in ipairs(binds) do
local new, old, keep = unpack(bind)
lousy.bind.remap_bind(mdata.binds or {}, new, old, keep)
end
end
end
--- Add a set of commands to the built-in `command` mode.
-- @tparam table|string mode The name of the mode, or an array of mode names.
-- @tparam table binds An raray of binds to add to each of the named modes.
_M.add_cmds = function (binds)
for _, m in ipairs(binds) do
local b = m[1]
if b and b:match("^[^<^]") then
for _, c in ipairs(lousy.util.string.split(b, ",%s+")) do
assert(c:match("^:[%[%]%w%-!]+$"), "Bad command binding '" .. b .. "'")
end
end
end
_M.add_binds("command", binds)
end
return _M
-- vim: et:sw=4:ts=8:sts=4:tw=80
|