Skip to content

Commit e92563d

Browse files
committed
Improve an example of linewise motions
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.
1 parent be68eec commit e92563d

File tree

1 file changed

+106
-18
lines changed

1 file changed

+106
-18
lines changed

README.md

+106-18
Original file line numberDiff line numberDiff line change
@@ -628,35 +628,86 @@ examples:
628628
<summary>Linewise motions</summary>
629629

630630
```lua
631-
local function get_line_starts(winid, skip_range)
632-
local wininfo = vim.fn.getwininfo(winid)[1]
633-
local cur_line = vim.fn.line('.')
631+
-- ref: https://stackoverflow.com/a/6081639/14110650
632+
local function serialize_table(val, name, skipnewlines, depth)
633+
skipnewlines = skipnewlines or false
634+
depth = depth or 0
635+
local tmp = string.rep(" ", depth)
636+
if name then
637+
tmp = tmp .. name .. " = "
638+
end
639+
if type(val) == "table" then
640+
tmp = tmp .. "{" .. (not skipnewlines and "\n" or "")
641+
for k, v in pairs(val) do
642+
tmp = tmp
643+
.. serialize_table(v, k, skipnewlines, depth + 1)
644+
.. ","
645+
.. (not skipnewlines and "\n" or "")
646+
end
647+
tmp = tmp .. string.rep(" ", depth) .. "}"
648+
elseif type(val) == "number" then
649+
tmp = tmp .. tostring(val)
650+
elseif type(val) == "string" then
651+
tmp = tmp .. string.format("%q", val)
652+
elseif type(val) == "boolean" then
653+
tmp = tmp .. (val and "true" or "false")
654+
else
655+
tmp = tmp .. '"[inserializeable datatype:' .. type(val) .. ']"'
656+
end
657+
return tmp
658+
end
659+
660+
local function get_line_len(bufid, line_number)
661+
local line_content =
662+
vim.api.nvim_buf_get_lines(bufid, line_number - 1, line_number, false)[1]
663+
return string.len(line_content)
664+
end
665+
666+
local function get_line_targets(winid, skip_range, is_upward, keep_column)
667+
local wininfo = vim.fn.getwininfo(winid)[1]
668+
local bufid = vim.api.nvim_win_get_buf(winid)
669+
local cur_line = vim.fn.line "."
670+
local cur_col = vim.fn.col "."
634671
-- Skip lines close to the cursor.
635672
local skip_range = skip_range or 2
673+
local keep_column = keep_column or false
674+
local is_directional = is_upward ~= nil and true or false
636675

637676
-- Get targets.
638677
local targets = {}
639678
local lnum = wininfo.topline
679+
local cnum = 1
640680
while lnum <= wininfo.botline do
641681
local fold_end = vim.fn.foldclosedend(lnum)
642682
-- Skip folded ranges.
643683
if fold_end ~= -1 then
644684
lnum = fold_end + 1
645685
else
686+
if is_directional then
687+
if is_upward and lnum > cur_line - skip_range then
688+
break
689+
elseif not is_upward and lnum < cur_line + skip_range then
690+
goto continue
691+
end
692+
end
693+
if keep_column then
694+
cnum = math.min(cur_col, get_line_len(bufid, lnum))
695+
end
646696
if (lnum < cur_line - skip_range) or (lnum > cur_line + skip_range) then
647-
table.insert(targets, { pos = { lnum, 1 } })
697+
table.insert(targets, { pos = { lnum, cnum } })
648698
end
699+
::continue::
649700
lnum = lnum + 1
650701
end
651702
end
652703

653704
-- Sort them by vertical screen distance from cursor.
654-
local cur_screen_row = vim.fn.screenpos(winid, cur_line, 1)['row']
705+
local cur_screen_row = vim.fn.screenpos(winid, cur_line, 1)["row"]
655706
local function screen_rows_from_cur(t)
656-
local t_screen_row = vim.fn.screenpos(winid, t.pos[1], t.pos[2])['row']
707+
local t_screen_row = vim.fn.screenpos(winid, t.pos[1], t.pos[2])["row"]
657708
return math.abs(cur_screen_row - t_screen_row)
658709
end
659-
table.sort(targets, function (t1, t2)
710+
table.sort(targets, function(t1, t2)
660711
return screen_rows_from_cur(t1) < screen_rows_from_cur(t2)
661712
end)
662713

@@ -665,23 +716,60 @@ local function get_line_starts(winid, skip_range)
665716
end
666717
end
667718

668-
-- You can pass an argument to specify a range to be skipped
669-
-- before/after the cursor (default is +/-2).
670-
function leap_line_start(skip_range)
719+
-- You can pass a table of arguments to specify the jump behavior:
720+
-- skip_range - range to be skipped before/after the cursor (default is +/-2)
721+
-- is_upward - set the jump direction, true - up, false - down (default nil - bidirectional)
722+
-- keep_column - whether to try to keep the column after the jump (default false)
723+
function leap_vertically(args)
724+
local args = args or {}
725+
local skip_range = args.skip_range
726+
local is_upward = args.is_upward
727+
local keep_column = args.keep_column
671728
local winid = vim.api.nvim_get_current_win()
672-
require('leap').leap {
729+
require("leap").leap {
673730
target_windows = { winid },
674-
targets = get_line_starts(winid, skip_range),
731+
targets = get_line_targets(winid, skip_range, is_upward, keep_column),
675732
}
676733
end
677734

678735
-- For maximum comfort, force linewise selection in the mappings:
679-
vim.keymap.set('x', '|', function ()
680-
-- Only force V if not already in it (otherwise it would exit Visual mode).
681-
if vim.fn.mode(1) ~= 'V' then vim.cmd('normal! V') end
682-
leap_line_start()
683-
end)
684-
vim.keymap.set('o', '|', "V<cmd>lua leap_line_start()<cr>")
736+
-- Create mappings for "|", "<leader><leader>J", "<leader><leader>K",
737+
-- "<leader><leader>j", "<leader><leader>k" in normal, visual, and operator-pending modes.
738+
for key, args in pairs {
739+
["|"] = { { keep_column = true }, { desc = "Leap vertically" } },
740+
["<leader><leader>J"] = {
741+
{ is_upward = false },
742+
{ desc = "Leap to line start downwards" },
743+
},
744+
["<leader><leader>K"] = {
745+
{ is_upward = true },
746+
{ desc = "Leap to line start upwards" },
747+
},
748+
["<leader><leader>j"] = {
749+
{ is_upward = false, keep_column = true },
750+
{ desc = "Leap downwards" },
751+
},
752+
["<leader><leader>k"] = {
753+
{ is_upward = true, keep_column = true },
754+
{ desc = "Leap upwards" },
755+
},
756+
} do
757+
for mode, rhs_expr in pairs {
758+
n = function()
759+
leap_vertically(args[1])
760+
end,
761+
x = function()
762+
-- Only force V if not already in it (otherwise it would exit Visual mode).
763+
if vim.fn.mode(1) ~= "V" then
764+
vim.cmd "normal! V"
765+
end
766+
leap_vertically(args[1])
767+
end,
768+
o = "V<Cmd>lua leap_vertically(" .. serialize_table(args[1]):gsub("\n", "") .. ")<CR>",
769+
} do
770+
vim.keymap.set(mode, key, rhs_expr, args[2])
771+
end
772+
end
685773
```
686774
</details>
687775

0 commit comments

Comments
 (0)