1
0
mirror of https://github.com/SpaceVim/SpaceVim.git synced 2025-01-23 07:10:06 +08:00

chore(hop.nvim): update bundle hop.nvim

close https://github.com/SpaceVim/SpaceVim/issues/4837

update to: 90db1b2c61
This commit is contained in:
wsdjeg 2023-04-16 21:22:49 +08:00
parent 1d4406b5ca
commit 4d8d77fb5e
12 changed files with 454 additions and 310 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2021, Dimitri Sabadie <dimitri.sabadie@gmail.com>
Copyright (c) 2021-2022, Dimitri Sabadie <dimitri.sabadie@gmail.com>
All rights reserved.

View File

@ -6,7 +6,17 @@
/_/
· Neovim motions on speed! ·
[![](https://img.shields.io/badge/matrix-join%20the%20speed!-blueviolet)](https://matrix.to/#/#hop.nvim:matrix.org)
<p align="center">
<img src="https://img.shields.io/github/issues/phaazon/hop.nvim?color=cyan&style=for-the-badge"/>
<img src="https://img.shields.io/github/issues-pr/phaazon/hop.nvim?color=green&style=for-the-badge"/>
<img src="https://img.shields.io/github/contributors-anon/phaazon/hop.nvim?color=blue&style=for-the-badge"/>
<img src="https://img.shields.io/github/last-commit/phaazon/hop.nvim?style=for-the-badge"/>
<img src="https://img.shields.io/github/v/tag/phaazon/hop.nvim?color=pink&label=release&style=for-the-badge"/>
</p>
<p align="center">
<a href="#using-vim-plug">Install</a> · <a href="https://github.com/phaazon/hop.nvim/wiki">Wiki</a> · <a href="https://github.com/phaazon/hop.nvim/wiki/Screenshots">Screenshots</a> · <a href="https://matrix.to/#/#hop.nvim:matrix.org">Discuss</a>
</p>
**Hop** is an [EasyMotion]-like plugin allowing you to jump anywhere in a
document with as few keystrokes as possible. It does so by annotating text in
@ -15,19 +25,14 @@ represents a key to type to jump to the annotated text. Most of the time,
those sequences lengths will be between 1 to 3 characters, making every jump
target in your document reachable in a few keystrokes.
<p align="center">
<img src="https://user-images.githubusercontent.com/506592/176885253-5f618593-77c5-4843-9101-a9de30f0a022.png"/>
</p>
<!-- vim-markdown-toc GFM -->
* [Motivation](#motivation)
* [Features](#features)
* [Word mode (`:HopWord`)](#word-mode-hopword)
* [Line mode (`:HopLine`)](#line-mode-hopline)
* [1-char mode (`:HopChar1`)](#1-char-mode-hopchar1)
* [2-char mode (`:HopChar2`)](#2-char-mode-hopchar2)
* [Pattern mode (`:HopPattern`)](#pattern-mode-hoppattern)
* [Visual extend](#visual-extend)
* [Jump on sole occurrence](#jump-on-sole-occurrence)
* [Use as operator motion](#use-as-operator-motion)
* [Inclusive / exclusive motion](#inclusive--exclusive-motion)
* [Getting started](#getting-started)
* [Installation](#installation)
* [Important note about versioning](#important-note-about-versioning)
@ -36,8 +41,6 @@ target in your document reachable in a few keystrokes.
* [Nightly users](#nightly-users)
* [Usage](#usage)
* [Keybindings](#keybindings)
* [Configuration](#configuration)
* [Extension](#extension)
* [Chat](#chat)
<!-- vim-markdown-toc -->
@ -66,83 +69,18 @@ Neovim.
# Features
- [x] Go to any word in the current buffer.
- [x] Go to any character in the current buffer.
- [x] Go to any bigrams in the current buffer.
- [x] Use Hop cross windows with multi-windows support.
- [x] Make an arbitrary search akin to <kbd>/</kbd> and go to any occurrences.
- [x] Go to any line.
- [x] Visual extend mode, which allows you to extend a visual selection by hopping elsewhere in the document.
- [x] Use it with commands like `d`, `c`, `y` to delete/change/yank up to your new cursor position.
- [x] Support a wide variety of user configuration options, among the possibility to alter the behavior of commands
to hint only before or after the cursor, for the current line, change the dictionary keys to use for the labels,
jump on sole occurrence, etc.
- [x] Extensible: provide your own jump targets and create Hop extensions!
## Word mode (`:HopWord`)
This mode highlights all the recognized words in the visible part of the buffer and allows you to jump to any.
![](https://phaazon.net/media/uploads/hop_word_mode.gif)
## Line mode (`:HopLine`)
This mode highlights the beginnings of each line in the visible part of the buffer for quick line hopping.
![](https://phaazon.net/media/uploads/hop_line_mode.gif)
## 1-char mode (`:HopChar1`)
This mode expects the user to type a single character. That character will then be highlighted in the visible part of
the buffer, allowing to jump to any of its occurrence. This mode is especially useful to jump to operators, punctuations
or any symbols not recognized as parts of words.
![](https://phaazon.net/media/uploads/hop_char1_mode.gif)
## 2-char mode (`:HopChar2`)
A variant of the 1-char mode, this mode exacts the user to type two characters, representing a _bigram_ (they follow
each other, in order). The bigram occurrences in the visible part of the buffer will then be highlighted for you to jump
to any.
![](https://phaazon.net/media/uploads/hop_char2_mode.gif)
Note that its possible to _fallback to 1-char mode_ if you hit a special key as second key. This key can be controlled
via the user configuration. `:h hop-config-char2_fallback_key`.
## Pattern mode (`:HopPattern`)
Akin to `/`, this mode prompts you for a pattern (regex) to search. Occurrences will be highlighted, allowing you to
jump to any.
![](https://phaazon.net/media/uploads/hop_pattern_mode.gif)
## Visual extend
If you call any Hop commands / Lua functions from one of the visual modes, the visual selection will be extended.
![](https://phaazon.net/media/uploads/hop_visual_extend.gif)
## Jump on sole occurrence
If only a single occurrence is visible in the buffer, Hop will automatically jump to it without requiring pressing any
extra key.
![](https://phaazon.net/media/uploads/hop_sole_occurrence.gif)
## Use as operator motion
You can use Hop with any command that expects a motion, such as `d`, `y`, `c`, and it does what you would expect:
Delete/yank/change the document up to the new cursor position.
## Inclusive / exclusive motion
By default, Hop will operate in exclusive mode, which is similar to what you get with `t`: deleting from the cursor
position up to the next `)` (without deleting the `)`), which is normally done with `dt)`. However, if you want to be
inclusive (i.e. delete the `)`, which is `df)` in vanilla), you can set the `inclusive_jump` option to `true`.
Some limitations currently exist, requiring `virtualedit` special settings. `:h hop-config-inclusive_jump` for more
information.
- Go to any word in the current buffer (`:HopWord`).
- Go to any character in the current buffer (`:HopChar1`).
- Go to any bigrams in the current buffer (`:HopChar2`).
- Make an arbitrary search akin to <kbd>/</kbd> and go to any occurrences (`:HopPattern`).
- Go to any line and any line start (`:HopLine`, `:HopLineStart`).
- Go to anywhere (`:HopAnywhere`).
- Use Hop cross windows with multi-windows support (`:Hop*MW`).
- Use it with commands like `v`, `d`, `c`, `y` to visually select/delete/change/yank up to your new cursor position.
- Support a wide variety of user configuration options, among the possibility to alter the behavior of commands
to hint only before or after the cursor (`:Hop*BC`, `:Hop*AC`), for the current line (`:Hop*CurrentLine`),
change the dictionary keys to use for the labels, jump on sole occurrence, etc.
- Extensible: provide your own jump targets and create Hop extensions!
# Getting started
@ -184,7 +122,7 @@ Plug 'phaazon/hop.nvim'
```lua
use {
'phaazon/hop.nvim',
branch = 'v1', -- optional but strongly recommended
branch = 'v2', -- optional but strongly recommended
config = function()
-- you can configure Hop the way you like here; see :h hop-config
require'hop'.setup { keys = 'etovxqpdygfblzhckisuran' }
@ -199,29 +137,7 @@ the last one**. If you are not, then you are exposed to compatibility issues / b
# Usage
A bunch of vim commands are available to get your fingers wrapped around **Hop** quickly:
- `:HopWord`: hop around by highlighting words.
- `:HopPattern`: hop around by matching against a pattern (as with `/`).
- `:HopChar1`: type a single key and hop to any occurrence of that key in the document.
- `:HopChar2`: type a bigram (two keys) and hop to any occurrence of that bigram in the document.
- `:HopLine`: jump to any visible line in your buffer.
- `:HopLineStart`: jump to any visible first non-whitespace character of each line in your buffer.
Most of these commands have variant to jump before / after the cursor, and on the current line. For instance,
`:HopChar1CurrentLineAC` is a form of `f` (Vim native motion) using Hop.
If you would rather use the Lua API, you can test it via the command prompt:
```vim
:lua require'hop'.hint_words()
```
For a more complete user guide and help pages:
```vim
:help hop
```
See the [wiki](https://github.com/phaazon/hop.nvim/wiki).
# Keybindings
@ -231,53 +147,22 @@ If you want to create a key binding from within Lua:
```lua
-- place this in one of your configuration file(s)
vim.api.nvim_set_keymap('n', 'f', "<cmd>lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })<cr>", {})
vim.api.nvim_set_keymap('n', 'F', "<cmd>lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })<cr>", {})
vim.api.nvim_set_keymap('o', 'f', "<cmd>lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true, inclusive_jump = true })<cr>", {})
vim.api.nvim_set_keymap('o', 'F', "<cmd>lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true, inclusive_jump = true })<cr>", {})
vim.api.nvim_set_keymap('', 't', "<cmd>lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })<cr>", {})
vim.api.nvim_set_keymap('', 'T', "<cmd>lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })<cr>", {})
vim.api.nvim_set_keymap('n', '<leader>e', "<cmd> lua require'hop'.hint_words({ hint_position = require'hop.hint'.HintPosition.END })<cr>", {})
vim.api.nvim_set_keymap('v', '<leader>e', "<cmd> lua require'hop'.hint_words({ hint_position = require'hop.hint'.HintPosition.END })<cr>", {})
vim.api.nvim_set_keymap('o', '<leader>e', "<cmd> lua require'hop'.hint_words({ hint_position = require'hop.hint'.HintPosition.END, inclusive_jump = true })<cr>", {})
local hop = require('hop')
local directions = require('hop.hint').HintDirection
vim.keymap.set('', 'f', function()
hop.hint_char1({ direction = directions.AFTER_CURSOR, current_line_only = true })
end, {remap=true})
vim.keymap.set('', 'F', function()
hop.hint_char1({ direction = directions.BEFORE_CURSOR, current_line_only = true })
end, {remap=true})
vim.keymap.set('', 't', function()
hop.hint_char1({ direction = directions.AFTER_CURSOR, current_line_only = true, hint_offset = -1 })
end, {remap=true})
vim.keymap.set('', 'T', function()
hop.hint_char1({ direction = directions.BEFORE_CURSOR, current_line_only = true, hint_offset = 1 })
end, {remap=true})
```
# Configuration
You can configure Hop via several different mechanisms:
- _Global configuration_ uses the Lua `setup` API (`:h hop.setup`). This allows you to setup global options that will be
used by all Hop Lua functions as well as the vim commands (e.g. `:HopWord`). This is the easiest way to configure Hop
on a global scale. You can do this in your `init.lua` or any `.vim` file by using the `lua` vim command.
Example:
```vim
" init.vim
"
" Use better keys for the bépo keyboard layout and set
" a balanced distribution of terminal / sequence keys
lua require'hop'.setup { keys = 'etovxqpdygfblzhckisuran', jump_on_sole_occurrence = false }
```
- _Local configuration overrides_ are available only on the Lua API and are `{opts}` Lua tables passed to the various
Lua functions. Those options have precedence over global options, so they allow to locally override options. Useful if
you want to test a special option for a single Lua function, such as `require'hop'.hint_lines()`. You can test them
inside the command line, such as:
```
:lua require'hop'.hint_words({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR })
```
- In the case of none of the above are provided, options are automatically read from the _default_ options. See `:h
hop-config` for a list of default values.
# Extension
It is possible to extend Hop by creating *Hop extension plugins*. For more info:
```vim
:h hop-extension
```
> Disclaimer: you may have written a nice Hop extension plugin. You can open an issue to merge it upstream but remember
> that its unlikely to be merged as Hop should remain small and straight-to-the point.
# Chat
Join the discussion on the official [Matrix room](https://matrix.to/#/#hop.nvim:matrix.org)!

View File

@ -1,4 +1,4 @@
*hop.txt* For Neovim version 0.5 Last change: 2021 Nov 02
*hop.txt* For Neovim version 0.5 Last change: 2022 Oct 09
__
/ /_ ____ ____
@ -7,6 +7,7 @@
/_/ /_/\____/ .___/
/_/
· Neovim motions on speed! ·
v2.0.2
==============================================================================
CONTENTS *hop-contents*
@ -177,6 +178,14 @@ creates a variant of the command that will only run for the current line.
This is akin to calling the |hop.hint_lines_skip_whitespace| Lua function.
`:HopVertical` *:HopVertical*
`:HopVerticalBC` *:HopVerticalBC*
`:HopVerticalAC` *:HopVerticalAC*
`:HopVerticalMW` *:HopVerticalMW*
Like `HopLine` but keeps the column position of the cursor.
This is akin to calling the |hop.hint_vertical| Lua function.
`:HopAnywhere` *:HopAnywhere*
`:HopAnywhereBC` *:HopAnywhereBC*
`:HopAnywhereAC` *:HopAnywhereAC*
@ -310,6 +319,16 @@ Most of the functions and values you need to know about are in `hop`.
Arguments:~
{opts} Hop options.
`hop.hint_vertical(`{opts}`)` *hop.hint_vertical*
Hint the cursor position of each lines currently visible in the buffer
view and allow to jump to them. If the line is shorter than the current
cursor column position, it will default to the end of the line.
This works with empty lines as well.
Arguments:~
{opts} Hop options.
`hop.hint_anywhere(`{opts}`)` *hop.hint_anywhere*
Annotate anywhere in the current window with key sequences.
@ -499,7 +518,7 @@ doing that for you.
of all the words in the window and will make the cursor jump to the one
fully reduced.
`hop.jump_target.regex_by_line_start()` *hop.jump_target.regex_by_line_start*
`hop.jump_target.by_line_start()` *hop.jump_target.by_line_start*
Highlight the beginning of each line in the current window.
`hop.jump_target.regex_by_line_start_skip_whitespace()` *hop.jump_target.regex_by_line_start_skip_whitespace*
@ -728,6 +747,18 @@ below.
Defaults:~
`hint_position = require'hop.hint'.HintPosition.BEGIN`
`hint_offset` *hop-config-hint_offset*
Offset to apply to a jump location.
If it is non-zero, the jump target will be offset horizontally from the
selected jump position by `hint_offset` character(s).
This option can be used for emulating the motion commands |t| and |T| where
the cursor is positioned on/before the target position.
Defaults:~
`hint_offset = 0`
`current_line_only` *hop-config-current_line_only*
Apply Hop commands only to the current line.
@ -738,22 +769,6 @@ below.
Defaults:~
`current_line_only = false`
`inclusive_jump` *hop-config-inclusive_jump*
Make all motions inclusive; i.e. jumping to a jump target will actually
jump one display cell right to the jump target. Set this option to `true`
if you would like to have the same behavior as with the |f| motion. Set it
to `false` if you would like to have the same behavior as with the |t|
motion.
There is one important limitation if you use `inclusive_jump = true`: if
the jump target you would like to jump to is the last character on a
line, it will not do what you expect; for instance, deleting or yanking
with `d` / `y` will not include the last character by default, unless you
set |virtualedit| to `onemore`.
Defaults:~
`inclusive_jump = false`
`uppercase_labels` *hop-config-uppercase_labels*
Display labels as uppercase. This option only affects the displayed
labels; you still select them by typing the keys on your keyboard.
@ -845,6 +860,9 @@ highlight is used. For the rest:
Highlight used for the fake cursor visible when running a Hop command /
Lua functions.
`HopPreview` *hop-hl-HopPreview*
Highlight used for to preview the hint for HopPattern.
Highlights are inserted in an augroup, `HopInitHighlight`, and an autocommand
is automatically set when initializing the plugin, unless you set
|hop-config-create_hl_autocmd| to `false`.

View File

@ -9,9 +9,9 @@ M.jump_on_sole_occurrence = true
M.case_insensitive = true
M.create_hl_autocmd = true
M.current_line_only = false
M.inclusive_jump = false
M.uppercase_labels = false
M.multi_windows = false
M.hint_position = require'hop.hint'.HintPosition.BEGIN
M.hint_offset = 0
return M

View File

@ -5,7 +5,7 @@ local hop = require'hop'
--
-- This function will perform checks at initialization to ensure everything will work as expected.
function M.check()
local health = require'health'
local health = vim.health or require'health'
health.report_start('Ensuring keys are unique')
local existing_keys = {}

View File

@ -19,6 +19,9 @@ function M.insert_highlights()
-- Highlight used for the fake cursor visible when hopping.
vim.api.nvim_command('highlight default link HopCursor Cursor')
-- Highlight used for preview pattern
vim.api.nvim_command('highlight link HopPreview IncSearch')
end
function M.create_autocmd()

View File

@ -99,8 +99,10 @@ function M.set_hint_extmarks(hl_ns, hints, opts)
label = label:upper()
end
local col = hint.jump_target.column - 1
if vim.fn.strdisplaywidth(label) == 1 then
vim.api.nvim_buf_set_extmark(hint.jump_target.buffer or 0, hl_ns, hint.jump_target.line, hint.jump_target.column - 1, {
vim.api.nvim_buf_set_extmark(hint.jump_target.buffer or 0, hl_ns, hint.jump_target.line, col, {
virt_text = { { label, "HopNextKey" } },
virt_text_pos = 'overlay',
hl_mode = 'combine',
@ -109,7 +111,7 @@ function M.set_hint_extmarks(hl_ns, hints, opts)
else
-- get the byte index of the second hint so that we can slice it correctly
local snd_idx = vim.fn.byteidx(label, 1)
vim.api.nvim_buf_set_extmark(hint.jump_target.buffer or 0, hl_ns, hint.jump_target.line, hint.jump_target.column - 1, { -- HERE
vim.api.nvim_buf_set_extmark(hint.jump_target.buffer or 0, hl_ns, hint.jump_target.line, col, {
virt_text = { { label:sub(1, snd_idx), "HopNextKey1" }, { label:sub(snd_idx + 1), "HopNextKey2" } },
virt_text_pos = 'overlay',
hl_mode = 'combine',
@ -119,4 +121,16 @@ function M.set_hint_extmarks(hl_ns, hints, opts)
end
end
function M.set_hint_preview(hl_ns, jump_targets)
for _, jt in ipairs(jump_targets) do
vim.api.nvim_buf_set_extmark(jt.buffer, hl_ns, jt.line, jt.column - 1, {
end_row = jt.line,
end_col = jt.column - 1 + jt.length,
hl_group = 'HopPreview',
hl_eol = true,
priority = prio.HINT_PRIO
})
end
end
return M

View File

@ -1,9 +1,3 @@
local defaults = require'hop.defaults'
local hint = require'hop.hint'
local jump_target = require'hop.jump_target'
local prio = require'hop.priority'
local window = require'hop.window'
local M = {}
-- Ensure options are sound.
@ -18,6 +12,9 @@ local function check_opts(opts)
if opts.multi_windows and opts.current_line_only then
vim.notify('Cannot use current_line_only across multiple windows', 3)
end
if vim.api.nvim_get_mode().mode ~= 'n' then
opts.multi_windows = false
end
end
-- Allows to override global options with user local overrides.
@ -33,18 +30,62 @@ local function eprintln(msg, teasing)
end
end
-- A hack to prevent #57 by deleting twice the namespace (its super weird).
local function clear_namespace(buf_handle, hl_ns)
vim.api.nvim_buf_clear_namespace(buf_handle, hl_ns, 0, -1)
vim.api.nvim_buf_clear_namespace(buf_handle, hl_ns, 0, -1)
-- Create hint state
--
-- {
-- all_ctxs: All windows's context
-- buf_list: All buffers displayed in all windows
-- <xxx>_ns: Required namespaces
-- }
local function create_hint_state(opts)
local window = require'hop.window'
local hint_state = {}
-- get all window's context and buffer list
hint_state.all_ctxs = window.get_window_context(opts.multi_windows)
hint_state.buf_list = {}
for _, bctx in ipairs(hint_state.all_ctxs) do
hint_state.buf_list[#hint_state.buf_list + 1] = bctx.hbuf
for _, wctx in ipairs(bctx.contexts) do
window.clip_window_context(wctx, opts.direction)
end
end
-- create the highlight groups; the highlight groups will allow us to clean everything at once when Hop quits
hint_state.hl_ns = vim.api.nvim_create_namespace('hop_hl')
hint_state.dim_ns = vim.api.nvim_create_namespace('hop_dim')
-- backup namespaces of diagnostic
if vim.fn.has("nvim-0.6") == 1 then
hint_state.diag_ns = vim.diagnostic.get_namespaces()
end
-- Store users cursorline state
hint_state.cursorline = vim.api.nvim_win_get_option(vim.api.nvim_get_current_win(), 'cursorline')
return hint_state
end
-- Dim everything out to prepare the Hop session.
-- A hack to prevent #57 by deleting twice the namespace (its super weird).
local function clear_namespace(buf_list, hl_ns)
for _, buf in ipairs(buf_list) do
if vim.api.nvim_buf_is_valid(buf) then
vim.api.nvim_buf_clear_namespace(buf, hl_ns, 0, -1)
vim.api.nvim_buf_clear_namespace(buf, hl_ns, 0, -1)
end
end
end
-- Set the highlight of unmatched lines of the buffer.
--
-- - 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 apply_dimming(buf_handle, hl_ns, top_line, bottom_line, cursor_pos, direction, current_line_only)
local function set_unmatched_lines(buf_handle, hl_ns, top_line, bottom_line, cursor_pos, direction, current_line_only)
local hint = require'hop.hint'
local prio = require'hop.priority'
local start_line = top_line
local end_line = bottom_line
local start_col = 0
@ -53,9 +94,8 @@ local function apply_dimming(buf_handle, hl_ns, top_line, bottom_line, cursor_po
if direction == hint.HintDirection.AFTER_CURSOR then
start_col = cursor_pos[2]
elseif direction == hint.HintDirection.BEFORE_CURSOR then
if cursor_pos[2] ~= 0 then
end_col = cursor_pos[2] + 1
end
end_line = bottom_line - 1
if cursor_pos[2] ~= 0 then end_col = cursor_pos[2] end
end
if current_line_only then
@ -68,13 +108,45 @@ local function apply_dimming(buf_handle, hl_ns, top_line, bottom_line, cursor_po
end
end
vim.api.nvim_buf_set_extmark(buf_handle, hl_ns, start_line, start_col, {
local extmark_options = {
end_line = end_line,
end_col = end_col,
hl_group = 'HopUnmatched',
hl_eol = true,
priority = prio.DIM_PRIO
})
}
if end_col then
local current_line = vim.api.nvim_buf_get_lines(buf_handle, cursor_pos[1] - 1, cursor_pos[1], true)[1]
local current_width = vim.fn.strdisplaywidth(current_line)
if end_col > current_width then
end_col = current_width - 1
end
extmark_options.end_col = end_col
end
vim.api.nvim_buf_set_extmark(buf_handle, hl_ns, start_line, start_col,
extmark_options)
end
-- Dim everything out to prepare the Hop session for all windows.
local function apply_dimming(hint_state, opts)
local window = require'hop.window'
for _, bctx in ipairs(hint_state.all_ctxs) do
for _, wctx in ipairs(bctx.contexts) do
window.clip_window_context(wctx, opts.direction)
-- dim everything out, add the virtual cursor and hide diagnostics
set_unmatched_lines(bctx.hbuf, hint_state.dim_ns, wctx.top_line, wctx.bot_line, wctx.cursor_pos, opts.direction, opts.current_line_only)
end
if vim.fn.has("nvim-0.6") == 1 then
for ns in pairs(hint_state.diag_ns) do
vim.diagnostic.show(ns, bctx.hbuf, nil, { virtual_text = false })
end
end
end
end
-- Add the virtual cursor, taking care to handle the cases where:
@ -83,6 +155,8 @@ end
-- - the current line is empty
-- - there are multibyte characters on the line
local function add_virt_cur(ns)
local prio = require'hop.priority'
local cur_info = vim.fn.getcurpos()
local cur_row = cur_info[2] - 1
local cur_col = cur_info[3] - 1 -- this gives cursor column location, in bytes
@ -90,6 +164,12 @@ local function add_virt_cur(ns)
local virt_col = cur_info[5] - 1
local cur_line = vim.api.nvim_get_current_line()
-- toggle cursorline off if currently set
local cursorline_info = vim.api.nvim_win_get_option(vim.api.nvim_get_current_win(), 'cursorline')
if cursorline_info == true then
vim.api.nvim_win_set_option(vim.api.nvim_get_current_win(), 'cursorline', false)
end
-- first check to see if cursor is in a tab char or past end of line
if cur_offset ~= 0 then
vim.api.nvim_buf_set_extmark(0, ns, cur_row, cur_col, {
@ -114,27 +194,112 @@ local function add_virt_cur(ns)
end
end
-- Get pattern from input for hint and preview
function M.get_input_pattern(prompt, maxchar, opts)
local hint = require'hop.hint'
local jump_target = require'hop.jump_target'
local hs = {}
if opts then
hs = create_hint_state(opts)
hs.preview_ns = vim.api.nvim_create_namespace('hop_preview')
apply_dimming(hs, opts)
add_virt_cur(hs.hl_ns)
end
local K_Esc = vim.api.nvim_replace_termcodes('<Esc>', true, false, true)
local K_BS = vim.api.nvim_replace_termcodes('<BS>', true, false, true)
local K_C_H = vim.api.nvim_replace_termcodes('<C-H>', true, false, true)
local K_CR = vim.api.nvim_replace_termcodes('<CR>', true, false, true)
local K_NL = vim.api.nvim_replace_termcodes('<NL>', true, false, true)
local pat_keys = {}
local pat = ''
while (true) do
pat = vim.fn.join(pat_keys, '')
if opts then
clear_namespace(hs.buf_list, hs.preview_ns)
if #pat > 0 then
local ok, re = pcall(jump_target.regex_by_case_searching, pat, false, opts)
if ok then
local jump_target_gtr = jump_target.jump_targets_by_scanning_lines(re)
local generated = jump_target_gtr(opts)
hint.set_hint_preview(hs.preview_ns, generated.jump_targets)
end
end
end
vim.api.nvim_echo({}, false, {})
vim.cmd('redraw')
vim.api.nvim_echo({{prompt, 'Question'}, {pat}}, false, {})
local ok, key = pcall(vim.fn.getchar)
if not ok then -- Interrupted by <C-c>
pat = nil
break
end
if type(key) == 'number' then
key = vim.fn.nr2char(key)
elseif key:byte() == 128 then
-- It's a special key in string
end
if key == K_Esc then
pat = nil
break
elseif key == K_CR or key == K_NL then
break
elseif key == K_BS or key == K_C_H then
pat_keys[#pat_keys] = nil
else
pat_keys[#pat_keys + 1] = key
end
if maxchar and #pat_keys >= maxchar then
pat = vim.fn.join(pat_keys, '')
break
end
end
if opts then
clear_namespace(hs.buf_list, hs.preview_ns)
-- quit only when got nothin for pattern to avoid blink of highlight
if not pat then M.quit(hs) end
end
vim.api.nvim_echo({}, false, {})
vim.cmd('redraw')
return pat
end
-- Move the cursor at a given location.
--
-- If inclusive is `true`, the jump target will be incremented visually by 1, so that operator-pending motions can
-- correctly take into account the right offset. This is the main difference between motions such as `f` (inclusive)
-- and `t` (exclusive).
-- Add option to shift cursor by column offset
--
-- This function will update the jump list.
function M.move_cursor_to(w, line, column, inclusive)
-- If we do not ask for inclusive jump, we dont have to retreive any additional lines because we will jump to the
-- actual jump target. If we do want an inclusive jump, we need to retreive the line the jump target lies in so that
-- we can compute the offset correctly. This is linked to the fact that currently, Neovim doesns have an API to «
-- offset something by 1 visual column. »
if inclusive then
function M.move_cursor_to(w, line, column, hint_offset, direction)
-- If we do not ask for an offset jump, we dont have to retrieve any additional lines because we will jump to the
-- actual jump target. If we do want a jump with an offset, we need to retrieve the line the jump target lies in so
-- that we can compute the offset correctly. This is linked to the fact that currently, Neovim doesns have an API to
-- « offset something by N visual columns. »
-- If it is pending for operator shift column to the right by 1
if vim.api.nvim_get_mode().mode == 'no' and direction ~= 1 then
column = column + 1
end
if hint_offset ~= nil and not (hint_offset == 0) then
-- Add `hint_offset` based on `charidx`.
local buf_line = vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(w), line - 1, line, false)[1]
column = vim.fn.byteidx(buf_line, column + 1)
-- Since `charidx` returns -1 when `column` is the tail, subtract 1 and add 1 to the return value to get
-- the correct value.
local char_idx = vim.fn.charidx(buf_line, column - 1) + 1 + hint_offset
column = vim.fn.byteidx(buf_line, char_idx)
end
-- update the jump list
vim.cmd("normal! m'")
vim.api.nvim_set_current_win(w)
vim.api.nvim_win_set_cursor(w, { line, column})
vim.api.nvim_win_set_cursor(w, { line, column })
end
function M.hint_with(jump_target_gtr, opts)
@ -143,11 +308,13 @@ function M.hint_with(jump_target_gtr, opts)
end
M.hint_with_callback(jump_target_gtr, opts, function(jt)
M.move_cursor_to(jt.window, jt.line + 1, jt.column - 1, opts.inclusive_jump)
M.move_cursor_to(jt.window, jt.line + 1, jt.column - 1, opts.hint_offset, opts.direction)
end)
end
function M.hint_with_callback(jump_target_gtr, opts, callback)
local hint = require'hop.hint'
if opts == nil then
opts = override_opts(opts)
end
@ -157,64 +324,49 @@ function M.hint_with_callback(jump_target_gtr, opts, callback)
return
end
local all_ctxs = window.get_window_context(opts.multi_windows)
-- create the highlight groups; the highlight groups will allow us to clean everything at once when Hop quits
local hl_ns = vim.api.nvim_create_namespace('hop_hl')
local dim_ns = vim.api.nvim_create_namespace('')
-- create hint state
local hs = create_hint_state(opts)
-- create jump targets
local generated = jump_target_gtr(opts)
local jump_target_count = #generated.jump_targets
local h = nil
local target_idx = nil
if jump_target_count == 0 then
eprintln(' -> theres no such thing we can see…', opts.teasing)
clear_namespace(0, hl_ns)
clear_namespace(0, dim_ns)
return
target_idx = 0
elseif vim.v.count > 0 then
target_idx = vim.v.count
elseif jump_target_count == 1 and opts.jump_on_sole_occurrence then
local jt = generated.jump_targets[1]
callback(jt)
target_idx = 1
end
clear_namespace(0, hl_ns)
clear_namespace(0, dim_ns)
if target_idx ~= nil then
local jt = generated.jump_targets[target_idx]
if jt then
callback(jt)
else
eprintln(' -> theres no such thing we can see…', opts.teasing)
end
clear_namespace(hs.buf_list, hs.hl_ns)
clear_namespace(hs.buf_list, hs.dim_ns)
return
end
-- we have at least two targets, so generate hints to display
local hints = hint.create_hints(generated.jump_targets, generated.indirect_jump_targets, opts)
hs.hints = hint.create_hints(generated.jump_targets, generated.indirect_jump_targets, opts)
local hint_state = {
hints = hints,
hl_ns = hl_ns,
dim_ns = dim_ns,
}
local buf_list = {}
for _, bctx in ipairs(all_ctxs) do
buf_list[#buf_list + 1] = bctx.hbuf
for _, wctx in ipairs(bctx.contexts) do
window.clip_window_context(wctx, opts.direction)
-- dim everything out, add the virtual cursor and hide diagnostics
apply_dimming(bctx.hbuf, dim_ns, wctx.top_line, wctx.bot_line, wctx.cursor_pos, opts.direction, opts.current_line_only)
end
end
add_virt_cur(hl_ns)
if vim.fn.has("nvim-0.6") == 1 then
hint_state.diag_ns = vim.diagnostic.get_namespaces()
for ns in pairs(hint_state.diag_ns) do vim.diagnostic.show(ns, 0, nil, { virtual_text = false }) end
end
hint.set_hint_extmarks(hl_ns, hints, opts)
-- dim everything out, add the virtual cursor and hide diagnostics
apply_dimming(hs, opts)
add_virt_cur(hs.hl_ns)
hint.set_hint_extmarks(hs.hl_ns, hs.hints, opts)
vim.cmd('redraw')
local h = nil
while h == nil do
local ok, key = pcall(vim.fn.getchar)
if not ok then
for _, buf in ipairs(buf_list) do
M.quit(buf, hint_state)
end
M.quit(hs)
break
end
local not_special_key = true
@ -232,13 +384,11 @@ function M.hint_with_callback(jump_target_gtr, opts, callback)
if not_special_key and opts.keys:find(key, 1, true) then
-- If this is a key used in Hop (via opts.keys), deal with it in Hop
h = M.refine_hints(buf_list, key, hint_state, callback, opts)
h = M.refine_hints(key, hs, callback, opts)
vim.cmd('redraw')
else
-- If it's not, quit Hop
for _, buf in ipairs(buf_list) do
M.quit(buf, hint_state)
end
M.quit(hs)
-- If the key captured via getchar() is not the quit_key, pass it through
-- to nvim to be handled normally (including mappings)
if key ~= vim.api.nvim_replace_termcodes(opts.quit_key, true, false, true) then
@ -253,7 +403,9 @@ 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_list, key, hint_state, callback, opts)
function M.refine_hints(key, hint_state, callback, opts)
local hint = require'hop.hint'
local h, hints = hint.reduce_hints(hint_state.hints, key)
if h == nil then
@ -264,15 +416,10 @@ function M.refine_hints(buf_list, key, hint_state, callback, opts)
hint_state.hints = hints
for _, buf in ipairs(buf_list) do
clear_namespace(buf, hint_state.hl_ns)
end
clear_namespace(hint_state.buf_list, hint_state.hl_ns)
hint.set_hint_extmarks(hint_state.hl_ns, hints, opts)
vim.cmd('redraw')
else
for _, buf in ipairs(buf_list) do
M.quit(buf, hint_state)
end
M.quit(hint_state)
-- prior to jump, register the current position into the jump list
vim.cmd("normal! m'")
@ -283,16 +430,28 @@ function M.refine_hints(buf_list, key, hint_state, callback, opts)
end
-- Quit Hop and delete its resources.
function M.quit(buf_handle, hint_state)
clear_namespace(buf_handle, hint_state.hl_ns)
clear_namespace(buf_handle, hint_state.dim_ns)
function M.quit(hint_state)
clear_namespace(hint_state.buf_list, hint_state.hl_ns)
clear_namespace(hint_state.buf_list, hint_state.dim_ns)
if vim.fn.has("nvim-0.6") == 1 then
for ns in pairs(hint_state.diag_ns) do vim.diagnostic.show(ns, buf_handle) end
-- Restore users cursorline setting
if hint_state.cursorline == true then
vim.api.nvim_win_set_option(vim.api.nvim_get_current_win(), 'cursorline', true)
end
for _, buf in ipairs(hint_state.buf_list) do
-- sometimes, buffers might be unloaded; thats the case with floats for instance (we can invoke Hop from them but
-- then they disappear); we need to check whether the buffer is still valid before trying to do anything else with
-- it
if vim.api.nvim_buf_is_valid(buf) and vim.fn.has("nvim-0.6") == 1 then
for ns in pairs(hint_state.diag_ns) do vim.diagnostic.show(ns, buf) end
end
end
end
function M.hint_words(opts)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
local generator
@ -309,20 +468,26 @@ function M.hint_words(opts)
end
function M.hint_patterns(opts, pattern)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
-- The pattern to search is either retrieved from the (optional) argument
-- or directly from user input.
if pattern == nil then
local pat
if pattern then
pat = pattern
else
vim.cmd('redraw')
vim.fn.inputsave()
local ok
ok, pattern = pcall(vim.fn.input, 'Search: ')
pat = M.get_input_pattern('Hop pattern: ', nil, opts)
vim.fn.inputrestore()
if not pat then return end
end
if not ok then
return
end
if #pat == 0 then
eprintln('-> empty pattern', opts.teasing)
return
end
local generator
@ -333,16 +498,18 @@ function M.hint_patterns(opts, pattern)
end
M.hint_with(
generator(jump_target.regex_by_case_searching(pattern, false, opts)),
generator(jump_target.regex_by_case_searching(pat, false, opts)),
opts
)
end
function M.hint_char1(opts)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
local ok, c = pcall(vim.fn.getchar)
if not ok then
local c = M.get_input_pattern('Hop 1 char: ', 1)
if not c then
return
end
@ -354,32 +521,21 @@ function M.hint_char1(opts)
end
M.hint_with(
generator(jump_target.regex_by_case_searching(vim.fn.nr2char(c), true, opts)),
generator(jump_target.regex_by_case_searching(c, true, opts)),
opts
)
end
function M.hint_char2(opts)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
local ok, a = pcall(vim.fn.getchar)
if not ok then
local c = M.get_input_pattern('Hop 2 char: ', 2)
if not c then
return
end
local ok2, b = pcall(vim.fn.getchar)
if not ok2 then
return
end
local pattern = vim.fn.nr2char(a)
-- if we have a fallback key defined in the opts, if the second character is that key, we then fallback to the same
-- behavior as hint_char1()
if opts.char2_fallback_key == nil or b ~= vim.fn.char2nr(vim.api.nvim_replace_termcodes(opts.char2_fallback_key, true, false, true)) then
pattern = pattern .. vim.fn.nr2char(b)
end
local generator
if opts.current_line_only then
generator = jump_target.jump_targets_for_current_line
@ -388,12 +544,14 @@ function M.hint_char2(opts)
end
M.hint_with(
generator(jump_target.regex_by_case_searching(pattern, true, opts)),
generator(jump_target.regex_by_case_searching(c, true, opts)),
opts
)
end
function M.hint_lines(opts)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
local generator
@ -404,12 +562,37 @@ function M.hint_lines(opts)
end
M.hint_with(
generator(jump_target.regex_by_line_start()),
generator(jump_target.by_line_start()),
opts
)
end
function M.hint_vertical(opts)
local hint = require'hop.hint'
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
-- only makes sense as end position given movement goal.
opts.hint_position = hint.HintPosition.END
local generator
if opts.current_line_only then
generator = jump_target.jump_targets_for_current_line
else
generator = jump_target.jump_targets_by_scanning_lines
end
-- FIXME: need to exclude current and include empty lines.
M.hint_with(
generator(jump_target.regex_by_vertical()),
opts
)
end
function M.hint_lines_skip_whitespace(opts)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
local generator
@ -426,6 +609,8 @@ function M.hint_lines_skip_whitespace(opts)
end
function M.hint_anywhere(opts)
local jump_target = require'hop.jump_target'
opts = override_opts(opts)
local generator
@ -444,7 +629,7 @@ end
-- Setup user settings.
function M.setup(opts)
-- Look up keys in user-defined table with fallback to defaults.
M.opts = setmetatable(opts or {}, {__index = defaults})
M.opts = setmetatable(opts or {}, {__index = require'hop.defaults'})
M.initialized = true
-- Insert the highlights and register the autocommand if asked to.

View File

@ -91,6 +91,13 @@ local function mark_jump_targets_line(buf_handle, win_handle, regex, line_contex
if b == nil or (b == 0 and e == 0) then
break
end
-- Preview need a length to highlight the matched string. Zero means nothing to highlight.
local matched_length = e - b
-- As the make for jump target must be placed at a cell (but some pattern like '^' is
-- placed between cells), we should make sure e > b
if b == e then
e = e + 1
end
local colp = col + b
if hint_position == hint.HintPosition.MIDDLE then
@ -99,10 +106,9 @@ local function mark_jump_targets_line(buf_handle, win_handle, regex, line_contex
colp = col + e - 1
end
jump_targets[#jump_targets + 1] = {
line = line_nr,
column = math.max(1, colp + col_offset + col_bias),
line = line_context.line_nr,
column = math.max(1, colp + col_offset + col_bias),
length = math.max(0, matched_length),
buffer = buf_handle,
window = win_handle,
}
@ -342,10 +348,13 @@ function M.regex_by_searching(pat, plain_search)
if plain_search then
pat = vim.fn.escape(pat, '\\/.$^~[]')
end
local regex = vim.regex(pat)
return {
oneshot = false,
match = function(s)
return vim.regex(pat):match_str(s)
return regex:match_str(s)
end
}
end
@ -364,10 +373,12 @@ function M.regex_by_case_searching(pat, plain_search, opts)
pat = '\\c' .. pat
end
local regex = vim.regex(pat)
return {
oneshot = false,
match = function(s)
return vim.regex(pat):match_str(s)
return regex:match_str(s)
end
}
end
@ -378,22 +389,42 @@ function M.regex_by_word_start()
end
-- Line regex.
function M.regex_by_line_start()
function M.by_line_start()
local c = vim.fn.winsaveview().leftcol
return {
oneshot = true,
match = function(_)
return 0, 1, false
match = function(s)
local l = vim.fn.strdisplaywidth(s)
if c > 0 and l == 0 then
return nil
end
return 0, 1
end
}
end
-- Line regex at cursor position.
function M.regex_by_vertical()
local position = vim.api.nvim_win_get_cursor(0)[2]
local regex = vim.regex(string.format("^.\\{0,%d\\}\\(.\\|$\\)", position))
return {
oneshot = true,
match = function(s)
return regex:match_str(s)
end
}
end
-- Line regex skipping finding the first non-whitespace character on each line.
function M.regex_by_line_start_skip_whitespace()
local pat = vim.regex("\\S")
local regex = vim.regex("\\S")
return {
oneshot = true,
match = function(s)
return pat:match_str(s)
return regex:match_str(s)
end
}
end

View File

@ -7,7 +7,7 @@ end
-- Get the next key of the input key in the input key set, if any, or return nil.
local function next_key(keys, key)
local _, e = keys:find(key)
local _, e = keys:find(key, 1, true)
if e == #keys then
return nil

View File

@ -4,6 +4,7 @@ local M = {}
local function window_context(win_handle, cursor_pos)
-- get a bunch of information about the window and the cursor
vim.api.nvim_set_current_win(win_handle)
local win_info = vim.fn.getwininfo(win_handle)[1]
local win_view = vim.fn.winsaveview()
local top_line = win_info.topline - 1
@ -52,9 +53,10 @@ function M.get_window_context(multi_windows)
-- Generate contexts of windows
local cur_hwin = vim.api.nvim_get_current_win()
local cur_hbuf = vim.api.nvim_win_get_buf(cur_hwin)
all_ctxs[#all_ctxs + 1] = {
hbuf = cur_hbuf,
contexts = { window_context(cur_hwin, vim.api.nvim_win_get_cursor(cur_hwin)) },
contexts = { window_context(cur_hwin, {vim.fn.line('.'), vim.fn.charcol('.')} ) },
}
if not multi_windows then
@ -131,7 +133,7 @@ end
-- If the direction is HintDirection.AFTER_CURSOR, then everything before the cursor will be clipped.
function M.clip_window_context(context, direction)
if direction == hint.HintDirection.BEFORE_CURSOR then
context.bot_line = context.cursor_pos[1] - 1
context.bot_line = context.cursor_pos[1]
elseif direction == hint.HintDirection.AFTER_CURSOR then
context.top_line = context.cursor_pos[1] - 1
end

View File

@ -47,12 +47,18 @@ command! HopLineBC lua require'hop'.hint_lines({ direction = require'hop.hint'.H
command! HopLineAC lua require'hop'.hint_lines({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR })
command! HopLineMW lua require'hop'.hint_lines({ multi_windows = true })
" The jump-to-line command.
" The jump-to-line command (non-whitespace).
command! HopLineStart lua require'hop'.hint_lines_skip_whitespace()
command! HopLineStartBC lua require'hop'.hint_lines_skip_whitespace({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR })
command! HopLineStartAC lua require'hop'.hint_lines_skip_whitespace({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR })
command! HopLineStartMW lua require'hop'.hint_lines_skip_whitespace({ multi_windows = true })
" The vertical command (line jump preserving the column cursor position).
command! HopVertical lua require'hop'.hint_vertical()
command! HopVerticalBC lua require'hop'.hint_vertical({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR })
command! HopVerticalAC lua require'hop'.hint_vertical({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR })
command! HopVerticalMW lua require'hop'.hint_vertical({ multi_windows = true })
" The jump-to-anywhere command.
command! HopAnywhere lua require'hop'.hint_anywhere()
command! HopAnywhereBC lua require'hop'.hint_anywhere({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR })