diff --git a/README.md b/README.md index 83b99f4..cdb638c 100644 --- a/README.md +++ b/README.md @@ -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.max(1, 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) @@ -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) +local 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>