Skip to content

Commit

Permalink
feat(keymaps): add mappings for easier navigation
Browse files Browse the repository at this point in the history
To make it easier to navigate the buffer and manipulate its contents,
this commit adds a set of default keymaps, which provide basic
navigation:

- <c-i>, sets the cursor to the start
- <c-a>, sets the cursor to the end
- <c-e>, sets the cursor to the word end
- <c-b>, sets the cursor to the word start
- <c-c>, clears the line
- <c-u>, undo changes
- <c-r>, redo undone changes

Closes: GH-3
Signed-off-by: Filip Dutescu <filip.dutescu@gmail.com>
  • Loading branch information
filipdutescu committed Oct 25, 2021
1 parent e1be48f commit 2eae961
Show file tree
Hide file tree
Showing 12 changed files with 514 additions and 6 deletions.
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ of the settings table.
### Renamer setup structure

```lua
local mappings_utils = require('renamer.mappings.utils')
require('renamer').setup {
-- The popup title, shown if `border` is true
title = 'Rename',
Expand All @@ -119,8 +120,20 @@ require('renamer').setup {
border = true,
-- The characters which make up the border
border_chars = { '', '', '', '', '', '', '', '' },
-- The string to be used as a prompt prefix. It also sets the buffer to be a prompt
-- The string to be used as a prompt prefix. It also sets the buffer to
-- be a prompt
prefix = '',
-- The keymaps available while in the `renamer` buffer. The example below
-- overrides the default values, but you can add others as well.
mappings = {
['<c-i>'] = mappings_utils.set_cursor_to_start,
['<c-a>'] = mappings_utils.set_cursor_to_end,
['<c-e>'] = mappings_utils.set_cursor_to_word_end,
['<c-b>'] = mappings_utils.set_cursor_to_word_start,
['<c-c>'] = mappings_utils.clear_line,
['<c-u>'] = mappings_utils.undo,
['<c-r>'] = mappings_utils.redo,
},
}
```

Expand All @@ -135,7 +148,19 @@ hi default link RenamerPrefix Identifier

## Default mappings

TODO - Should add mappings to make it easy to edit and select text in the prompt.
Mappings are fully customizable. The default mappings are intended to be familiar
to users and mimic the default behaviour of their normal mode equivalents.

| Mappings | Action |
|----------|------------------------------|
| `<c-i>` | Go to the start of the line. |
| `<c-a>` | Go to the end of the line. |
| `<c-e>` | Go to the end of the word. |
| `<c-b>` | Go to the start of the word. |
| `<c-c>` | Clear the buffer line. |
| `<c-u>` | Undo changes. |
| `<c-r>` | Redo undone changes. |


## Media

Expand Down
78 changes: 76 additions & 2 deletions doc/renamer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ https://github.com/filipdutescu/renamer.nvim

:h renamer.setup
:h renamer.rename
:h renamer.mappings



Expand All @@ -30,6 +31,7 @@ renamer.setup({opts})

Usage:
>
local mappings_utils = require('renamer.mappings.utils')
require('renamer').setup {
-- The popup title, shown if `border` is true
title = 'Rename',
Expand All @@ -39,8 +41,20 @@ renamer.setup({opts})
border = true,
-- The characters which make up the border
border_chars = { '─', '│', '─', '│', '╭', '╮', '╯', '╰' },
-- The string to be used as a prompt prefix. It also sets the buffer to be a prompt
-- The string to be used as a prompt prefix. It also sets the buffer to
-- be a prompt
prefix = '',
-- The keymaps available while in the `renamer` buffer. The example below
-- overrides the default values, but you can add others as well.
mappings = {
['<c-i>'] = mappings_utils.set_cursor_to_start,
['<c-a>'] = mappings_utils.set_cursor_to_end,
['<c-e>'] = mappings_utils.set_cursor_to_word_end,
['<c-b>'] = mappings_utils.set_cursor_to_word_start,
['<c-c>'] = mappings_utils.clear_line,
['<c-u>'] = mappings_utils.undo,
['<c-r>'] = mappings_utils.redo,
},
}
<

Expand Down Expand Up @@ -69,6 +83,10 @@ renamer.setup({opts})
{prefix} (string) the character to be used as a prompt
prefix (default: '', meaning the popup is
not defined as a prompt)
{mappings} (table) the keymaps that should be available in
the buffer, alongside their respective
actions (default: see
|lua/renamer/mappings/init.lua|)



Expand All @@ -87,6 +105,62 @@ renamer.rename()
Usage:
>
require('renamer').rename()
<
===============================================================================
*renamer.mappings*

Mappings are different key combinations used to help you navigate the `renamer`
buffer window. The following are mapped by default and can be overriden as
described in |renamer.setup|:

*<c-i>*
<c-i> Go to the start of the line (or buffer, since it only
contains one line). Behind the scenes, it uses the "I"
normal mode keymap.

See `:help I`

