From 8b4cd67aa482f083c2f3f7c5291eff5c40d7507f Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sun, 13 Jun 2021 15:10:41 +0200 Subject: [PATCH 1/3] Add HintDirection and read it from opts. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This first commit allows to apply the hints to only the previous part of the cursor or what comes after. This commit doesn’t currently check the column, only the line. The next commit will add support for before / after on the current line and should also add support for a way to reduce apply hints only to the current line. This is an alternative implementation of #59. Relates to #71, #91. --- lua/hop/init.lua | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lua/hop/init.lua b/lua/hop/init.lua index 522d34bb..5a63ce4a 100644 --- a/lua/hop/init.lua +++ b/lua/hop/init.lua @@ -40,6 +40,14 @@ local function unhl_and_unmark(buf_handle, hl_ns) vim.api.nvim_buf_del_var(buf_handle, 'hop#marked') end +M.HintDirection = { + BEFORE_CURSOR = 1, + AFTER_CURSOR = 2, +} + +-- Hint the whole visible part of the buffer. +-- +-- The 'hint_mode' argument is the mode to use to hint the buffer. local function hint_with(hint_mode, opts) -- first, we ensure we’re not already hopping around; if not, we mark the current buffer (this mark will be removed -- when a jump is performed or if the user stops hopping) @@ -58,6 +66,14 @@ local function hint_with(hint_mode, opts) local bot_line = win_info.botline - 1 local cursor_pos = vim.api.nvim_win_get_cursor(0) + -- adjust the visible part of the buffer to hint based on the direction + local direction = opts.direction + if direction == M.HintDirection.BEFORE_CURSOR then + bot_line = cursor_pos[1] - 1 + elseif direction == M.HintDirection.AFTER_CURSOR then + top_line = cursor_pos[1] - 1 + end + -- NOTE: due to an (unknown yet) bug in neovim, the sign_width is not correctly reported when shifting the window -- view inside a non-wrap window, so we can’t rely on this; for this reason, we have to implement a weird hack that -- is going to disable the signs while hop is running (I’m sorry); the state is restored after jump From aff46fba7ac71711bc34807b0b054f7c12fccf75 Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sat, 19 Jun 2021 00:53:52 +0200 Subject: [PATCH 2/3] Implement full before / after cursor variation. Allows to hint lines by respecting a before / after hint mode. The first / last line will be truncated accordingly. Relates to #71, #91. --- lua/hop/hint.lua | 142 +++++++++++++++++++++++++++++++++++++++++++---- lua/hop/init.lua | 42 +++++++++----- 2 files changed, 160 insertions(+), 24 deletions(-) diff --git a/lua/hop/hint.lua b/lua/hop/hint.lua index 1c1cb9c6..18baaa88 100644 --- a/lua/hop/hint.lua +++ b/lua/hop/hint.lua @@ -2,6 +2,11 @@ local perm = require'hop.perm' local M = {} +M.HintDirection = { + BEFORE_CURSOR = 1, + AFTER_CURSOR = 2, +} + -- I hate Lua. local function starts_with_uppercase(s) if #s == 0 then @@ -92,10 +97,12 @@ end -- -- The input line_nr is the line number of the line currently being marked. -- +-- The direction argument allows to start / end hint creation after or before the cursor position +-- -- This function returns the list of hints as well as the length of the line in the form of table: -- -- { hints, length } -function M.mark_hints_line(hint_mode, line_nr, line, col_offset, win_width) +function M.mark_hints_line(hint_mode, line_nr, line, col_offset, win_width, direction_mode) local hints = {} local end_index = nil @@ -107,6 +114,20 @@ function M.mark_hints_line(hint_mode, line_nr, line, col_offset, win_width) local shifted_line = line:sub(1 + col_offset, vim.fn.byteidx(line, end_index)) + -- modify the shifted line to take the direction mode into account, if any + local col_bias = 0 + if direction_mode ~= nil then + local col = vim.fn.byteidx(line, direction_mode.cursor_col + 1) + if direction_mode.direction == M.HintDirection.AFTER_CURSOR then + -- we want to change the start offset so that we ignore everything before the cursor + shifted_line = shifted_line:sub(col - col_offset) + col_bias = col - 1 + elseif direction_mode.direction == M.HintDirection.BEFORE_CURSOR then + -- we want to change the end + shifted_line = shifted_line:sub(1, col - col_offset) + end + end + local col = 1 while true do local s = shifted_line:sub(col) @@ -119,7 +140,7 @@ function M.mark_hints_line(hint_mode, line_nr, line, col_offset, win_width) local colb = col + b hints[#hints + 1] = { line = line_nr; - col = colb + col_offset; + col = colb + col_offset + col_bias; } if hint_mode.oneshot then @@ -178,7 +199,38 @@ function M.reduce_hints_lines(per_line_hints, key) return nil, output, update_count end -function M.create_hints(hint_mode, win_width, cursor_pos, col_offset, top_line, lines, opts) +-- Create hints for a given indexed line. +-- +-- This function is used in M.create_hints to apply the hints to all the visible lines in the buffer. The need for such +-- a specialized function is made real because of the possibility to have variations of hinting functions that will also +-- work in a given direction, requiring a more granular control at the line level. +local function create_hints_for_line( + i, + hints, + indirect_hints, + hint_counts, + hint_mode, + win_width, + cursor_pos, + col_offset, + top_line, + direction_mode, + lines +) + local line_hints = M.mark_hints_line(hint_mode, top_line + i - 1, lines[i], col_offset, win_width, direction_mode) + hints[i] = line_hints + + hint_counts = hint_counts + #line_hints.hints + + for j = 1, #line_hints.hints do + local hint = line_hints.hints[j] + indirect_hints[#indirect_hints + 1] = { i = i; j = j; dist = manh_dist(cursor_pos, { hint.line, hint.col }) } + end + + return hint_counts +end + +function M.create_hints(hint_mode, win_width, cursor_pos, col_offset, top_line, lines, direction, opts) -- extract all the words currently visible on screen; the hints variable contains the list -- of words as a pair of { line, column } for each word on a given line and indirect_words is a -- simple list containing { line, word_index, distance_to_cursor } that is sorted by distance to @@ -186,15 +238,85 @@ function M.create_hints(hint_mode, win_width, cursor_pos, col_offset, top_line, local hints = {} local indirect_hints = {} local hint_counts = 0 - for i = 1, #lines do - local line_hints = M.mark_hints_line(hint_mode, top_line + i - 1, lines[i], col_offset, win_width) - hints[i] = line_hints - hint_counts = hint_counts + #line_hints.hints + -- in the case of a direction, we want to treat the first or last line (according to the direction) differently + if direction == M.HintDirection.AFTER_CURSOR then + -- the first line is to be checked first + hint_counts = create_hints_for_line( + 1, + hints, + indirect_hints, + hint_counts, + hint_mode, + win_width, + cursor_pos, + col_offset, + top_line, + { cursor_col = cursor_pos[2], direction = direction }, + lines + ) + + for i = 2, #lines do + hint_counts = create_hints_for_line( + i, + hints, + indirect_hints, + hint_counts, + hint_mode, + win_width, + cursor_pos, + col_offset, + top_line, + nil, + lines + ) + end + elseif direction == M.HintDirection.BEFORE_CURSOR then + -- the last line is to be checked last + for i = 1, #lines - 1 do + hint_counts = create_hints_for_line( + i, + hints, + indirect_hints, + hint_counts, + hint_mode, + win_width, + cursor_pos, + col_offset, + top_line, + nil, + lines + ) + end - for j = 1, #line_hints.hints do - local hint = line_hints.hints[j] - indirect_hints[#indirect_hints + 1] = { i = i; j = j; dist = manh_dist(cursor_pos, { hint.line, hint.col }) } + hint_counts = create_hints_for_line( + #lines, + hints, + indirect_hints, + hint_counts, + hint_mode, + win_width, + cursor_pos, + col_offset, + top_line, + { cursor_col = cursor_pos[2], direction = direction }, + lines + ) + else + for i = 1, #lines do + hint_counts = create_hints_for_line( + i, + hints, + indirect_hints, + hint_counts, + hint_mode, + win_width, + cursor_pos, + col_offset, + top_line, + nil, + lines + ) end end diff --git a/lua/hop/init.lua b/lua/hop/init.lua index 5a63ce4a..14c265c9 100644 --- a/lua/hop/init.lua +++ b/lua/hop/init.lua @@ -27,10 +27,25 @@ end -- - hl_ns is the highlight namespace. -- - top_line is the top line in the buffer to start highlighting at -- - bottom_line is the bottom line in the buffer to stop highlighting at -local function grey_things_out(buf_handle, hl_ns, top_line, bottom_line) +local function grey_things_out(buf_handle, hl_ns, top_line, bottom_line, direction_mode) clear_namespace(buf_handle, hl_ns) - for line_i = top_line, bottom_line do - vim.api.nvim_buf_add_highlight(buf_handle, hl_ns, 'HopUnmatched', line_i, 0, -1) + + if direction_mode ~= nil then + if direction_mode.direction == hint.HintDirection.AFTER_CURSOR then + vim.api.nvim_buf_add_highlight(buf_handle, hl_ns, 'HopUnmatched', top_line, direction_mode.cursor_col, -1) + for line_i = top_line + 1, bottom_line do + vim.api.nvim_buf_add_highlight(buf_handle, hl_ns, 'HopUnmatched', line_i, 0, -1) + end + elseif direction_mode.direction == hint.HintDirection.BEFORE_CURSOR then + for line_i = top_line, bottom_line - 1 do + vim.api.nvim_buf_add_highlight(buf_handle, hl_ns, 'HopUnmatched', line_i, 0, -1) + end + vim.api.nvim_buf_add_highlight(buf_handle, hl_ns, 'HopUnmatched', bottom_line, 0, direction_mode.cursor_col) + end + else + for line_i = top_line, bottom_line do + vim.api.nvim_buf_add_highlight(buf_handle, hl_ns, 'HopUnmatched', line_i, 0, -1) + end end end @@ -40,11 +55,6 @@ local function unhl_and_unmark(buf_handle, hl_ns) vim.api.nvim_buf_del_var(buf_handle, 'hop#marked') end -M.HintDirection = { - BEFORE_CURSOR = 1, - AFTER_CURSOR = 2, -} - -- Hint the whole visible part of the buffer. -- -- The 'hint_mode' argument is the mode to use to hint the buffer. @@ -68,10 +78,13 @@ local function hint_with(hint_mode, opts) -- adjust the visible part of the buffer to hint based on the direction local direction = opts.direction - if direction == M.HintDirection.BEFORE_CURSOR then + local direction_mode = nil + if direction == hint.HintDirection.BEFORE_CURSOR then bot_line = cursor_pos[1] - 1 - elseif direction == M.HintDirection.AFTER_CURSOR then + direction_mode = { cursor_col = cursor_pos[2], direction = direction } + elseif direction == hint.HintDirection.AFTER_CURSOR then top_line = cursor_pos[1] - 1 + direction_mode = { cursor_col = cursor_pos[2], direction = direction } end -- NOTE: due to an (unknown yet) bug in neovim, the sign_width is not correctly reported when shifting the window @@ -91,7 +104,7 @@ local function hint_with(hint_mode, opts) -- create the highlight group and grey everything out; the highlight group will allow us to clean everything at once -- when hop quits local hl_ns = vim.api.nvim_create_namespace('') - grey_things_out(0, hl_ns, top_line, bot_line) + grey_things_out(0, hl_ns, top_line, bot_line, direction_mode) -- get the buffer lines and create hints; hint_counts allows us to display some error diagnostics to the user, if any, -- or even perform direct jump in the case of a single match @@ -103,6 +116,7 @@ local function hint_with(hint_mode, opts) win_view.leftcol, top_line, win_lines, + direction, opts ) @@ -150,7 +164,7 @@ local function hint_with(hint_mode, opts) local key_str = vim.fn.nr2char(key) if opts.keys:find(key_str, 1, true) then -- If this is a key used in hop (via opts.keys), deal with it in hop - h = M.refine_hints(0, key_str, opts.teasing) + h = M.refine_hints(0, key_str, opts.teasing, direction_mode) vim.cmd('redraw') else -- If it's not, quit hop and use the key like normal instead @@ -168,7 +182,7 @@ end -- -- Refining hints allows to advance the state machine by one step. If a terminal step is reached, this function jumps to -- the location. Otherwise, it stores the new state machine. -function M.refine_hints(buf_handle, key, teasing) +function M.refine_hints(buf_handle, key, teasing, direction_mode) local hint_state = vim.api.nvim_buf_get_var(buf_handle, 'hop#hint_state') local h, hints, update_count = hint.reduce_hints_lines(hint_state.hints, key) @@ -181,7 +195,7 @@ function M.refine_hints(buf_handle, key, teasing) hint_state.hints = hints vim.api.nvim_buf_set_var(buf_handle, 'hop#hint_state', hint_state) - grey_things_out(buf_handle, hint_state.hl_ns, hint_state.top_line, hint_state.bot_line) + grey_things_out(buf_handle, hint_state.hl_ns, hint_state.top_line, hint_state.bot_line, direction_mode) hint.set_hint_extmarks(hint_state.hl_ns, hints) vim.cmd('redraw') else From 505f3b05a8ea8b48938fba32c3338b9117dcf36a Mon Sep 17 00:00:00 2001 From: Dimitri Sabadie Date: Sat, 19 Jun 2021 02:27:55 +0200 Subject: [PATCH 3/3] Add BC / AC variations of commands; also, doc. --- doc/hop.txt | 63 +++++++++++++++++++++++++++++++++++++++++++------- plugin/hop.vim | 10 ++++++++ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/doc/hop.txt b/doc/hop.txt index 4b015deb..ef82f58a 100644 --- a/doc/hop.txt +++ b/doc/hop.txt @@ -66,7 +66,13 @@ You can try those commands by typing them in your command line. By default, they will use the default options for the configuration of Hop. If you want to customize how those functions work, have a look at |hop.setup|. +Some of the commands have a suffix, such as `BC` and `AC`. Those are +variations of the command without the suffix, applying to the visible part of +the buffer before and after the cursor, respectively. + `:HopWord` *:HopWord* +`:HopWordBC` *:HopWordBC* +`:HopWordAC` *:HopWordAC* Annotate all words in the current window with key sequences. Typing a first key will visually filter the sequences and reduce them. Continue typing key sequences until you reduce a sequence completely, which will @@ -75,21 +81,29 @@ customize how those functions work, have a look at |hop.setup|. This is akin to calling the |hop.hint_words| Lua function. `:HopPattern` *:HopPattern* +`:HopPatternBC` *:HopPatternBC* +`:HopPatternAC` *:HopPatternAC* Ask the user for a pattern and hint the document with it. This is akin to calling the |hop.hint_patterns| Lua function. `:HopChar1` *:HopChar1* +`:HopChar1BC` *:HopChar1BC* +`:HopChar1AC` *:HopChar1AC* Type a key and immediately hint the document for this key. This is akin to calling the |hop.hint_char1| Lua Function `:HopChar2` *:HopChar2* +`:HopChar2BC` *:HopChar2BC* +`:HopChar2AC` *:HopChar2AC* Type two keys and immediately hint the document for this bigram. This is akin to calling the |hop.hint_char2| Lua Function `:HopLine` *:HopLine* +`:HopLineBC` *:HopLineBC* +`:HopLineAC` *:HopLineAC* Jump to the beginning of the line of your choice inside your buffer. This is akin to calling the |hop.hint_lines| Lua function. @@ -178,17 +192,31 @@ Hint API~ The hint API allows to create, reduce and display *hints* in easy ways. `hop.hint.by_word_start` *hop.hint.by_word_start* - |word| hint mode. This mode will highlights the beginnings of all the - words in the document and will make the cursor jump to the one fully - reduced. + |word| hint mode. This mode will highlights the beginnings of all the + words in the document and will make the cursor jump to the one fully + reduced. + +`hop.hint.by_searching(`{pat}`,` {plain_search}`)` *hop.hint.by_searching* + |pattern| hint mode. This mode will highlights the beginnings of a + pattern you will be prompted for in the document and will make the + cursor jump to the one fully reduced. + + Arguments:~ + {pat} Pattern to search. + {plain_search} Should the pattern by plain-text. -`hop.hint.by_searching(`{pat}`)` *hop.hint.by_searching* - |pattern| hint mode. This mode will highlights the beginnings of a - pattern you will be prompted for in the document and will make the - cursor jump to the one fully reduced. +`hop.hint.by_case_searching(` *hop.hint.by_case_searching* + {pat}`,` + {plain_search}`,` + {opts} +`) + Similar to |hop.hint.by_searching|, but respects the user case sensitivity + set by 'smartcase' and |hop-config-case_insensitive|. Arguments:~ - {pat} Pattern to search. + {pat} Pattern to search. + {plain_search} Should the pattern by plain-text. + {opts} User options. `hop.hint.mark_hints_line(` *hop.hint.mark_hints_line* {hint_mode}`,` @@ -265,6 +293,13 @@ The hint API allows to create, reduce and display *hints* in easy ways. was possible), and `update_count` is the number of changes that have occurred while reducing. +`hop.hint.HintDirection` *hop.hint.HintDirection* + Enumeration for hinting direction. + + Enumeration variants:~ + {BEFORE_CURSOR} Create and apply hints before the cursor. + {AFTER_CURSOR} Create and apply hints after the cursor. + `hop.hint.create_hints(` *hop.hint.create_hints* {hint_mode}`,` {win_width}`,` @@ -272,6 +307,7 @@ The hint API allows to create, reduce and display *hints* in easy ways. {col_offset}`,` {top_line}`,` {lines}`,` + {direction}, {opts} `)` Given a set of {lines}, this function creates the hints for each line in @@ -285,6 +321,7 @@ The hint API allows to create, reduce and display *hints* in easy ways. {col_offset} Left column offset in the buffer to start at. {top_line} First line in the buffer to create hints for. {lines} Lines to create hints for. + {direction} Direction to hint in. See |hop.hint.HintDirection|. {opts} Hop options. Return:~ @@ -484,6 +521,16 @@ below. > create_hl_autocmd = true < +`direction` *hop-config-direction* + Direction in which to hint. See |hop.hint.HintDirection| for further + details. + + Setting this in the user configuration will make all commands default to + that direction, unless overriden. + + Defaults:~ + + {none} ============================================================================== HIGHLIGHTS *hop-highlights* diff --git a/plugin/hop.vim b/plugin/hop.vim index 881f4861..e10ad6c1 100644 --- a/plugin/hop.vim +++ b/plugin/hop.vim @@ -7,15 +7,25 @@ endif " The jump-to-word command. command! HopWord lua require'hop'.hint_words() +command! HopWordBC lua require'hop'.hint_words({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR }) +command! HopWordAC lua require'hop'.hint_words({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR }) " The jump-to-pattern command. command! HopPattern lua require'hop'.hint_patterns() +command! HopPatternBC lua require'hop'.hint_patterns({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR }) +command! HopPatternAC lua require'hop'.hint_patterns({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR }) " The jump-to-char-1 command. command! HopChar1 lua require'hop'.hint_char1() +command! HopChar1BC lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR }) +command! HopChar1AC lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR }) " The jump-to-char-2 command. command! HopChar2 lua require'hop'.hint_char2() +command! HopChar2BC lua require'hop'.hint_char2({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR }) +command! HopChar2AC lua require'hop'.hint_char2({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR }) " The jump-to-line command. command! HopLine lua require'hop'.hint_lines() +command! HopLineBC lua require'hop'.hint_lines({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR }) +command! HopLineAC lua require'hop'.hint_lines({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR })