Refresh generated neovim config
This commit is contained in:
@ -1,175 +1,182 @@
|
||||
local M = {}
|
||||
local uv = vim.uv or vim.loop
|
||||
local t = function(str)
|
||||
return vim.api.nvim_replace_termcodes(str, true, true, true)
|
||||
end
|
||||
|
||||
local api = vim.api
|
||||
M.waiting = false
|
||||
|
||||
local settings = {
|
||||
timeout = vim.o.timeoutlen,
|
||||
mapping = { "jk", "jj" },
|
||||
clear_empty_lines = false,
|
||||
---@type string|function
|
||||
keys = "<Esc>",
|
||||
default_mappings = true,
|
||||
mappings = {
|
||||
i = {
|
||||
-- first_key[s]
|
||||
j = {
|
||||
-- second_key[s]
|
||||
k = "<Esc>",
|
||||
j = "<Esc>",
|
||||
},
|
||||
},
|
||||
c = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
j = "<Esc>",
|
||||
},
|
||||
},
|
||||
t = {
|
||||
j = {
|
||||
k = "<C-\\><C-n>",
|
||||
},
|
||||
},
|
||||
v = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
},
|
||||
},
|
||||
s = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
local first_chars = {}
|
||||
local second_chars = {}
|
||||
|
||||
---@class State
|
||||
---@field char string
|
||||
---@field modified boolean
|
||||
|
||||
local timer
|
||||
local waiting = false
|
||||
---@type State[]
|
||||
local input_states = {}
|
||||
|
||||
---@param tbl table table to search through
|
||||
---@param element any element to search in tbl
|
||||
---@return table indices
|
||||
--- Search for indices in tbl where element occurs
|
||||
local function get_indices(tbl, element)
|
||||
local indices = {}
|
||||
for idx, value in ipairs(tbl) do
|
||||
if element == value then
|
||||
table.insert(indices, idx)
|
||||
end
|
||||
end
|
||||
return indices
|
||||
end
|
||||
|
||||
---@param keys string keys to feed
|
||||
--- Replace keys with termcodes and feed them
|
||||
local function feed(keys, mode)
|
||||
api.nvim_feedkeys(
|
||||
api.nvim_replace_termcodes(keys, true, true, true),
|
||||
mode or "n",
|
||||
false
|
||||
)
|
||||
end
|
||||
|
||||
local function start_timer()
|
||||
waiting = true
|
||||
|
||||
if timer then
|
||||
timer:stop()
|
||||
end
|
||||
|
||||
timer = vim.defer_fn(function()
|
||||
waiting = false
|
||||
end, settings.timeout)
|
||||
end
|
||||
|
||||
local function get_keys()
|
||||
-- if keys is string use it, else use it as a function
|
||||
return type(settings.keys) == "string" and settings.keys or settings.keys()
|
||||
end
|
||||
|
||||
local function check_timeout()
|
||||
if waiting then
|
||||
local current_line = api.nvim_get_current_line()
|
||||
if settings.clear_empty_lines and current_line:match("^%s+j$") then
|
||||
vim.schedule(function()
|
||||
api.nvim_set_current_line("")
|
||||
feed(get_keys(), "in")
|
||||
end)
|
||||
else
|
||||
feed("<BS><BS>" .. get_keys(), "in") -- delete the characters from the mapping
|
||||
end
|
||||
|
||||
waiting = false -- more timely
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M.check_charaters()
|
||||
local char = vim.v.char
|
||||
table.insert(input_states, { char = char, modified = vim.bo.modified })
|
||||
|
||||
local matched = false
|
||||
if #input_states >= 2 then
|
||||
---@type State
|
||||
local prev_state = input_states[#input_states - 1]
|
||||
local indices = get_indices(second_chars, char)
|
||||
-- if char == second_chars[idx] and prev_char == first_chars[idx] as well
|
||||
-- then matched = true
|
||||
for _, idx in ipairs(indices) do
|
||||
if first_chars[idx] == prev_state.char then
|
||||
matched = check_timeout()
|
||||
break
|
||||
local function unmap_keys()
|
||||
for mode, keys in pairs(settings.mappings) do
|
||||
for key, subkeys in pairs(keys) do
|
||||
pcall(vim.keymap.del, mode, key)
|
||||
for subkey, _ in pairs(subkeys) do
|
||||
pcall(vim.keymap.del, mode, subkey)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if matched then
|
||||
input_states = {}
|
||||
vim.schedule(function()
|
||||
vim.bo.modified = prev_state.modified
|
||||
end)
|
||||
-- WIP: move this into recorder.lua ?
|
||||
-- When a first_key is pressed, `recorded_key` is set to it
|
||||
-- (e.g. if jk is a mapping, when 'j' is pressed, `recorded_key` is set to 'j')
|
||||
local recorded_key = nil
|
||||
local bufmodified = nil
|
||||
local timeout_timer = uv.new_timer()
|
||||
local has_recorded = false -- See `vim.on_key` below
|
||||
local function record_key(key)
|
||||
if timeout_timer:is_active() then
|
||||
timeout_timer:stop()
|
||||
end
|
||||
bufmodified = vim.bo.modified
|
||||
recorded_key = key
|
||||
has_recorded = true
|
||||
M.waiting = true
|
||||
timeout_timer:start(settings.timeout, 0, function()
|
||||
M.waiting = false
|
||||
recorded_key = nil
|
||||
end)
|
||||
end
|
||||
|
||||
vim.on_key(function(_, typed)
|
||||
if typed == "" then
|
||||
return
|
||||
end
|
||||
if has_recorded == false then
|
||||
-- If the user presses a key that doesn't get recorded, remove the previously recorded key.
|
||||
recorded_key = nil
|
||||
return
|
||||
end
|
||||
has_recorded = false
|
||||
end)
|
||||
|
||||
-- List of keys that undo the effect of pressing first_key
|
||||
local undo_key = {
|
||||
i = "<bs>",
|
||||
c = "<bs>",
|
||||
t = "<bs>",
|
||||
}
|
||||
|
||||
local function map_keys()
|
||||
for mode, first_keys in pairs(settings.mappings) do
|
||||
local map_opts = { expr = true }
|
||||
for first_key, _ in pairs(first_keys) do
|
||||
vim.keymap.set(mode, first_key, function()
|
||||
record_key(first_key)
|
||||
return first_key
|
||||
end, map_opts)
|
||||
end
|
||||
for _, second_keys in pairs(first_keys) do
|
||||
for second_key, mapping in pairs(second_keys) do
|
||||
if not mapping then
|
||||
goto continue
|
||||
end
|
||||
vim.keymap.set(mode, second_key, function()
|
||||
-- If a first_key wasn't recorded, record second_key because it might be a first_key for another sequence.
|
||||
-- TODO: Explicitly, check if it's a starting key. I don't think that's necessary right now.
|
||||
if recorded_key == nil then
|
||||
record_key(second_key)
|
||||
return second_key
|
||||
end
|
||||
-- If a key was recorded, but it isn't the first_key for second_key, record second_key(second_key might be a first_key for another sequence)
|
||||
-- Or if the recorded_key was just a second_key
|
||||
if
|
||||
not (
|
||||
first_keys[recorded_key]
|
||||
and first_keys[recorded_key][second_key]
|
||||
)
|
||||
then
|
||||
record_key(second_key)
|
||||
return second_key
|
||||
end
|
||||
local keys = ""
|
||||
keys = keys
|
||||
.. t(
|
||||
(undo_key[mode] or "")
|
||||
.. (
|
||||
("<cmd>setlocal %smodified<cr>"):format(
|
||||
bufmodified and "" or "no"
|
||||
)
|
||||
)
|
||||
)
|
||||
if type(mapping) == "string" then
|
||||
keys = keys .. t(mapping)
|
||||
elseif type(mapping) == "function" then
|
||||
keys = keys .. t(mapping() or "")
|
||||
end
|
||||
vim.api.nvim_feedkeys(keys, "in", false)
|
||||
end, map_opts)
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- if can't find a match, and the typed char is first in a mapping, start the timeout
|
||||
if not matched and vim.tbl_contains(first_chars, char) then
|
||||
start_timer()
|
||||
end
|
||||
end
|
||||
|
||||
local function char_at(str, pos)
|
||||
return vim.fn.nr2char(vim.fn.strgetchar(str, pos))
|
||||
end
|
||||
|
||||
local function validate_settings()
|
||||
assert(type(settings.mapping) == "table", "Mapping must be a table.")
|
||||
|
||||
for _, mapping in ipairs(settings.mapping) do
|
||||
-- replace all multibyte chars to `A` char
|
||||
local length = #vim.fn.substitute(mapping, ".", "A", "g")
|
||||
assert(length == 2, "Mapping must be 2 keys.")
|
||||
end
|
||||
|
||||
if settings.timeout then
|
||||
assert(type(settings.timeout) == "number", "Timeout must be a number.")
|
||||
assert(
|
||||
settings.timeout >= 100,
|
||||
"Timeout must be greater than or equal to 100."
|
||||
)
|
||||
end
|
||||
|
||||
assert(
|
||||
vim.tbl_contains({ "string", "function" }, type(settings.keys)),
|
||||
"Keys must be a function or string."
|
||||
)
|
||||
end
|
||||
|
||||
function M.setup(update)
|
||||
if update and update.default_mappings == false then
|
||||
settings.mappings = {}
|
||||
end
|
||||
settings = vim.tbl_deep_extend("force", settings, update or {})
|
||||
-- if mapping is a string (single mapping) make it a table
|
||||
if type(settings.mapping) == "string" then
|
||||
settings.mapping = { settings.mapping }
|
||||
if settings.keys or settings.clear_empty_lines then
|
||||
vim.notify(
|
||||
"[better-escape.nvim]: Rewrite! Check: https://github.com/max397574/better-escape.nvim",
|
||||
vim.log.levels.WARN,
|
||||
{}
|
||||
)
|
||||
end
|
||||
local ok, msg = pcall(validate_settings)
|
||||
if ok then
|
||||
-- create tables with the first and seconds chars of the mappings
|
||||
for _, shortcut in ipairs(settings.mapping) do
|
||||
vim.cmd("silent! iunmap " .. shortcut)
|
||||
table.insert(first_chars, char_at(shortcut, 0))
|
||||
table.insert(second_chars, char_at(shortcut, 1))
|
||||
if settings.mapping then
|
||||
vim.notify(
|
||||
"[better-escape.nvim]: Rewrite! Check: https://github.com/max397574/better-escape.nvim",
|
||||
vim.log.levels.WARN,
|
||||
{}
|
||||
)
|
||||
if type(settings.mapping) == "string" then
|
||||
settings.mapping = { settings.mapping }
|
||||
end
|
||||
for _, mapping in ipairs(settings.mappings) do
|
||||
settings.mappings.i[mapping:sub(1, 2)] = {}
|
||||
settings.mappings.i[mapping:sub(1, 1)][mapping:sub(2, 2)] =
|
||||
settings.keys
|
||||
end
|
||||
|
||||
vim.cmd([[
|
||||
augroup better_escape
|
||||
autocmd!
|
||||
autocmd InsertCharPre * lua require"better_escape".check_charaters()
|
||||
augroup END
|
||||
]])
|
||||
else
|
||||
vim.notify("Error(better-escape.nvim): " .. msg, vim.log.levels.ERROR)
|
||||
end
|
||||
unmap_keys()
|
||||
map_keys()
|
||||
end
|
||||
|
||||
return setmetatable(M, {
|
||||
__index = function(_, k)
|
||||
if k == "waiting" then
|
||||
return waiting
|
||||
end
|
||||
end,
|
||||
})
|
||||
return M
|
||||
|
||||
@ -1,21 +1,24 @@
|
||||
# 🚪better-escape.nvim
|
||||
# better-escape.nvim
|
||||
|
||||
This plugin is the lua version of [better_escape.vim](https://github.com/jdhao/better-escape.vim),
|
||||
with some additional features and optimizations
|
||||

|
||||
|
||||
A lot of people have mappings like `jk` or `jj` to escape insert mode.
|
||||
The problem with this mappings is that whenever you type a `j`, neovim wait about 100-500ms (depending on your timeoutlen) to see, if you type a `j` or a `k` because these are mapped.
|
||||
Only after that time the `j` will be inserted.
|
||||
Then you always get a delay when typing a `j`.
|
||||
A lot of people have mappings like `jk` or `jj` to escape insert mode. The
|
||||
problem with this mappings is that whenever you type a `j`, neovim wait about
|
||||
100-500ms (depending on your timeoutlen) to see, if you type a `j` or a `k`
|
||||
because these are mapped. Only after that time the `j` will be inserted. Then
|
||||
you always get a delay when typing a `j`.
|
||||
|
||||
This looks like this (see below for a gif):
|
||||
An example where this has a big impact is e.g. telescope. Because the characters
|
||||
which are mapped aren't really inserted at first the whole filtering isn't
|
||||
instant.
|
||||
|
||||

|
||||

|
||||
|
||||
## ✨Features
|
||||
|
||||
- Escape without getting delay when typing in insert mode
|
||||
- Customizable mapping and timeout
|
||||
- Write mappings in many modes without having a delay when typing
|
||||
- Customizable timeout
|
||||
- Map key sequences and lua functions
|
||||
- Use multiple mappings
|
||||
- Really small and fast
|
||||
|
||||
@ -24,8 +27,8 @@ This looks like this (see below for a gif):
|
||||
Use your favourite package manager and call the setup function.
|
||||
|
||||
```lua
|
||||
-- lua with packer.nvim
|
||||
use {
|
||||
-- lua with lazy.nvim
|
||||
{
|
||||
"max397574/better-escape.nvim",
|
||||
config = function()
|
||||
require("better_escape").setup()
|
||||
@ -33,31 +36,107 @@ use {
|
||||
}
|
||||
```
|
||||
|
||||
## ❗Rewrite
|
||||
|
||||
There was a big rewrite which allows much more flexibility now. You can now
|
||||
define mappings in most modes and also use functions.
|
||||
|
||||
The biggest change was that the `mapping` config option was removed. Check the
|
||||
default configuration below to see the new structure.
|
||||
|
||||
This also deprecated the `clear_empty_lines` setting. You can replicate this
|
||||
behavior by setting a mapping to a function like this:
|
||||
|
||||
```lua
|
||||
-- `k` would be the second key of a mapping
|
||||
k = function()
|
||||
vim.api.nvim_input("<esc>")
|
||||
local current_line = vim.api.nvim_get_current_line()
|
||||
if current_line:match("^%s+j$") then
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_set_current_line("")
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## ⚙️Customization
|
||||
|
||||
Call the setup function with your options as arguments.
|
||||
|
||||
After the rewrite you can also use any function. So you could for example map
|
||||
`<space><tab>` to jump with luasnip like this:
|
||||
|
||||
```lua
|
||||
i = {
|
||||
[" "] = {
|
||||
["<tab>"] = function()
|
||||
-- Defer execution to avoid side-effects
|
||||
vim.defer_fn(function()
|
||||
-- set undo point
|
||||
vim.o.ul = vim.o.ul
|
||||
require("luasnip").expand_or_jump()
|
||||
end, 1)
|
||||
end
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Disable mappings
|
||||
To disable keys set them to `false` in the configuration.
|
||||
You can also disable all default mappings by setting the `default_mappings` option to false.
|
||||
|
||||
<details>
|
||||
<summary>Default Config</summary>
|
||||
|
||||
```lua
|
||||
-- lua, default settings
|
||||
require("better_escape").setup {
|
||||
mapping = {"jk", "jj"}, -- a table with mappings to use
|
||||
timeout = vim.o.timeoutlen, -- the time in which the keys must be hit in ms. Use option timeoutlen by default
|
||||
clear_empty_lines = false, -- clear line after escaping if there is only whitespace
|
||||
keys = "<Esc>", -- keys used for escaping, if it is a function will use the result everytime
|
||||
-- example(recommended)
|
||||
-- keys = function()
|
||||
-- return vim.api.nvim_win_get_cursor(0)[2] > 1 and '<esc>l' or '<esc>'
|
||||
-- end,
|
||||
timeout = vim.o.timeoutlen,
|
||||
default_mappings = true,
|
||||
mappings = {
|
||||
i = {
|
||||
j = {
|
||||
-- These can all also be functions
|
||||
k = "<Esc>",
|
||||
j = "<Esc>",
|
||||
},
|
||||
},
|
||||
c = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
j = "<Esc>",
|
||||
},
|
||||
},
|
||||
t = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
j = "<Esc>",
|
||||
},
|
||||
},
|
||||
v = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
},
|
||||
},
|
||||
s = {
|
||||
j = {
|
||||
k = "<Esc>",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## API
|
||||
|
||||
`require("better_escape").waiting` is a boolean indicating that it's waiting for
|
||||
a mapped sequence to complete.
|
||||
|
||||
<details>
|
||||
<summary>statusline example</summary>
|
||||
<summary>Statusline example</summary>
|
||||
|
||||
```lua
|
||||
function escape_status()
|
||||
@ -68,28 +147,15 @@ end
|
||||
|
||||
</details>
|
||||
|
||||
## 👀Demo
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 🎓How it works
|
||||
|
||||
With the mappings there are two tables created.
|
||||
One contains all first characters and one all second characters.
|
||||
Whenever you type a character the plugin checks if it's in any of the two tables
|
||||
If it is in the first one, the plugin starts a timer.
|
||||
If it is in the second, the plugin checks whether the character you typed before is in the table with the first characters.
|
||||
|
||||
If this is the case the plugin gets all the indices where the characters are in the tables, then is searches for matches.
|
||||
If there is a match, that means that there is a mapping which has the typed character as second and the previous typed character as first character.
|
||||
The plugin then checks if the time passed since the first character was types is smaller than `timoutlen`.
|
||||
If this is the case the two characters get deleted and `keys` get feed or executed.
|
||||
|
||||
Like this it is possible that the characters really get inserted and therefore you have no delay after typing one of the characters of your mapping.
|
||||
With the `timeoutlen` it's still possible to type the characters of your mappings.
|
||||
|
||||
## ❤️ Support
|
||||
If you like the projects I do and they can help you in your life you can support my work with [github sponsors](https://github.com/sponsors/max397574).
|
||||
Every support motivates me to continue working on my open source projects.
|
||||
|
||||
If you like the projects I do and they can help you in your life you can support
|
||||
my work with [github sponsors](https://github.com/sponsors/max397574). Every
|
||||
support motivates me to continue working on my open source projects.
|
||||
|
||||
## Similar plugins
|
||||
|
||||
The old version of this plugin was a lua version of
|
||||
[better_escape.vim](https://github.com/jdhao/better-escape.vim), with some
|
||||
additional features and optimizations. This changed with the rewrite though. Now
|
||||
it has much more features.
|
||||
|
||||
Reference in New Issue
Block a user