*<c-a>*
<c-a> Go to the end of the line (or buffer, since it only
contains one line). Behind the scenes, it uses the "A"
normal mode keymap.

See `:help A`

*<c-e>*
<c-e> Go to the end of the word currently under the cursor.
Behind the scenes, it uses the "e" normal mode keymap.

See `:help e`

*<c-b>*
<c-b> Go to the beginning of the word currently under the
cursor. Behind the scenes, it uses the "b" normal mode
keymap.

See `:help b`

*<c-c>*
<c-c> Clears the line (or buffer, since it only contains one
line). Behind the scenes, it uses the "0C" normal mode
keymaps.

See `:help 0`, `:help C`

*<c-u>*
<c-u> Undo changes. Behind the scenes, it uses the "u" normal
mode keymap.

See `:help u`

*<c-r>*
<c-r> Redo changes that were undone. Behind the scenes, it uses
the "<c-r>" normal mode keymap.

See ``:help CTRL-R`


vim:tw=78:ts=8:ft=help:norl:
9 changes: 8 additions & 1 deletion lua/renamer/defaults.lua
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
local mappings = require 'renamer.mappings'

--- @class Defaults
--- @field public title string
--- @field public padding integer[]
--- @field public border boolean
--- @field public border_chars string[]
--- @field public prefix string
--- @field public mappings string
local defaults = {
-- The popup title, shown if `border` is true
title = 'Rename',
Expand All @@ -13,8 +16,12 @@ local defaults = {
border = true,
-- The characters which make up the border
border_chars = { '', '', '', '', '', '', '', '' },
-- The string to be used as a prompt prefix. It also sets the buffer to be a prompt
-- The string to be used as a prompt prefix. It also sets the buffer to
-- be a prompt
prefix = '',
-- The keymaps available while in the `renamer` buffer. The example below
-- overrides the default values, but you can add others as well.
mappings = mappings.bindings,
}

return defaults
32 changes: 31 additions & 1 deletion lua/renamer/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local log = require('plenary.log').new {
}
local popup = require 'plenary.popup'
local utils = require 'renamer.utils'
local mappings = require 'renamer.mappings'

--- @class Renamer
--- @field public title string
Expand Down Expand Up @@ -32,6 +33,17 @@ local renamer = {}
--- -- The string to be used as a prompt prefix. It also sets the buffer to
--- -- be a prompt
--- prefix = '',
--- -- The keymaps available while in the `renamer` buffer. The example below
--- -- overrides the default values, but you can add others as well.
--- mappings = {
--- ['<c-i>'] = require('renamer.mappings.utils').set_cursor_to_start,
--- ['<c-a>'] = require('renamer.mappings.utils').set_cursor_to_end,
--- ['<c-e>'] = require('renamer.mappings.utils').set_cursor_to_word_end,
--- ['<c-b>'] = require('renamer.mappings.utils').set_cursor_to_word_start,
--- ['<c-c>'] = require('renamer.mappings.utils').clear_line,
--- ['<c-u>'] = require('renamer.mappings.utils').undo,
--- ['<c-r>'] = require('renamer.mappings.utils').redo,
--- },
--- }
--- </code>
--- @param opts Defaults Configuration options, see `renamer.defaults`.
Expand All @@ -45,8 +57,10 @@ function renamer.setup(opts)
renamer.border = utils.get_value_or_default(opts, 'border', defaults.border)
renamer.border_chars = utils.get_value_or_default(opts, 'border_chars', defaults.border_chars)
renamer.prefix = utils.get_value_or_default(opts, 'prefix', defaults.prefix)
mappings.bindings = utils.get_value_or_default(opts, 'mappings', mappings.bindings)

renamer._buffers = {}
log.info 'Finished setup.'
end

--- Function that renames the word under the cursor, using Neovim's built in
Expand Down Expand Up @@ -101,8 +115,12 @@ function renamer.rename()
opts = popup_opts,
border_opts = prompt_opts.border,
}
log.fmt_info('Created "plenary" popup, with options: %s', vim.inspect(renamer._buffers[prompt_win_id]))

renamer._setup_window(prompt_win_id)
log.trace 'Finished setting up the popup.'
renamer._set_prompt_win_style(prompt_win_id)
log.trace 'Finished styling the popup.'

return prompt_win_id, renamer._buffers[prompt_win_id]
end
Expand All @@ -112,6 +130,8 @@ function renamer.on_submit(window_id)
local buf_id = vim.api.nvim_win_get_buf(window_id)
local new_word = vim.api.nvim_buf_get_lines(buf_id, -2, -1, false)[1]

log.fmt_info('Submitted word: "%s".', new_word)

renamer._delete_autocmds()
local initial_mode = renamer._buffers[window_id].opts and renamer._buffers[window_id].opts.initial_mode
if initial_mode and not string.match(initial_mode, 'i') then
Expand Down Expand Up @@ -152,7 +172,9 @@ function renamer.on_close(window_id)
local opts = renamer._buffers and renamer._buffers[window_id]
local border_win_id = opts and opts.border_opts and opts.border_opts.win_id
delete_window(window_id)
log.fmt_info('Deleted window: "%s".', window_id)
delete_window(border_win_id)
log.fmt_info('Deleted window: "%s" (border).', border_win_id)
end

function renamer._get_cursor()
Expand Down Expand Up @@ -199,6 +221,14 @@ function renamer._setup_window(prompt_win_id)
"<cmd>lua require('renamer').on_submit(" .. prompt_win_id .. ')<cr>',
{ noremap = true }
)
vim.api.nvim_buf_set_keymap(
prompt_buf_id,
'i',
'<esc>',
"<cmd>lua require('renamer').on_close(" .. prompt_win_id .. ')<cr>',
{ noremap = true }
)
mappings.register_bindings(prompt_buf_id)
end

function renamer._set_prompt_win_style(prompt_win_id)
Expand Down Expand Up @@ -236,7 +266,7 @@ end

function renamer._create_autocmds(prompt_win_id)
local on_leave = string.format(
[[ autocmd BufLeave,WinLeave,InsertLeave <buffer> ++nested ++once :silent lua require'renamer'.on_close(%s)]],
[[ autocmd BufLeave,WinLeave <buffer> ++nested ++once :silent lua require'renamer'.on_close(%s)]],
prompt_win_id
)
vim.cmd [[augroup RenamerInsert]]
Expand Down
40 changes: 40 additions & 0 deletions lua/renamer/mappings/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
local utils = require 'renamer.mappings.utils'

--- @class Mappings
--- @field public bindings table
--- @field public keymap_opts table
local mappings = {
bindings = {
['<c-i>'] = utils.set_cursor_to_start,
['<c-a>'] = utils.set_cursor_to_end,
['<c-e>'] = utils.set_cursor_to_word_end,
['<c-b>'] = utils.set_cursor_to_word_start,
['<c-c>'] = utils.clear_line,
['<c-u>'] = utils.undo,
['<c-r>'] = utils.redo,
},
keymap_opts = {
noremap = true,
silent = true,
},
}

mappings.register_bindings = function(buf_id)
if buf_id and mappings.bindings then
for binding, _ in pairs(mappings.bindings) do
local action = string.format(
'<cmd>lua require("renamer.mappings").exec_keymap_action("%s")<cr>',
binding:gsub('<', '<lt>')
)
vim.api.nvim_buf_set_keymap(buf_id, 'i', binding, action, mappings.keymap_opts)
end
end
end

mappings.exec_keymap_action = function(binding)
if binding and mappings.bindings and mappings.bindings[binding] then
mappings.bindings[binding]()
end
end

return mappings
45 changes: 45 additions & 0 deletions lua/renamer/mappings/utils.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
local utils = {}

utils.exec_in_normal = function(callback, opts, ...)
vim.cmd [[stopinsert]]
callback(...)

if opts and opts.keep_insert then
vim.api.nvim_feedkeys('i', 'n', true)
end
end

utils.set_cursor_to_end = function()
utils.exec_in_normal(vim.api.nvim_feedkeys, {}, 'A', 'n', true)
end

utils.set_cursor_to_start = function()
utils.exec_in_normal(vim.api.nvim_feedkeys, {}, 'I', 'n', true)
end

utils.set_cursor_to_word_end = function()
utils.exec_in_normal(vim.api.nvim_feedkeys, { keep_insert = true }, 'e', 'n', true)
end

utils.set_cursor_to_word_start = function()
utils.exec_in_normal(vim.api.nvim_feedkeys, { keep_insert = true }, 'b', 'n', true)
end

utils.clear_line = function()
utils.exec_in_normal(vim.api.nvim_feedkeys, { keep_insert = true }, '0C', 'n', true)
end

utils.undo = function()
utils.exec_in_normal(vim.api.nvim_feedkeys, { keep_insert = true }, 'u', 't', true)
end

utils.redo = function()
utils.exec_in_normal(function()
local key = vim.api.nvim_replace_termcodes('<c-r>', true, false, true)
vim.api.nvim_feedkeys(key, 't', true)
end, {
keep_insert = true,
})
end

return utils
20 changes: 20 additions & 0 deletions lua/tests/mappings/mappings_defaults_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
local mappings = require 'renamer.mappings'

local eq = assert.are.same

describe('mappings', function()
it('should have actions initialized for all `bindings`', function()
local bindings = mappings.bindings

for key, value in pairs(bindings) do
assert(value, string.format('No action mapped to "%s".', key))
end
end)

it('should have `keymap_opts` initialized', function()
local expected_opts = { noremap = true, silent = true }
local keymap_opts = mappings.keymap_opts

eq(expected_opts, keymap_opts)
end)
end)
Loading

0 comments on commit 2eae961

Please sign in to comment.