1--- Add readline bindings to the input bar. 2-- 3-- This module adds a set of readline-inspired bindings to the input bar. These 4-- bindings are not bound to any specific mode, but are automatically activated 5-- whenever the input bar has focus. 6-- 7-- @module readline 8-- @copyright 2017 Aidan Holm <aidanholm@gmail.com> 9 10local window = require "window" 11local lousy = require "lousy" 12local prev_glyph = lousy.util.string.prev_glyph 13local next_glyph = lousy.util.string.next_glyph 14 15local _M = {} 16 17local yank_ring = "" 18 19local actions = { 20 paste = { 21 func = function (w) 22 local str = luakit.selection.primary 23 if not str then return end 24 local i = w.ibar.input 25 local text = i.text 26 local pos = i.position 27 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1) 28 i.text = left .. str .. right 29 i.position = pos + #str 30 end, 31 desc = "Insert contents of primary selection at cursor position.", 32 }, 33 del_word = { 34 func = function (w) 35 local i = w.ibar.input 36 local text = i.text 37 local pos = i.position 38 if text and utf8.len(text) > 1 and pos > 1 then 39 local left = string.sub(text, 2, utf8.offset(text, pos)) 40 local right = string.sub(text, utf8.offset(text, pos + 1)) 41 if not string.find(left, "%s") then 42 left = "" 43 elseif string.find(left, "%S+%s*$") then 44 left = string.sub(left, 0, string.find(left, "%S+%s*$") - 1) 45 elseif string.find(left, "%W+%s*$") then 46 left = string.sub(left, 0, string.find(left, "%W+%s*$") - 1) 47 end 48 i.text = string.sub(text, 1, 1) .. left .. right 49 i.position = utf8.len(left) + 1 50 end 51 end, 52 desc = "Delete previous word.", 53 }, 54 del_word_backward = { 55 func = function (w) 56 local i = w.ibar.input 57 local text = i.text 58 local pos = i.position 59 if text and utf8.len(text) > 1 and pos > 1 then 60 local right = string.sub(text, utf8.offset(text, pos + 1)) 61 pos = utf8.offset(text, pos) - 1 62 while true 63 do 64 local new_pos, glyph = prev_glyph(text, pos) 65 if not new_pos or (glyph:len() == 1 and not glyph:find("%w")) then 66 break 67 end 68 pos = new_pos 69 end 70 local left = "" 71 if pos then 72 left = text:sub(2, pos) 73 end 74 i.text = text:sub(1, 1) .. left .. right 75 i.position = utf8.len(left) + 1 76 end 77 end, 78 desc = "Delete word backward.", 79 }, 80 del_word_forward = { 81 func = function (w) 82 local i = w.ibar.input 83 local text = i.text 84 local pos = i.position 85 if text and utf8.len(text) > 1 and pos < utf8.len(text) then 86 -- include current character 87 local left = text:sub(1, utf8.offset(text, pos + 1) - 1) 88 -- at least delete one character 89 pos = utf8.offset(text, pos + 2) 90 while true 91 do 92 local new_pos, glyph = next_glyph(text, pos) 93 if not new_pos or (glyph:len() == 1 and not glyph:find("%w")) then 94 break 95 end 96 pos = new_pos 97 end 98 local right 99 if pos then 100 right = text:sub(pos) 101 else 102 right = "" 103 end 104 i.text = left .. right 105 i.position = utf8.len(left) 106 end 107 end, 108 desc = "Delete word forward.", 109 }, 110 del_line = { 111 func = function (w) 112 local i = w.ibar.input 113 if not string.match(i.text, "^[:/?]$") then 114 yank_ring = string.sub(i.text, 2) 115 i.text = string.sub(i.text, 1, 1) 116 i.position = -1 117 end 118 end, 119 desc = "Delete until beginning of current line.", 120 }, 121 del_to_eol = { 122 func = function (w) 123 local i = w.ibar.input 124 local text = i.text 125 local pos = i.position 126 if not string.match(text, "^[:/?]$") then 127 i.text = string.sub(text, 1, pos) 128 i.position = pos 129 end 130 end, 131 desc = "Delete to the end of current line.", 132 }, 133 del_backward_char = { 134 func = function (w) 135 local i = w.ibar.input 136 local text = i.text 137 local pos = i.position 138 139 if pos > 1 then 140 i.text = string.sub(text, 0, pos - 1) .. string.sub(text, pos + 1) 141 i.position = pos - 1 142 end 143 end, 144 desc = "Delete character to the left.", 145 }, 146 del_forward_char = { 147 func = function (w) 148 local i = w.ibar.input 149 local text = i.text 150 local pos = i.position 151 152 i.text = string.sub(text, 0, pos) .. string.sub(text, pos + 2) 153 i.position = pos 154 end, 155 desc = "Delete character to the right.", 156 }, 157 beg_line = { 158 func = function (w) 159 local i = w.ibar.input 160 i.position = 1 161 end, 162 desc = "Move cursor to beginning of current line.", 163 }, 164 end_line = { 165 func = function (w) 166 local i = w.ibar.input 167 i.position = -1 168 end, 169 desc = "Move cursor to end of current line.", 170 }, 171 forward_char = { 172 func = function (w) 173 local i = w.ibar.input 174 i.position = i.position + 1 175 end, 176 desc = "Move cursor forward one character.", 177 }, 178 backward_char = { 179 func = function (w) 180 local i = w.ibar.input 181 local pos = i.position 182 if pos > 1 then 183 i.position = pos - 1 184 end 185 end, 186 desc = "Move cursor backward one character.", 187 }, 188 forward_word = { 189 func = function (w) 190 local i = w.ibar.input 191 local text = i.text 192 local pos = i.position 193 if text and utf8.len(text) > 1 then 194 pos = pos + 1 195 local raw_pos = utf8.offset(text, pos + 1) 196 while true 197 do 198 local glyph 199 raw_pos, glyph = next_glyph(text, raw_pos) 200 if not raw_pos or (glyph:len() == 1 and not glyph:find("%w")) then 201 break 202 end 203 pos = pos + 1 204 end 205 i.position = pos 206 end 207 end, 208 desc = "Move cursor forward one word.", 209 }, 210 backward_word = { 211 func = function (w) 212 local i = w.ibar.input 213 local text = i.text 214 local pos = i.position 215 if text and utf8.len(text) > 1 and pos > 1 then 216 local raw_pos = utf8.offset(text, pos) - 1 217 while true 218 do 219 local glyph 220 raw_pos, glyph = prev_glyph(text, raw_pos) 221 pos = pos - 1 222 if not raw_pos or (glyph:len() == 1 and not glyph:find("%w")) then 223 break 224 end 225 end 226 if not pos then 227 i.position = 1 228 else 229 i.position = pos 230 end 231 end 232 end, 233 desc = "Move cursor backward one word.", 234 }, 235 236 yank_text = { 237 func = function (w) 238 local i = w.ibar.input 239 local text = i.text 240 local pos = i.position 241 local left, right = string.sub(text, 1, pos), string.sub(text, pos+1) 242 i.text = left .. yank_ring .. right 243 i.position = pos + #yank_ring 244 end, 245 desc = "Yank the most recently killed text into the input bar, at the cursor.", 246 }, 247} 248 249--- Table of bindings that are added to the input bar. 250-- @readwrite 251-- @type table 252_M.bindings = { 253 { "<Shift-Insert>", actions.paste , {} }, 254 { "<Control-w>", actions.del_word , {} }, 255 { "<Mod1-BackSpace>", actions.del_word_backward , {} }, 256 { "<Mod1-d>", actions.del_word_forward , {} }, 257 { "<Control-u>", actions.del_line , {} }, 258 { "<Control-o>", actions.del_to_eol , {} }, 259 { "<Control-h>", actions.del_backward_char , {} }, 260 { "<Control-d>", actions.del_forward_char , {} }, 261 { "<Control-a>", actions.beg_line , {} }, 262 { "<Control-e>", actions.end_line , {} }, 263 { "<Control-f>", actions.forward_char , {} }, 264 { "<Control-b>", actions.backward_char , {} }, 265 { "<Mod1-f>", actions.forward_word , {} }, 266 { "<Mod1-b>", actions.backward_word , {} }, 267 { "<Control-y>", actions.yank_text , {} }, 268} 269 270window.add_signal("init", function (w) 271 w.ibar.input:add_signal("key-press", function (input, mods, key) 272 local ww = assert(window.ancestor(input)) -- Unlikely, but just in case 273 local success, match = xpcall( 274 function () return lousy.bind.hit(ww, _M.bindings, mods, key, {}) end, 275 function (err) w:error(debug.traceback(err, 2)) end) 276 if success and match then 277 return true 278 end 279 end) 280end) 281 282-- Check for old config/window.lua 283for k in pairs(actions) do 284 k = k == "paste" and "insert_cmd" or k 285 for wm in pairs(window.methods) do 286 if k == wm then 287 msg.warn("detected old window.lua: method '%s'", wm) 288 msg.warn(" readline bindings have been moved to readline.lua") 289 msg.warn(" you should remove this method from your config/window.lua") 290 end 291 end 292end 293 294return _M 295 296-- vim: et:sw=4:ts=8:sts=4:tw=80 297