Skip to content

Commit

Permalink
Improve an example of linewise motions
Browse files Browse the repository at this point in the history
Extend the example of linewise motions to include the ability to jump to
the start of the line or to try to keep the column after the jump.

Also add and option and mappings for the directional jumps (upwards and
downwards).

Create keymaps for "|", "<leader><leader>J", "<leader><leader>K",
"<leader><leader>j", "<leader><leader>k" in normal, visual, and
operator-pending modes.
  • Loading branch information
awerebea committed May 13, 2024
1 parent be68eec commit e92563d
Showing 1 changed file with 106 additions and 18 deletions.
124 changes: 106 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -628,35 +628,86 @@ examples:
<summary>Linewise motions</summary>

```lua
local function get_line_starts(winid, skip_range)
local wininfo = vim.fn.getwininfo(winid)[1]
local cur_line = vim.fn.line('.')
-- ref: https://stackoverflow.com/a/6081639/14110650
local function serialize_table(val, name, skipnewlines, depth)
skipnewlines = skipnewlines or false
depth = depth or 0
local tmp = string.rep(" ", depth)
if name then
tmp = tmp .. name .. " = "
end
if type(val) == "table" then
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
for k, v in pairs(val) do
tmp = tmp
.. serialize_table(v, k, skipnewlines, depth + 1)
.. ","
.. (not skipnewlines and "\n" or "")
end
tmp = tmp .. string.rep(" ", depth) .. "}"
elseif type(val) == "number" then
tmp = tmp .. tostring(val)
elseif type(val) == "string" then
tmp = tmp .. string.format("%q", val)
elseif type(val) == "boolean" then
tmp = tmp .. (val and "true" or "false")
else
tmp = tmp .. '"[inserializeable datatype:' .. type(val) .. ']"'
end
return tmp
end

local function get_line_len(bufid, line_number)
local line_content =
vim.api.nvim_buf_get_lines(bufid, line_number - 1, line_number, false)[1]
return string.len(line_content)
end

local function get_line_targets(winid, skip_range, is_upward, keep_column)
local wininfo = vim.fn.getwininfo(winid)[1]
local bufid = vim.api.nvim_win_get_buf(winid)
local cur_line = vim.fn.line "."
local cur_col = vim.fn.col "."
-- Skip lines close to the cursor.
local skip_range = skip_range or 2
local keep_column = keep_column or false
local is_directional = is_upward ~= nil and true or false

-- Get targets.
local targets = {}
local lnum = wininfo.topline
local cnum = 1
while lnum <= wininfo.botline do
local fold_end = vim.fn.foldclosedend(lnum)
-- Skip folded ranges.
if fold_end ~= -1 then
lnum = fold_end + 1
else
if is_directional then
if is_upward and lnum > cur_line - skip_range then
break
elseif not is_upward and lnum < cur_line + skip_range then
goto continue
end
end
if keep_column then
cnum = math.min(cur_col, get_line_len(bufid, lnum))
end
if (lnum < cur_line - skip_range) or (lnum > cur_line + skip_range) then
table.insert(targets, { pos = { lnum, 1 } })
table.insert(targets, { pos = { lnum, cnum } })
end
::continue::
lnum = lnum + 1
end
end

-- Sort them by vertical screen distance from cursor.
local cur_screen_row = vim.fn.screenpos(winid, cur_line, 1)['row']
local cur_screen_row = vim.fn.screenpos(winid, cur_line, 1)["row"]
local function screen_rows_from_cur(t)
local t_screen_row = vim.fn.screenpos(winid, t.pos[1], t.pos[2])['row']
local t_screen_row = vim.fn.screenpos(winid, t.pos[1], t.pos[2])["row"]
return math.abs(cur_screen_row - t_screen_row)
end
table.sort(targets, function (t1, t2)
table.sort(targets, function(t1, t2)
return screen_rows_from_cur(t1) < screen_rows_from_cur(t2)
end)

Expand All @@ -665,23 +716,60 @@ local function get_line_starts(winid, skip_range)
end
end

-- You can pass an argument to specify a range to be skipped
-- before/after the cursor (default is +/-2).
function leap_line_start(skip_range)
-- You can pass a table of arguments to specify the jump behavior:
-- skip_range - range to be skipped before/after the cursor (default is +/-2)
-- is_upward - set the jump direction, true - up, false - down (default nil - bidirectional)
-- keep_column - whether to try to keep the column after the jump (default false)
function leap_vertically(args)
local args = args or {}
local skip_range = args.skip_range
local is_upward = args.is_upward
local keep_column = args.keep_column
local winid = vim.api.nvim_get_current_win()
require('leap').leap {
require("leap").leap {
target_windows = { winid },
targets = get_line_starts(winid, skip_range),
targets = get_line_targets(winid, skip_range, is_upward, keep_column),
}
end

-- For maximum comfort, force linewise selection in the mappings:
vim.keymap.set('x', '|', function ()
-- Only force V if not already in it (otherwise it would exit Visual mode).
if vim.fn.mode(1) ~= 'V' then vim.cmd('normal! V') end
leap_line_start()
end)
vim.keymap.set('o', '|', "V<cmd>lua leap_line_start()<cr>")
-- Create mappings for "|", "<leader><leader>J", "<leader><leader>K",
-- "<leader><leader>j", "<leader><leader>k" in normal, visual, and operator-pending modes.
for key, args in pairs {
["|"] = { { keep_column = true }, { desc = "Leap vertically" } },
["<leader><leader>J"] = {
{ is_upward = false },
{ desc = "Leap to line start downwards" },
},
["<leader><leader>K"] = {
{ is_upward = true },
{ desc = "Leap to line start upwards" },
},
["<leader><leader>j"] = {
{ is_upward = false, keep_column = true },
{ desc = "Leap downwards" },
},
["<leader><leader>k"] = {
{ is_upward = true, keep_column = true },
{ desc = "Leap upwards" },
},
} do
for mode, rhs_expr in pairs {
n = function()
leap_vertically(args[1])
end,
x = function()
-- Only force V if not already in it (otherwise it would exit Visual mode).
if vim.fn.mode(1) ~= "V" then
vim.cmd "normal! V"
end
leap_vertically(args[1])
end,
o = "V<Cmd>lua leap_vertically(" .. serialize_table(args[1]):gsub("\n", "") .. ")<CR>",
} do
vim.keymap.set(mode, key, rhs_expr, args[2])
end
end
```
</details>

Expand Down

0 comments on commit e92563d

Please sign in to comment.