From 8a4ab8a3fe1b3df1b226fb8b3c24642ce994d9bf Mon Sep 17 00:00:00 2001 From: awerebea Date: Mon, 13 May 2024 19:50:33 +0400 Subject: [PATCH] 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 "|", "J", "K", "j", "k" in normal, visual, and operator-pending modes. --- README.md | 124 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 83b99f4..cdb638c 100644 --- a/README.md +++ b/README.md @@ -628,35 +628,86 @@ examples: Linewise motions ```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', '|', "Vlua leap_line_start()") +-- Create mappings for "|", "J", "K", +-- "j", "k" in normal, visual, and operator-pending modes. +for key, args in pairs { + ["|"] = { { keep_column = true }, { desc = "Leap vertically" } }, + ["J"] = { + { is_upward = false }, + { desc = "Leap to line start downwards" }, + }, + ["K"] = { + { is_upward = true }, + { desc = "Leap to line start upwards" }, + }, + ["j"] = { + { is_upward = false, keep_column = true }, + { desc = "Leap downwards" }, + }, + ["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 = "Vlua leap_vertically(" .. serialize_table(args[1]):gsub("\n", "") .. ")", + } do + vim.keymap.set(mode, key, rhs_expr, args[2]) + end +end ```