mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-23 07:20:04 +08:00
feat(hop): add hop.nvim for neovim v0.6.0
This commit is contained in:
parent
550fd06375
commit
e360f0c9d5
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@ -8,6 +8,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install git
|
||||
- name: Run vint with reviewdog
|
||||
uses: reviewdog/action-vint@v1.0.1
|
||||
with:
|
||||
|
@ -188,19 +188,6 @@ function! SpaceVim#layers#core#config() abort
|
||||
\ . string(s:_function('s:explore_current_dir')) . ', [1])',
|
||||
\ 'split-explore-current-directory', 1)
|
||||
|
||||
" call SpaceVim#mapping#space#def('nmap', ['j', 'j'], '<Plug>(easymotion-overwin-f)', 'jump to a character', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'j'], '<Plug>(better-easymotion-overwin-f)', 'jump-or-select-to-a-character', 0, 1)
|
||||
nnoremap <silent> <Plug>(better-easymotion-overwin-f) :call <SID>better_easymotion_overwin_f(0)<Cr>
|
||||
xnoremap <silent> <Plug>(better-easymotion-overwin-f) :<C-U>call <SID>better_easymotion_overwin_f(1)<Cr>
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'J'], '<Plug>(easymotion-overwin-f2)', 'jump-to-suite-of-two-characters', 0)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'k'], 'j==', 'goto-next-line-and-indent', 0)
|
||||
" call SpaceVim#mapping#space#def('nmap', ['j', 'l'], '<Plug>(easymotion-overwin-line)', 'jump to a line', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'l'], '<Plug>(better-easymotion-overwin-line)', 'jump-or-select-to-a-line', 0, 1)
|
||||
nnoremap <silent> <Plug>(better-easymotion-overwin-line) :call <SID>better_easymotion_overwin_line(0)<Cr>
|
||||
xnoremap <silent> <Plug>(better-easymotion-overwin-line) :<C-U>call <SID>better_easymotion_overwin_line(1)<Cr>
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'v'], '<Plug>(easymotion-overwin-line)', 'jump-to-a-line', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'w'], '<Plug>(easymotion-overwin-w)', 'jump-to-a-word', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'q'], '<Plug>(easymotion-overwin-line)', 'jump-to-a-line', 0)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'n'], "i\<cr>\<esc>", 'sp-newline', 0)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'c'], 'call call('
|
||||
\ . string(s:_function('s:jump_last_change')) . ', [])',
|
||||
@ -220,9 +207,6 @@ function! SpaceVim#layers#core#config() abort
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['w', 'R'], 'call call('
|
||||
\ . string(s:_function('s:previous_window')) . ', [])',
|
||||
\ 'rotate-windows-backward', 1)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'u'], 'call call('
|
||||
\ . string(s:_function('s:jump_to_url')) . ', [])',
|
||||
\ 'jump-to-url', 1)
|
||||
call SpaceVim#mapping#def('nnoremap <silent>', '<S-Tab>', ':wincmd p<CR>', 'Switch to previous window or tab','wincmd p')
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['<Tab>'], 'try | b# | catch | endtry', 'last-buffer', 1)
|
||||
let lnum = expand('<slnum>') + s:lnum - 1
|
||||
@ -610,11 +594,6 @@ else
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! s:jump_to_url() abort
|
||||
let g:EasyMotion_re_anywhere = 'http[s]*://'
|
||||
call feedkeys("\<Plug>(easymotion-jumptoanywhere)")
|
||||
endfunction
|
||||
|
||||
function! s:safe_erase_buffer() abort
|
||||
if s:MESSAGE.confirm('Erase content of buffer ' . expand('%:t'))
|
||||
normal! ggdG
|
||||
@ -857,50 +836,6 @@ function! s:comment_invert_yank(visual) range abort
|
||||
call feedkeys("\<Plug>NERDCommenterInvert")
|
||||
endfunction
|
||||
|
||||
function! s:better_easymotion_overwin_line(is_visual) abort
|
||||
let current_line = line('.')
|
||||
try
|
||||
if a:is_visual
|
||||
call EasyMotion#Sol(0, 2)
|
||||
else
|
||||
call EasyMotion#overwin#line()
|
||||
endif
|
||||
" clear cmd line
|
||||
noautocmd normal! :
|
||||
if a:is_visual
|
||||
let last_line = line('.')
|
||||
exe current_line
|
||||
if last_line > current_line
|
||||
exe 'normal! V' . (last_line - current_line) . 'j'
|
||||
else
|
||||
exe 'normal! V' . (current_line - last_line) . 'k'
|
||||
endif
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:better_easymotion_overwin_f(is_visual) abort
|
||||
let [current_line, current_col] = getpos('.')[1:2]
|
||||
try
|
||||
call EasyMotion#OverwinF(1)
|
||||
" clear cmd line
|
||||
noautocmd normal! :
|
||||
if a:is_visual
|
||||
let last_line = line('.')
|
||||
let [last_line, last_col] = getpos('.')[1:2]
|
||||
call cursor(current_line, current_col)
|
||||
if last_line > current_line
|
||||
exe 'normal! v' . (last_line - current_line) . 'j0' . last_col . '|'
|
||||
else
|
||||
exe 'normal! v' . (current_line - last_line) . 'k0' . last_col . '|'
|
||||
endif
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:comment_paragraphs(invert) abort
|
||||
if a:invert
|
||||
|
@ -48,6 +48,29 @@
|
||||
" >
|
||||
" autosave_location/path+=to+=filename.ext.backup
|
||||
" <
|
||||
" 5. `enable_hop`: by default, spacevim use easymotion plugin. and if you are
|
||||
" using neovim 0.6.0 or above, hop.nvim will be enabled. You can disabled this
|
||||
" plugin and still using easymotion.
|
||||
"
|
||||
" @subsection key bindings
|
||||
"
|
||||
" The `edit` layer also provides many key bindings:
|
||||
" >
|
||||
" key binding description
|
||||
" SPC x c count in the selection region
|
||||
" <
|
||||
"
|
||||
" The following key binding is to jump to targets. The default plugin is
|
||||
" `easymotion`, and if you are using neovim 0.6.0 or above. The `hop.nvim` will
|
||||
" be used.
|
||||
" >
|
||||
" key binding description
|
||||
" SPC j j jump or select a character
|
||||
" SPC j J jump to suite of two characters
|
||||
" SPC j l jump or select to a line
|
||||
" SPC j w jump to a word
|
||||
" SPC j u jump to a url
|
||||
" <
|
||||
|
||||
scriptencoding utf-8
|
||||
if exists('s:autosave_timeout')
|
||||
@ -66,6 +89,7 @@ let s:autosave_timeout = 0
|
||||
let s:autosave_events = []
|
||||
let s:autosave_all_buffers = 0
|
||||
let s:autosave_location = ''
|
||||
let s:enable_hop = 1
|
||||
|
||||
function! SpaceVim#layers#edit#health() abort
|
||||
call SpaceVim#layers#edit#plugins()
|
||||
@ -85,13 +109,17 @@ function! SpaceVim#layers#edit#plugins() abort
|
||||
\ [g:_spacevim_root_dir . 'bundle/vim-table-mode'],
|
||||
\ [g:_spacevim_root_dir . 'bundle/vim-textobj-entire'],
|
||||
\ [g:_spacevim_root_dir . 'bundle/wildfire.vim',{'on_map' : '<Plug>(wildfire-'}],
|
||||
\ [g:_spacevim_root_dir . 'bundle/vim-easymotion'],
|
||||
\ [g:_spacevim_root_dir . 'bundle/vim-easyoperator-line'],
|
||||
\ [g:_spacevim_root_dir . 'bundle/editorconfig-vim', { 'merged' : 0, 'if' : has('python') || has('python3')}],
|
||||
\ [g:_spacevim_root_dir . 'bundle/vim-jplus', { 'on_map' : '<Plug>(jplus' }],
|
||||
\ [g:_spacevim_root_dir . 'bundle/tabular', { 'merged' : 0}],
|
||||
\ ['andrewradev/splitjoin.vim',{ 'on_cmd':['SplitjoinJoin', 'SplitjoinSplit'],'merged' : 0, 'loadconf' : 1}],
|
||||
\ ]
|
||||
if has('nvim-0.6.0')
|
||||
call add(plugins,[g:_spacevim_root_dir . 'bundle/hop.nvim', { 'merged' : 0, 'loadconf' : 1}])
|
||||
else
|
||||
call add(plugins,[g:_spacevim_root_dir . 'bundle/vim-easymotion', { 'merged' : 0}])
|
||||
call add(plugins,[g:_spacevim_root_dir . 'bundle/vim-easyoperator-line', { 'merged' : 0}])
|
||||
endif
|
||||
if executable('fcitx')
|
||||
call add(plugins,[g:_spacevim_root_dir . 'bundle/fcitx.vim', { 'on_event' : 'InsertEnter'}])
|
||||
endif
|
||||
@ -111,6 +139,7 @@ function! SpaceVim#layers#edit#set_variable(var) abort
|
||||
let s:autosave_events = get(a:var, 'autosave_events', s:autosave_events)
|
||||
let s:autosave_all_buffers = get(a:var, 'autosave_all_buffers', s:autosave_all_buffers)
|
||||
let s:autosave_location = get(a:var, 'autosave_location', s:autosave_location)
|
||||
let s:enable_hop = get(a:var, 'enable_hop', s:enable_hop)
|
||||
endfunction
|
||||
|
||||
function! SpaceVim#layers#edit#get_options() abort
|
||||
@ -329,8 +358,68 @@ function! SpaceVim#layers#edit#config() abort
|
||||
\ 'SplitjoinJoin', 'join into a single-line statement', 1)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'm'],
|
||||
\ 'SplitjoinSplit', 'split a one-liner into multiple lines', 1)
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'k'], 'j==', 'goto-next-line-and-indent', 0)
|
||||
|
||||
if has('nvim-0.6.0') && s:enable_hop
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'j'], 'HopChar1', 'jump-or-select-to-a-character', 1, 1)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'J'], 'HopChar2', 'jump-to-suite-of-two-characters', 1, 1)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'l'], 'HopLine', 'jump-or-select-to-a-line', 1, 1)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'w'], 'HopWord', 'jump-to-a-word', 1, 1)
|
||||
else
|
||||
" call SpaceVim#mapping#space#def('nmap', ['j', 'j'], '<Plug>(easymotion-overwin-f)', 'jump to a character', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'j'], '<Plug>(better-easymotion-overwin-f)', 'jump-or-select-to-a-character', 0, 1)
|
||||
nnoremap <silent> <Plug>(better-easymotion-overwin-f) :call <SID>better_easymotion_overwin_f(0)<Cr>
|
||||
xnoremap <silent> <Plug>(better-easymotion-overwin-f) :<C-U>call <SID>better_easymotion_overwin_f(1)<Cr>
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'J'], '<Plug>(easymotion-overwin-f2)', 'jump-to-suite-of-two-characters', 0)
|
||||
" call SpaceVim#mapping#space#def('nmap', ['j', 'l'], '<Plug>(easymotion-overwin-line)', 'jump to a line', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'l'], '<Plug>(better-easymotion-overwin-line)', 'jump-or-select-to-a-line', 0, 1)
|
||||
nnoremap <silent> <Plug>(better-easymotion-overwin-line) :call <SID>better_easymotion_overwin_line(0)<Cr>
|
||||
xnoremap <silent> <Plug>(better-easymotion-overwin-line) :<C-U>call <SID>better_easymotion_overwin_line(1)<Cr>
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'v'], '<Plug>(easymotion-overwin-line)', 'jump-to-a-line', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'w'], '<Plug>(easymotion-overwin-w)', 'jump-to-a-word', 0)
|
||||
call SpaceVim#mapping#space#def('nmap', ['j', 'q'], '<Plug>(easymotion-overwin-line)', 'jump-to-a-line', 0)
|
||||
endif
|
||||
call SpaceVim#mapping#space#def('nnoremap', ['j', 'u'], 'call call('
|
||||
\ . string(s:_function('s:jump_to_url')) . ', [])',
|
||||
\ 'jump-to-url', 1)
|
||||
endfunction
|
||||
|
||||
if has('nvim-0.6.0')
|
||||
" Hop
|
||||
lua << EOF
|
||||
-- Like hop.jump_target.regex_by_line_start_skip_whitespace() except it also
|
||||
-- marks empty or whitespace only lines
|
||||
function regexLines()
|
||||
return {
|
||||
oneshot = true,
|
||||
match = function(str)
|
||||
return vim.regex("http[s]*://"):match_str(str)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Like :HopLineStart except it also jumps to empty or whitespace only lines
|
||||
function hintLines(opts)
|
||||
-- Taken from override_opts()
|
||||
opts = setmetatable(opts or {}, {__index = require'hop'.opts})
|
||||
|
||||
local gen = require'hop.jump_target'.jump_targets_by_scanning_lines
|
||||
require'hop'.hint_with(gen(regexLines()), opts)
|
||||
end
|
||||
EOF
|
||||
|
||||
|
||||
" See `:h forced-motion` for these operator-pending mappings
|
||||
function! s:jump_to_url() abort
|
||||
lua hintLines()
|
||||
endfunction
|
||||
else
|
||||
function! s:jump_to_url() abort
|
||||
let g:EasyMotion_re_anywhere = 'http[s]*://'
|
||||
call feedkeys("\<Plug>(easymotion-jumptoanywhere)")
|
||||
endfunction
|
||||
endif
|
||||
|
||||
function! s:transpose_with_previous(type) abort
|
||||
let l:save_register = @"
|
||||
if a:type ==# 'line'
|
||||
@ -381,6 +470,51 @@ function! s:transpose_with_next(type) abort
|
||||
let @" = l:save_register
|
||||
endfunction
|
||||
|
||||
function! s:better_easymotion_overwin_line(is_visual) abort
|
||||
let current_line = line('.')
|
||||
try
|
||||
if a:is_visual
|
||||
call EasyMotion#Sol(0, 2)
|
||||
else
|
||||
call EasyMotion#overwin#line()
|
||||
endif
|
||||
" clear cmd line
|
||||
noautocmd normal! :
|
||||
if a:is_visual
|
||||
let last_line = line('.')
|
||||
exe current_line
|
||||
if last_line > current_line
|
||||
exe 'normal! V' . (last_line - current_line) . 'j'
|
||||
else
|
||||
exe 'normal! V' . (current_line - last_line) . 'k'
|
||||
endif
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:better_easymotion_overwin_f(is_visual) abort
|
||||
let [current_line, current_col] = getpos('.')[1:2]
|
||||
try
|
||||
call EasyMotion#OverwinF(1)
|
||||
" clear cmd line
|
||||
noautocmd normal! :
|
||||
if a:is_visual
|
||||
let last_line = line('.')
|
||||
let [last_line, last_col] = getpos('.')[1:2]
|
||||
call cursor(current_line, current_col)
|
||||
if last_line > current_line
|
||||
exe 'normal! v' . (last_line - current_line) . 'j0' . last_col . '|'
|
||||
else
|
||||
exe 'normal! v' . (current_line - last_line) . 'k0' . last_col . '|'
|
||||
endif
|
||||
endif
|
||||
catch /^Vim\%((\a\+)\)\=:E117/
|
||||
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:move_text_down_transient_state() abort
|
||||
if line('.') == line('$')
|
||||
else
|
||||
|
1
bundle/hop.nvim/.gitignore
vendored
Normal file
1
bundle/hop.nvim/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/doc/tags
|
192
bundle/hop.nvim/CONTRIBUTING.md
Normal file
192
bundle/hop.nvim/CONTRIBUTING.md
Normal file
@ -0,0 +1,192 @@
|
||||
# Contributing
|
||||
|
||||
This document is the official contribution guide contributors must follow. It will be **greatly appreciated** if you
|
||||
read it first before contributing. It will also prevent you from losing your time if you open an issue / make a PR that
|
||||
doesn’t comply to this document.
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Disclaimer and why this document](#disclaimer-and-why-this-document)
|
||||
* [How to make a change](#how-to-make-a-change)
|
||||
* [Process](#process)
|
||||
* [Conventions](#conventions)
|
||||
* [Coding](#coding)
|
||||
* [Git](#git)
|
||||
* [Git message](#git-message)
|
||||
* [Commit atomicity](#commit-atomicity)
|
||||
* [Hygiene](#hygiene)
|
||||
* [Release process](#release-process)
|
||||
* [Overall process](#overall-process)
|
||||
* [Changelogs update](#changelogs-update)
|
||||
* [Git tag](#git-tag)
|
||||
* [Support and donation](#support-and-donation)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
# Disclaimer and why this document
|
||||
|
||||
People contributing is awesome. The more people contribute to Free & Open-Source software, the better the
|
||||
world is to me. However, the more people contribute, the more work we have to do on our spare-time. Good
|
||||
contributions are highly appreciated, especially if they thoroughly follow the conventions and guidelines of
|
||||
each and every repository. However, bad contributions — that don’t follow this document, for instance — are
|
||||
going to require me more work than was involved into making the actual change. It’s even worse when the contribution
|
||||
actually solves a bug or add a new feature.
|
||||
|
||||
So please read this document; it’s not hard and the few rules here are easy to respect. You might already do
|
||||
everything in this list anyway, but reading it won’t hurt you. For more junior / less-experienced developers, it’s
|
||||
very likely you will learn a bit of process that is considered good practice, especially when working with VCS like
|
||||
Git.
|
||||
|
||||
> Thank you!
|
||||
|
||||
# How to make a change
|
||||
|
||||
## Process
|
||||
|
||||
The typical process is to base your work on the `master` branch. The `master` branch must always contain a stable
|
||||
version of the project. It is possible to make changes by basing your work on other branches but the source
|
||||
of truth is `master`. If you want to synchronize with other people on other branches, feel free to.
|
||||
|
||||
The process is:
|
||||
|
||||
1. (optional) Open an issue and discuss what you want to do. This is optional but highly recommended. If you
|
||||
don’t open an issue first and work on something that is not in the scope of the project, or already being
|
||||
made by someone else, you’ll be working for nothing. Also, keep in mind that if your change doesn’t refer to an
|
||||
existing issue, I will be wondering what is the context of your change. So prepare to be asked about the motivation
|
||||
and need behind your changes — it’s greatly appreciated if the commit messages, code and PR’s content already
|
||||
contains this information so that people don’t have to ask.
|
||||
2. Fork the project.
|
||||
3. Create a branch starting from `master` – or the branch you need to work on. Even though this is not really enforced,
|
||||
you’re advised to name your branch according to the _Git Flow_ naming convention:
|
||||
- `fix/your-bug-here`: if you’re fixing a bug, name your branch.
|
||||
- `feature/new-feature-here`: if you’re adding some work.
|
||||
- Free for anything else.
|
||||
- The special `release/*` branch is used to either back-port changes from newer versions to previous
|
||||
versions, or to release new versions by updating files, changelogs, etc. Normally, contributors should
|
||||
never have to worry about this kind of brach as their creations is often triggered when wanting to make a release.
|
||||
4. Make some commits!
|
||||
5. Once you’re ready, open a Pull Request (PR) to merge your work on the target branch. For instance, open a PR for
|
||||
`master <- feature/something-new`.
|
||||
6. (optional) Ask someone to review your code in the UI. Normally, I’m pretty reactive to notifications but it never
|
||||
hurts to ask for a review.
|
||||
7. Discussion and peer-review.
|
||||
8. Once the CI is all green, someone (likely me [@phaazon]) will merge your code and close your PR.
|
||||
9. Feel free to delete your branch.
|
||||
|
||||
# Conventions
|
||||
|
||||
## Coding
|
||||
|
||||
N/A
|
||||
|
||||
## Git
|
||||
|
||||
### Git message
|
||||
|
||||
Please format your git messages like so:
|
||||
|
||||
> Starting with an uppercase letter, ending with a dot. #343
|
||||
>
|
||||
> The #343 after the dot is appreciated to link to issues. Feel free to add, like this message, more context
|
||||
> and/or precision to your git message. You don’t have to put it in the first line of the commit message,
|
||||
> but if you are fixing a bug or implementing a feature thas has an issue linked, please reference it, so
|
||||
> that it is easier to generate changelogs when reading the git log.
|
||||
|
||||
**I’m very strict on git messages as I use them to write `CHANGELOG.md` files. Don’t be surprised if I ask you
|
||||
to edit a commit message. :)**
|
||||
|
||||
### Commit atomicity
|
||||
|
||||
Your commits should be as atomic as possible. That means that if you make a change that touches two different
|
||||
concepts / has different scopes, most of the time, you want two commits – for instance one commit for the backend code
|
||||
and one commit for the interface code. There are exceptions, so this is not an absolute rule, but take some time
|
||||
thinking about whether you should split your commits or not. Commits which add a feature / fix a bug _and_ add tests at
|
||||
the same time are fine.
|
||||
|
||||
However, here’s a non-comprehensive list of commits considered bad and that will be refused:
|
||||
|
||||
- **Formatting, refactoring, cleaning, linting code in a PR that is not strictly about formatting**. If you open a PR to
|
||||
fix a bug, implement a feature, change configuration, add metadata to the CI, etc. — pretty much anything — but you
|
||||
also format some old code that has nothing to do with your PR, apply a linter’s suggestions (such as `clippy`), remove
|
||||
old code, etc., then I will refuse your commit(s) and ask you to edit your PR.
|
||||
- **Too atomic commits**. If two commits are logically connected to one another and are small, it’s likely that you want
|
||||
to merge them as a single commit — unless they work on too different parts of your code. This is a bit subjective
|
||||
topic, so I won’t be _too picky_ about it, but if I judge that you should split a commit into two or fixup two commits,
|
||||
please don’t take it too personal. :)
|
||||
|
||||
If you don’t know how to write your commits in an atomic maneer, think about how one would revert your commits if
|
||||
something bad happens with your changes — like a big breaking change we need to roll back from very quickly. If your
|
||||
commits are not atomic enough, rolling them back will also roll back code that has nothing to do with your changes.
|
||||
|
||||
### Hygiene
|
||||
|
||||
When working on a fix or a feature, it’s very likely that you will periodically need to update your branch
|
||||
with the `master` branch. **Do not use merge commits**, as your contributions will be refused if you have
|
||||
merge commits in them. The only case where merge commits are accepted is when you work with someone else
|
||||
and are required to merge another branch into your feature branch (and even then, it is even advised to
|
||||
simply rebase). If you want to synchronize your branch with `master`, please use:
|
||||
|
||||
```
|
||||
git switch <your_branch>
|
||||
git fetch origin --prune
|
||||
git rebase origin/master
|
||||
```
|
||||
|
||||
# Release process
|
||||
|
||||
## Overall process
|
||||
|
||||
Releases occur at arbitrary rates. If something is considered urgent, it is most of the time released immediately
|
||||
after being merged and tested. Sometimes, several issues are being fixed at the same time (spanning on a few
|
||||
days at max). Those will be gathered inside a single update.
|
||||
|
||||
Feature requests might be delayed a bit to be packed together as well but eventually get released, even if
|
||||
they’re small. Getting your PR merged means it will be released _soon_, but depending on the urgency of your changes,
|
||||
it might take a few minutes to a few days.
|
||||
|
||||
## Changelogs update
|
||||
|
||||
`CHANGELOG.md` files must be updated **before any release**. Especially, they must contain:
|
||||
|
||||
- The version of the release.
|
||||
- The date of the release.
|
||||
- How to migrate from a minor to the next major.
|
||||
- Everything that a release has introduced, such as major, minor and patch changes.
|
||||
|
||||
Because I don’t ask people to maintain changelogs, I have a high esteem of people knowing how to use Git and create
|
||||
correct commits. Be advised that I will refuse any commit that prevents me from writing the changelog correctly.
|
||||
|
||||
## Git tag
|
||||
|
||||
Once a new release occurs, a Git tag is created. Git tags are formatted regarding the project they refer to, if several
|
||||
projects are present in the repository. If only one project is present, tags will refer to this project by the same
|
||||
naming scheme anyway:
|
||||
|
||||
> <project-name>-X.Y.Z
|
||||
|
||||
Where `X` is the _major version_, `Y` is the _minor version_ and `Z` is the _patch version_. For instance
|
||||
`project-0.37.1` is a valid Git tag, so is `project-derive-0.5.3`.
|
||||
|
||||
A special kind of tag is also possible:
|
||||
|
||||
> <project-name>-X.Y.Z-rc.W
|
||||
|
||||
Where `W` is a number starting from `1` and incrementing. This format is for _release candidates_ and occurs
|
||||
when a new version (most of the time a major one) is to be released but more feedback is required.
|
||||
|
||||
# Support and donation
|
||||
|
||||
This project is a _free and open-source_ project. It has no financial motivation nor support. I
|
||||
([@phaazon]) would like to make it very clear that:
|
||||
|
||||
- Sponsorship is not available. You cannot pay me to make me do things for you. That includes issues reports,
|
||||
features requests and such.
|
||||
- If you still want to donate because you like the project and think I should be rewarded, you are free to
|
||||
give whatever you want.
|
||||
- However, keep in mind that donating doesn’t unlock any privilege people who don’t donate wouldn’t already
|
||||
have. This is very important as it would bias priorities. Donations must remain anonymous.
|
||||
- For this reason, no _sponsor badge_ will be shown, as it would distinguish people who donate from those
|
||||
who don’t. This is a _free and open-source_ project, everybody is welcome to contribute, with or without
|
||||
money.
|
||||
|
||||
[@phaazon]: https://github.com/phaazon
|
30
bundle/hop.nvim/LICENSE
Normal file
30
bundle/hop.nvim/LICENSE
Normal file
@ -0,0 +1,30 @@
|
||||
Copyright (c) 2021, Dimitri Sabadie <dimitri.sabadie@gmail.com>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Dimitri Sabadie <dimitri.sabadie@gmail.com> nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
287
bundle/hop.nvim/README.md
Normal file
287
bundle/hop.nvim/README.md
Normal file
@ -0,0 +1,287 @@
|
||||
__
|
||||
/ /_ ____ ____
|
||||
/ __ \/ __ \/ __ \
|
||||
/ / / / /_/ / /_/ /
|
||||
/_/ /_/\____/ .___/
|
||||
/_/
|
||||
· Neovim motions on speed! ·
|
||||
|
||||
[![](https://img.shields.io/badge/matrix-join%20the%20speed!-blueviolet)](https://matrix.to/#/#hop.nvim:matrix.org)
|
||||
|
||||
**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
|
||||
your buffer with hints, short string sequences for which each character
|
||||
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.
|
||||
|
||||
<!-- 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)
|
||||
* [Using vim-plug](#using-vim-plug)
|
||||
* [Using packer](#using-packer)
|
||||
* [Nightly users](#nightly-users)
|
||||
* [Usage](#usage)
|
||||
* [Keybindings](#keybindings)
|
||||
* [Configuration](#configuration)
|
||||
* [Extension](#extension)
|
||||
* [Chat](#chat)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
|
||||
# Motivation
|
||||
|
||||
**Hop** is a complete from-scratch rewrite of [EasyMotion], a famous plugin to
|
||||
enhance the native motions of Vim. Even though [EasyMotion] is usable in
|
||||
Neovim, it suffers from a few drawbacks making it not comfortable to use with
|
||||
Neovim version >0.5 – at least at the time of writing these lines:
|
||||
|
||||
- [EasyMotion] uses an old trick to annotate jump targets by saving the
|
||||
contents of the buffer, replacing it with the highlighted annotations and
|
||||
then restoring the initial buffer after jump. This trick is dangerous as it
|
||||
will change the contents of your buffer. A UI plugin should never do anything
|
||||
to existing buffers’ contents.
|
||||
- Because the contents of buffers will temporarily change, other parts of the
|
||||
editor and/or plugins relying on buffer change events will react and will go
|
||||
mad. An example is the internal LSP client implementation of Neovim >0.5 or
|
||||
its treesitter native implementation. For LSP, it means that the connected
|
||||
LSP server will receive a buffer with the jump target annotations… not
|
||||
ideal.
|
||||
|
||||
**Hop** is a modern take implementing this concept for the latest versions of
|
||||
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 it’s 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.
|
||||
|
||||
# Getting started
|
||||
|
||||
This section will guide you through the list of steps you must take to be able to get started with **Hop**.
|
||||
|
||||
This plugin was written against Neovim 0.5, which is currently a nightly version. This plugin will not work:
|
||||
|
||||
- With a version of Neovim before 0.5.
|
||||
- On Vim. **No support for Vim is planned.**
|
||||
|
||||
## Installation
|
||||
|
||||
Whatever solution / package manager you are using, you need to ensure that the `setup` Lua function is called at some
|
||||
point, otherwise the plugin will not work. If your package manager doesn’t support automatic calling of this function,
|
||||
you can call it manually after your plugin is installed:
|
||||
|
||||
```lua
|
||||
require'hop'.setup()
|
||||
```
|
||||
|
||||
To get a default experience. Feel free to customize later the `setup` invocation (`:h hop.setup`). If you do, then you
|
||||
will probably want to ensure the configuration is okay by running `:checkhealth`. Various checks will be performed by
|
||||
Hop to ensure everything is all good.
|
||||
|
||||
### Important note about versioning
|
||||
|
||||
This plugin implements [SemVer] via git branches and tags. Versions are prefixed with a `v`, and only patch versions
|
||||
are git tags. Major and minor versions are git branches. You are **very strongly advised** to use a major version
|
||||
dependency to be sure your config will not break when Hop gets updated.
|
||||
|
||||
### Using vim-plug
|
||||
|
||||
```vim
|
||||
Plug 'phaazon/hop.nvim'
|
||||
```
|
||||
|
||||
### Using packer
|
||||
|
||||
```lua
|
||||
use {
|
||||
'phaazon/hop.nvim',
|
||||
branch = 'v1', -- optional but strongly recommended
|
||||
config = function()
|
||||
-- you can configure Hop the way you like here; see :h hop-config
|
||||
require'hop'.setup { keys = 'etovxqpdygfblzhckisuran' }
|
||||
end
|
||||
}
|
||||
```
|
||||
|
||||
### Nightly users
|
||||
|
||||
Hop supports nightly releases of Neovim. However, keep in mind that if you are on a nightly version, you must be **on
|
||||
the last one**. If you are not, then you are exposed to compatibility issues / breakage.
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
# Keybindings
|
||||
|
||||
Hop doesn’t set any keybindings; you will have to define them by yourself.
|
||||
|
||||
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>", {})
|
||||
```
|
||||
|
||||
# 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 it’s 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)!
|
||||
|
||||
[EasyMotion]: https://github.com/easymotion/vim-easymotion
|
||||
[packer]: https://github.com/wbthomason/packer.nvim
|
||||
[SemVer]: https://semver.org
|
887
bundle/hop.nvim/doc/hop.txt
Normal file
887
bundle/hop.nvim/doc/hop.txt
Normal file
@ -0,0 +1,887 @@
|
||||
*hop.txt* For Neovim version 0.5 Last change: 2021 Nov 02
|
||||
|
||||
__
|
||||
/ /_ ____ ____
|
||||
/ __ \/ __ \/ __ \
|
||||
/ / / / /_/ / /_/ /
|
||||
/_/ /_/\____/ .___/
|
||||
/_/
|
||||
· Neovim motions on speed! ·
|
||||
|
||||
==============================================================================
|
||||
CONTENTS *hop-contents*
|
||||
|
||||
Introduction ·············································· |hop-introduction|
|
||||
Requirements ·············································· |hop-requirements|
|
||||
Usage ···························································· |hop-usage|
|
||||
Commands ···················································· |hop-commands|
|
||||
Lua API ······················································ |hop-lua-api|
|
||||
Jump target API ······································ |hop-jump-target-api|
|
||||
Configuration ··················································· |hop-config|
|
||||
Extension ···················································· |hop-extension|
|
||||
Highlights ·················································· |hop-highlights|
|
||||
License ························································ |hop-license|
|
||||
|
||||
==============================================================================
|
||||
INTRODUCTION *hop* *hop-introduction*
|
||||
|
||||
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 your
|
||||
buffer with |hints|, short string sequences for which each character 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.
|
||||
|
||||
Hop is a complete from-scratch rewrite of EasyMotion, a famous plugin to
|
||||
enhance the native motions of Vim. Even though EasyMotion is usable in
|
||||
Neovim, it suffers from a few drawbacks making it not comfortable to use with
|
||||
Neovim version >0.5 – at least at the time of writing these lines:
|
||||
|
||||
- EasyMotion uses an old trick to annotate jump targets by saving the
|
||||
contents of the buffer, replacing it with the highlighted annotations and
|
||||
then restoring the initial buffer after jump. This trick is dangerous as it
|
||||
will change the contents of your buffer. A UI plugin should never do anything
|
||||
to existing buffers’ contents.
|
||||
- Because the contents of buffers will temporarily change, other parts of the
|
||||
editor and/or plugins relying on buffer change events will react and will go
|
||||
mad. An example is the internal LSP client implementation of Neovim >0.5 or
|
||||
its treesitter native implementation. For LSP, it means that the connected
|
||||
LSP server will receive a buffer with the jump target annotations… not
|
||||
ideal.
|
||||
|
||||
==============================================================================
|
||||
REQUIREMENTS *hop-requirements*
|
||||
|
||||
Hop works only with Neovim and was written with Neovim-0.5, so it is highly
|
||||
recommended to use Neovim version 0.5+.
|
||||
|
||||
Especially, hop uses |api-extended-marks|, which are not available before
|
||||
Neovim-0.5.
|
||||
|
||||
==============================================================================
|
||||
USAGE *hop-usage*
|
||||
|
||||
Before doing anything else, you have to setup the plugin. If you are not using
|
||||
a package manager or environment doing that automatically for you, you need to
|
||||
call the |hop.setup| function to correctly initialize the plugin.
|
||||
|
||||
For a minimal setup:
|
||||
|
||||
For people using init.lua~
|
||||
In your `init.lua`, add:
|
||||
>
|
||||
require'hop'.setup()
|
||||
<
|
||||
For people using init.vim~
|
||||
In your `init.vim`, add:
|
||||
>
|
||||
lua << EOF
|
||||
require'hop'.setup()
|
||||
EOF
|
||||
<
|
||||
You can pass an optional argument to `setup(opts)` in order to pass {opts}.
|
||||
Have a look at |hop.setup| for further details.
|
||||
|
||||
*hop-health*
|
||||
Healthcheck~
|
||||
|
||||
Hop has support for |:checkhealth|. If you find yourself in a situation where
|
||||
something looks odd or incorrect, do not forget to run this command before
|
||||
opening an issue.
|
||||
|
||||
*hop-commands*
|
||||
Commands~
|
||||
|
||||
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 commands work, have a look at |hop.setup|. Also, something
|
||||
pretty important to know is that those are Vim commands. Hop tries to expose
|
||||
as many features as possible via the Vim commands but ultimately, you will
|
||||
have access to more features by using the Lua API directly. Have a look at
|
||||
|hop-lua-api| for more documentation.
|
||||
|
||||
Some of the commands have a suffix, such as `BC`, `AC` and `MW`. Those are
|
||||
variations of the commands without the suffix, applying to the visible part of
|
||||
the buffer before and after the cursor, and multiple windows, respectively.
|
||||
Another kind of suffix (that can be mixed with `BC` and `AC`) is `CurrentLine`. This
|
||||
creates a variant of the command that will only run for the current line.
|
||||
|
||||
`:HopWord` *:HopWord*
|
||||
`:HopWordBC` *:HopWordBC*
|
||||
`:HopWordAC` *:HopWordAC*
|
||||
`:HopWordCurrentLine` *:HopWordCurrentLine*
|
||||
`:HopWordCurrentLineBC` *:HopWordCurrentLineBC*
|
||||
`:HopWordCurrentLineAC` *:HopWordCurrentLineAC*
|
||||
`:HopWordMW` *:HopWordMW*
|
||||
Annotate all |word|s 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 bring your cursor at that
|
||||
position.
|
||||
|
||||
This is akin to calling the |hop.hint_words| Lua function.
|
||||
|
||||
`:HopPattern` *:HopPattern*
|
||||
`:HopPatternBC` *:HopPatternBC*
|
||||
`:HopPatternAC` *:HopPatternAC*
|
||||
`:HopPatternCurrentLine` *:HopPatternCurrentLine*
|
||||
`:HopPatternCurrentLineBC` *:HopPatternCurrentLineBC*
|
||||
`:HopPatternCurrentLineAC` *:HopPatternCurrentLineAC*
|
||||
`:HopPatternMW` *:HopPatternMW*
|
||||
Ask the user for a pattern and hint the document with it.
|
||||
|
||||
This is akin to calling the |hop.hint_patterns| Lua function
|
||||
with no explicit pattern.
|
||||
|
||||
`:HopChar1` *:HopChar1*
|
||||
`:HopChar1BC` *:HopChar1BC*
|
||||
`:HopChar1AC` *:HopChar1AC*
|
||||
`:HopChar1CurrentLine` *:HopChar1CurrentLine*
|
||||
`:HopChar1CurrentLineBC` *:HopChar1CurrentLineBC*
|
||||
`:HopChar1CurrentLineAC` *:HopChar1CurrentLineAC*
|
||||
`:HopChar1MW` *:HopChar1MW*
|
||||
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*
|
||||
`:HopChar2CurrentLine` *:HopChar2CurrentLine*
|
||||
`:HopChar2CurrentLineBC` *:HopChar2CurrentLineBC*
|
||||
`:HopChar2CurrentLineAC` *:HopChar2CurrentLineAC*
|
||||
`:HopChar2MW` *:HopChar2MW*
|
||||
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*
|
||||
`:HopLineCurrentLine` *:HopLineCurrentLine*
|
||||
`:HopLineCurrentLineBC` *:HopLineCurrentLineBC*
|
||||
`:HopLineCurrentLineAC` *:HopLineCurrentLineAC*
|
||||
`:HopLineMW` *:HopLineMW*
|
||||
Jump to the beginning of the line of your choice inside your buffer.
|
||||
|
||||
This is akin to calling the |hop.hint_lines| Lua function.
|
||||
|
||||
`:HopLineStart` *:HopLineStart*
|
||||
`:HopLineStartBC` *:HopLineStartBC*
|
||||
`:HopLineStartAC` *:HopLineStartAC*
|
||||
`:HopLineStartCurrentLine` *:HopLineStartCurrentLine*
|
||||
`:HopLineStartCurrentLineBC` *:HopLineStartCurrentLineBC*
|
||||
`:HopLineStartCurrentLineAC` *:HopLineStartCurrentLineAC*
|
||||
`:HopLineStartMW` *:HopLineStartMW*
|
||||
Like `HopLine` but skips leading whitespace on every line. Blank lines are
|
||||
skipped over.
|
||||
|
||||
This is akin to calling the |hop.hint_lines_skip_whitespace| Lua function.
|
||||
|
||||
`:HopAnywhere` *:HopAnywhere*
|
||||
`:HopAnywhereBC` *:HopAnywhereBC*
|
||||
`:HopAnywhereAC` *:HopAnywhereAC*
|
||||
`:HopAnywhereCurrentLine` *:HopAnywhereCurrentLine*
|
||||
`:HopAnywhereCurrentLineBC` *:HopAnywhereCurrentLineBC*
|
||||
`:HopAnywhereCurrentLineAC` *:HopAnywhereCurrentLineAC*
|
||||
`:HopAnywhereMW` *:HopAnywhereMW*
|
||||
Annotate anywhere with key sequences.
|
||||
|
||||
This is akin to calling the |hop.hint_anywhere| Lua function.
|
||||
|
||||
*hop-lua-api*
|
||||
Lua API~
|
||||
|
||||
The Lua API comprises several modules. Even though those modules might have
|
||||
more public functions than described here, you are only supposed to use the
|
||||
functions in this help page. Using one that is not listed here is considered
|
||||
unstable.
|
||||
|
||||
`hop` Entry point and main interface. If you just want to use
|
||||
Hop from within Lua via keybindings, you shouldn’t need to
|
||||
read any other modules.
|
||||
`hop.defaults` Default options.
|
||||
`hop.hint` Various functions to create, update and reduce hints.
|
||||
`hop.highlight` Highlight functions (creation / autocommands / etc.).
|
||||
`hop.jump_target` Core module used to create jump targets.
|
||||
`hop.perm` Permutation functions. Permutations are used as labels for
|
||||
the hints.
|
||||
|
||||
Main API~
|
||||
|
||||
Most of the functions and values you need to know about are in `hop`.
|
||||
|
||||
`hop.setup(`{opts}`)` *hop.setup*
|
||||
Setup the library with options.
|
||||
|
||||
This function will setup the Lua API and commands in a way that respects
|
||||
the options you pass. It is mandatory to call that function at some time
|
||||
if you want to be able to use Hop.
|
||||
|
||||
Note:
|
||||
Some plugins will automatically call the `setup` public function of a
|
||||
plugin if declared, which is the case with Hop. With such plugins, you
|
||||
shouldn’t have to care too much about `setup` but focus more on the
|
||||
{opts} you can pass it.
|
||||
|
||||
Arguments:~
|
||||
{opts} List of options. See the |hop-config| section.
|
||||
|
||||
`hop.hint_with(`{jump_target_gtr}`,` {opts}`)` *hop.hint_with*
|
||||
Main entry-point of Hop, this function expects a jump target generator and
|
||||
will call it with {opts} to get a list of jump targets. This function will
|
||||
take care of reducing the hints for you automatically and will perform the
|
||||
actual jumps.
|
||||
|
||||
If you would like to use a more general version to implement different
|
||||
kind of actions instead of jumping, have a look at
|
||||
|hop.hint_with_callback|.
|
||||
|
||||
Arguments:~
|
||||
{jump_target_gtr} Jump target generator. See |hop-jump-target-api| for
|
||||
further information.
|
||||
{opts} User options.
|
||||
|
||||
`hop.hint_with_callback(` *hop.hint_with_callback*
|
||||
{jump_target_gtr}`,`
|
||||
{opts}`,`
|
||||
{callback}
|
||||
`)`
|
||||
Main entry-point of Hop, this function expects a jump target generator and
|
||||
will call it with {opts} to get a list of jump targets. This function will
|
||||
take care of reducing the hints for you automatically. Once a jump target
|
||||
is reduced completely, this function will call the {callback} with the
|
||||
selected jump target.
|
||||
|
||||
Arguments:~
|
||||
{jump_target_gtr} Jump target generator. See |hop-jump-target-api| for
|
||||
further information.
|
||||
{opts} User options.
|
||||
|
||||
|
||||
`hop.hint_words(`{opts}`)` *hop.hint_words*
|
||||
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
|
||||
bring your cursor at that position. See |hop-config| for a complete list
|
||||
of the options you can pass as arguments.
|
||||
|
||||
Arguments:~
|
||||
{opts} Hop options.
|
||||
|
||||
`hop.hint_patterns(`{opts}`,` {pattern}`)` *hop.hint_patterns*
|
||||
Annotate all matched patterns in the current window with key sequences.
|
||||
|
||||
Arguments:~
|
||||
{opts} Hop options.
|
||||
{pattern} (optional) The pattern to search for.
|
||||
If not set, the user is prompted for the pattern to search.
|
||||
|
||||
`hop.hint_char1(`{opts}`)` *hop.hint_char1*
|
||||
Let the user type a key and immediately hint all of its occurrences.
|
||||
|
||||
Arguments:~
|
||||
{opts} Hop options.
|
||||
|
||||
`hop.hint_char2(`{opts}`)` *hop.hint_char2*
|
||||
Let the user type a bigram (two concatenated keys) and immediately hint
|
||||
all of its occurrences.
|
||||
|
||||
This function can behave like |hop.hint_char1| in some cases. See
|
||||
|hop-config-char2_fallback_key|.
|
||||
|
||||
Arguments:~
|
||||
{opts} Hop options.
|
||||
|
||||
`hop.hint_lines(`{opts}`)` *hop.hint_lines*
|
||||
Hint the beginning of each lines currently visible in the buffer view and
|
||||
allow to jump to them.
|
||||
|
||||
This works with empty lines as well.
|
||||
|
||||
Arguments:~
|
||||
{opts} Hop options.
|
||||
|
||||
`hop.hint_lines_skip_whitespace(`{opts}`)` *hop.hint_lines_skip_whitespace*
|
||||
Hint the first non-whitespace character of each lines currently visible in
|
||||
the buffer view and allow to jump to them.
|
||||
|
||||
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.
|
||||
|
||||
Arguments:~
|
||||
{opts} Hop options.
|
||||
|
||||
Hint API~
|
||||
|
||||
The hint API provide the `HintDirection` and `HintPosition`
|
||||
|
||||
`hop.hint.HintDirection` *hop.hint.HintDirection*
|
||||
Enumeration for hinting direction.
|
||||
|
||||
Use this table as a value for |hop-config-direction|. Setting it to {nil}
|
||||
makes the command / function act on the whole visible part of the buffer.
|
||||
|
||||
Enumeration variants:~
|
||||
{BEFORE_CURSOR} Create and apply hints before the cursor.
|
||||
{AFTER_CURSOR} Create and apply hints after the cursor.
|
||||
|
||||
`hop.hint.HintPosition` *hop.hint.HintPosition*
|
||||
Enumeration for hinting position in match.
|
||||
|
||||
Use this table as a value for |hop-config-hint_posititon|.
|
||||
|
||||
Enumeration variants:~
|
||||
{BEGIN} Create and apply hints at the beginning of the match.
|
||||
{MIDDLE} Create and apply hints at the middle of the match.
|
||||
{END} Create and apply hints at the end of the match.
|
||||
|
||||
*hop-jump-target-api*
|
||||
Jump target API~
|
||||
|
||||
The jump target API is probably the most core API of all. It provides some
|
||||
helper functions to help you extend Hop by providing your own jump targets.
|
||||
Most importantly, you will want to read this documentation section to know
|
||||
exactly which kind of format Hop expects for the jump targets when
|
||||
implementing your own.
|
||||
|
||||
Jump targets are locations in buffers where users might jump to. They are
|
||||
wrapped in a table and provide the required information so that Hop can
|
||||
associate labels and display the hints. Such a table must have the following
|
||||
form:
|
||||
>
|
||||
{
|
||||
jump_targets = {},
|
||||
indirect_jump_targets = {},
|
||||
}
|
||||
>
|
||||
The `jump_targets` field is a list-table of jump targets. A single jump target
|
||||
is simply a location in a given window. Actually, the location will be set on
|
||||
the buffer that is being displayed by that window. So you can picture a jump
|
||||
target as a triple (line, column, window).
|
||||
>
|
||||
{
|
||||
line = 0,
|
||||
column = 0,
|
||||
window = 0,
|
||||
}
|
||||
<
|
||||
`indirect_jump_targets` is an optional yet highly recommended table. They
|
||||
provide an indirect access to `jump_targets` to re-order them. They are for
|
||||
instance used to be able to distribute hints according to their distance to
|
||||
the current location of the cursor. Not providing that table (`nil`) will
|
||||
result in the jump targets being considered fully ordered and will be assigned
|
||||
sequences based on their index in `jump_targets`.
|
||||
|
||||
Indirect jump targets are encoded as a flat list-table of pairs
|
||||
(index, score). This table allows to quickly score and sort jump targets.
|
||||
>
|
||||
{
|
||||
index = 0,
|
||||
score = 0,
|
||||
}
|
||||
<
|
||||
The `index` field gives the index in the `jump_targets` list. The `score` is any
|
||||
number. The rule is that the lower the score is, the less prioritized the
|
||||
jump target will be.
|
||||
|
||||
So for instance, for two jump targets, a jump target generator must return
|
||||
such a table:
|
||||
>
|
||||
{
|
||||
jump_targets = {
|
||||
{ line = 1, column = 14, window = 0 },
|
||||
{ line = 2, column = 1, window = 0 },
|
||||
},
|
||||
|
||||
indirect_jump_targets = {
|
||||
{ index = 0, score = 14 },
|
||||
{ index = 1, score = 7 },
|
||||
},
|
||||
}
|
||||
<
|
||||
|
||||
If you don’t need to change the score, or if the scores are always ascending
|
||||
with the indices ascending, you can completely omit the
|
||||
`indirect_jump_targets` table:
|
||||
>
|
||||
{
|
||||
jump_targets = {
|
||||
{ line = 1, column = 14, window = 0 },
|
||||
{ line = 2, column = 1, window = 0 },
|
||||
},
|
||||
}
|
||||
<
|
||||
This module provides several functions, named `jump_targets_*`, which are
|
||||
called jump target generators. Such functions are to be passed to
|
||||
|hop.hint_with| or |hop.hint_with_callback|. Most of the Vim commands are already
|
||||
doing that for you.
|
||||
|
||||
`hop.jump_target.jump_targets_by_scanning_line(` *hop.jump_target.jump_targets_by_scanning_line*
|
||||
{regex}
|
||||
`)`
|
||||
Jump target generator that scans lines of the currently visible buffer and
|
||||
create the jump targets by using the input {regex}.
|
||||
|
||||
Arguments:~
|
||||
{regex} Regex-like table to apply to each line. See the various
|
||||
`hop.jump_target.regex*` functions below for a list of available
|
||||
options.
|
||||
|
||||
If you want to create your own regex-like table, you need to
|
||||
provide a table with two fields: `oneshot`, a boolean value,
|
||||
that is to be set to `true` if the regex should be applied
|
||||
only once to each line, and `match`, which is the actual
|
||||
matcher that returns the beginning / end
|
||||
(inclusive / exclusive) byte indices of the match. You are
|
||||
advised to use |vim.regex|.
|
||||
|
||||
`hop.jump_target.jump_targets_for_current_line(` *hop.jump_target.jump_targets_for_current_line*
|
||||
{regex}
|
||||
`)`
|
||||
Jump target generator that applies {regex} only to the current line of the
|
||||
currently active window.
|
||||
|
||||
Arguments:~
|
||||
{regex} Regex-like table to apply to each line. See the various
|
||||
`hop.jump_target.regex*` functions below for a list of available
|
||||
options.
|
||||
|
||||
If you want to create your own regex-like table, you need to
|
||||
provide a table with two fields: `oneshot`, a boolean value,
|
||||
that is to be set to `true` if the regex should be applied to
|
||||
each line only once, and `match`, which is the actual matcher
|
||||
that returns the beginning / end (inclusive / exclusive) byte
|
||||
indices of the match. You are advised to use |vim.regex|.
|
||||
|
||||
|
||||
`hop.jump_target.sort_indirect_jump_targets(` *hop.jump_target.sort_indirect_jump_targets*
|
||||
{indirect_jump_targets}`,`
|
||||
{opts}
|
||||
`)`
|
||||
Sort {indirect_jump_targets} according to their scores.
|
||||
|
||||
Arguments:~
|
||||
{indirect_jump_targets} Indirect jump targets to sort.
|
||||
{opts} User options.
|
||||
|
||||
`hop.jump_target.regex_by_searching(` *hop.jump_target.regex_by_searching*
|
||||
{pat}`,`
|
||||
{plain_search}
|
||||
`)`
|
||||
Buffer-line based |pattern| hint mode. This mode will highlight 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.jump_target.regex_by_case_searching(` *hop.jump_target.regex_by_case_searching*
|
||||
{pat}`,`
|
||||
{plain_search}`,`
|
||||
{opts}
|
||||
`)`
|
||||
Similar to |hop.jump_target.regex_by_searching|, but respects the user case
|
||||
sensitivity set by 'smartcase' and |hop-config-case_insensitive|.
|
||||
|
||||
Arguments:~
|
||||
{pat} Pattern to search.
|
||||
{plain_search} Should the pattern by plain-text.
|
||||
{opts} User options.
|
||||
|
||||
`hop.jump_target.regex_by_word_start()` *hop.jump_target.regex_by_word_start*
|
||||
Buffer-line based |word| hint mode. This mode will highlight the beginnings
|
||||
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*
|
||||
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*
|
||||
Highlight the non-whitespace character of each line in the current window.
|
||||
|
||||
`hop.jump_target.regex_by_anywhere()` *hop.jump_target.regex_by_anywhere*
|
||||
Highlight the anywhere in the current window.
|
||||
|
||||
`hop.jump_target.manh_dist(` *hop.jump_target.manh_dist*
|
||||
{a}`,`
|
||||
{b}`,`
|
||||
{x_bias}
|
||||
`)`
|
||||
Manhattan distance between two buffer positions. Both {a} and {b} must be
|
||||
tables containing two values: the first one for the line and the second one
|
||||
for the column.
|
||||
|
||||
{x_bias} is to be used to skew the Manhattan distance in terms of lines.
|
||||
|
||||
Arguments:~
|
||||
{a} First position.
|
||||
{b} Second position.
|
||||
{x_bias} Optional bias applied to the line distance. Defaults to 10.
|
||||
|
||||
Highlight API~
|
||||
|
||||
The highlight API gives two functions to manipulate the highlights Hop uses:
|
||||
|
||||
`hop.highlight.insert_highlights()` *hop.highlight.insert_highlights*
|
||||
Manually insert the highlights by calling the `highlight default` command.
|
||||
See |hop-highlights| for further details about the list of highlights
|
||||
currently available.
|
||||
|
||||
`hop.highlight.create_autocmd` *hop.highlight.create_autocmd*
|
||||
Register autocommands for the `ColorScheme` event, calling
|
||||
|hop.highlight.insert_highlights|. This is called when you require Hop and
|
||||
you shouldn’t need to call this function by yourself.
|
||||
|
||||
Permutation API~
|
||||
|
||||
The permutation API is the core part of the algorithm used in Hop.
|
||||
Permutations in Hop are made out of a source sequence of characters, called
|
||||
a key set and represented with {keys}. A good choice of key set is important
|
||||
to yield short and concise permutations, allowing to jump to targets with
|
||||
fewer keystrokes.
|
||||
|
||||
`hop.perm.permutations(`{keys}`,` {n}`,` {opts}`)` *hop.perm.permutations*
|
||||
Get the first {n} permutations out of {keys}.
|
||||
|
||||
Arguments:~
|
||||
{keys} Input key set to use to create permutations.
|
||||
{n} Number of permutations to generate.
|
||||
{opts} Hop options.
|
||||
|
||||
Return:~
|
||||
The {n} first permutations for the given {keys} and
|
||||
|hop-config-perm_method| set in {opts}.
|
||||
|
||||
==============================================================================
|
||||
CONFIGURATION *hop-config*
|
||||
|
||||
The configuration can be provided in two different ways:
|
||||
|
||||
- Either by explicitly passing an {opts} table to the various Lua
|
||||
functions.
|
||||
- Or by passing an {opts} table to the Lua |hop.setup| function.
|
||||
|
||||
Not providing any of the two above will use the default values, as described
|
||||
below.
|
||||
|
||||
`keys` *hop-config-keys*
|
||||
A string representing all the keys that can be part of a permutation.
|
||||
Every character (key) used in the string will be used as part of a
|
||||
permutation. The shortest permutation is a permutation of a single
|
||||
character, and, depending on the content of your buffer, you might end up
|
||||
with 3-character (or more) permutations in worst situations.
|
||||
|
||||
However, it is important to notice that if you decide to provide `keys`,
|
||||
you have to ensure to use enough characters in the string, otherwise you
|
||||
might get very long sequences and a not so pleasant experience.
|
||||
|
||||
Note:
|
||||
This is only for the old permutation algorithm only, which is to get
|
||||
deprecated soon.
|
||||
|
||||
Another important aspect is that the order of the characters you put
|
||||
in your `keys` is important. Depending on the value you use in
|
||||
|hop-config-term_seq_bias|, the keys will be split in two and the first
|
||||
part of it will be used for terminal keys and the second part for
|
||||
sequence keys.
|
||||
|
||||
Defaults:~
|
||||
`keys = 'asdghklqwertyuiopzxcvbnmfj'`
|
||||
|
||||
`quit_key` *hop-config-quit_key*
|
||||
A string representing a key that will quit Hop mode without also feeding
|
||||
that key into Neovim to be treated as a normal key press.
|
||||
|
||||
It is possible to quit hopping by pressing any key that is not present in
|
||||
|hop-config-keys|; however, when you do this, the key normal function is
|
||||
also performed. For example if, hopping in |visual-mode|, pressing <Esc>
|
||||
will quit hopping and also exit |visual-mode|.
|
||||
|
||||
If the user presses `quit_key`, Hop will be quit without the key normal
|
||||
function being performed. For example if hopping in |visual-mode| with
|
||||
`quit_key` set to '<Esc>', pressing <Esc> will quit hopping without
|
||||
quitting |visual-mode|.
|
||||
|
||||
If you don't want to use a `quit_key`, set `quit_key` to an empty string.
|
||||
|
||||
Note:~
|
||||
`quit_key` should only contain a single key or be an empty string.
|
||||
|
||||
Note:~
|
||||
`quit_key` should not contain a key that is also present in
|
||||
|hop-config-keys|.
|
||||
|
||||
Defaults:~
|
||||
`quit_key = '<Esc>'`
|
||||
|
||||
`perm_method` *hop-config-perm_method*
|
||||
Permutation method to use.
|
||||
|
||||
Permutation methods allow to change the way permutations (i.e. hints
|
||||
sequence labels) are generated internally. There are currently two
|
||||
possible options:
|
||||
|
||||
Possible algorithms~
|
||||
`TermSeqBias`
|
||||
This algorithm splits your input key set (|hop-config-keys|) into
|
||||
two separate sets, a terminal key set and a sequence key set.
|
||||
Terminal keys will always be at the end of a sequence and then
|
||||
typing them will always jump somewhere in your buffer, while
|
||||
sequence keys will always be part of a prefix sequence before a
|
||||
terminal key.
|
||||
|
||||
Additionally to |hop-config-keys|, this algorithm uses the
|
||||
|hop-config-term_seq_bias| option to determine how to split
|
||||
|hop-config-keys|. A good default value is `0.5` or `3 / 4` but
|
||||
feel free to experiment with values between (exclusive) `0` and
|
||||
`1`.
|
||||
|
||||
Note:
|
||||
This algorithm will be deprecated very soon. You are strongly
|
||||
advised to switch to `TrieBacktrackFilling` if you haven’t
|
||||
yet.
|
||||
|
||||
`TrieBacktrackFilling`
|
||||
Permutation algorithm based on tries and backtrack filling.
|
||||
|
||||
This algorithm uses the full potential of |hop-config-keys| by
|
||||
using them all to saturate a trie, representing all the
|
||||
permutations. Once a layer is saturated, this algorithm will
|
||||
backtrack (from the end of the trie, deepest first) and create a
|
||||
new layer in the trie, ensuring that the first permutations will
|
||||
be shorter than the last ones.
|
||||
|
||||
Because of the last, deepest trie insertion mechanism and trie
|
||||
saturation, this algorithm yields a much better distribution
|
||||
accross your buffer, and you should get 1-sequences and
|
||||
2-sequences most of the time. Each dimension grows
|
||||
exponentially, so you get `keys_length²` 2-sequence keys,
|
||||
`keys_length³` 3-sequence keys, etc in the worst cases.
|
||||
|
||||
Default value~
|
||||
`perm_method = require'hop.perm'.TrieBacktrackFilling`
|
||||
|
||||
`reverse_distribution` *hop-config-reverse_distribution*
|
||||
The default behavior for key sequence distribution in your buffer is to
|
||||
concentrate shorter sequences near the cursor, grouping 1-character
|
||||
sequences around. As hints get further from the cursor, the dimension of
|
||||
the sequences will grow, making the furthest sequences the longest ones
|
||||
to type.
|
||||
|
||||
Set this option to `true` to reverse the density and concentrate the
|
||||
shortest sequences (1-character) around the furthest words and the longest
|
||||
sequences around the cursor.
|
||||
|
||||
Defaults:~
|
||||
`reverse_distribution = false`
|
||||
|
||||
`teasing` *hop-config-teasing*
|
||||
Boolean value stating whether Hop should tease you when you do something
|
||||
you are not supposed to.
|
||||
|
||||
If you find this setting annoying, feel free to turn it to `false`.
|
||||
|
||||
Defaults:~
|
||||
`teasing = true`
|
||||
|
||||
`jump_on_sole_occurrence` *hop-config-jump_on_sole_occurrence*
|
||||
Immediately jump without displaying hints if only one occurrence exists.
|
||||
|
||||
Defaults:~
|
||||
`jump_on_sole_occurrence = true`
|
||||
|
||||
`case_insensitive` *hop-config-case_insensitive*
|
||||
Use case-insensitive matching by default for commands requiring user
|
||||
input.
|
||||
|
||||
Defaults:~
|
||||
`case_insensitive = true`
|
||||
|
||||
`create_hl_autocmd` *hop-config-create_hl_autocmd*
|
||||
Create and set highlight autocommands to automatically apply highlights.
|
||||
You will want this if you use a theme that clears all highlights before
|
||||
applying its colorscheme.
|
||||
|
||||
Defaults:~
|
||||
`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:~
|
||||
`direction = nil`
|
||||
|
||||
`hint_position` *hop-config-hint_position*
|
||||
Position of hint in match. See |hop.hint.HintPosition| for further
|
||||
details.
|
||||
|
||||
Defaults:~
|
||||
`hint_position = require'hop.hint'.HintPosition.BEGIN`
|
||||
|
||||
`current_line_only` *hop-config-current_line_only*
|
||||
Apply Hop commands only to the current line.
|
||||
|
||||
Note:
|
||||
Trying to use this option along with |hop-config-multi_windows| is
|
||||
unsound.
|
||||
|
||||
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.
|
||||
|
||||
Defaults:~
|
||||
`uppercase_labels = false`
|
||||
|
||||
`char2_fallback_key` *hop-config-char2_fallback_key*
|
||||
Enable a special key that breaks |hop.hint_char2| if only one key is
|
||||
pressed, falling back to the same behavior as |hop.hint_char1|. It is
|
||||
recommended that, if you set this option, you not use a key that is in
|
||||
your |hop-config-keys| (because it would make those keys unreachable), and
|
||||
not a character that you might jump to. A good fallback key could be
|
||||
`<esc>` or `<cr>`, for instance.
|
||||
|
||||
Defaults:~
|
||||
`char2_fallback_key = nil`
|
||||
|
||||
`extensions` *hop-config-extensions*
|
||||
List-table of extensions to enable (names). As described in |hop-extension|,
|
||||
extensions for which the name in that list must have a `register(opts)`
|
||||
function in their public API for Hop to correctly initialized them.
|
||||
|
||||
Defaults:~
|
||||
`extensions = nil`
|
||||
|
||||
`multi_windows` *hop-config-multi_windows*
|
||||
Enable cross-windows support and hint all the currently visible windows.
|
||||
This behavior allows you to jump around any position in any buffer
|
||||
currently visible in a window. Although a powerful a feature, remember
|
||||
that enabling this will also generate many more sequence combinations, so
|
||||
you could get deeper sequences to type (most of the time it should be good
|
||||
if you have enough keys in |hop-config-keys|).
|
||||
|
||||
Defaults:~
|
||||
`multi_windows = false`
|
||||
|
||||
==============================================================================
|
||||
EXTENSION *hop-extension*
|
||||
|
||||
Hop supports extensions, that can provide ad hoc jump targets that are not
|
||||
part of the core of Hop. If you want to write a Hop extension, this section
|
||||
should provide all the required informations.
|
||||
|
||||
The first important part is to know how to generate jump targets. You will
|
||||
then first want to read the |hop-jump-target-api|, that describes this process
|
||||
in terms of Lua protocol.
|
||||
|
||||
The second part is that because you will depend on the Hop API, you need to be
|
||||
sure that Hop is completely setup before initiating your Hop extension plugin.
|
||||
In order to do so, your users will have to read the user guide of the package
|
||||
manager they are using to mark Hop as a dependency of your plugin when
|
||||
installing. You should ensure such instructions are easily accessible to them,
|
||||
for instance in your help pages and README.
|
||||
|
||||
Finally, Hop extension plugins do not get initialized via the regular `setup`
|
||||
function — well, it’s up to you to still provide that function, but keep in
|
||||
mind that Hop should not be assumed ready if you do so, so really keep setting
|
||||
up things unrelated to Hop in your `setup` function. In order to setup a Hop
|
||||
extension plugin, your plugin must provide the `register(opts)` function, that
|
||||
takes the same {opts} arguments as `setup(opts)`. This function will be called
|
||||
by Hop automatically.
|
||||
|
||||
Once a user has installed both Hop and the extension plugin, they can simply
|
||||
modify the configuration of Hop and pass the name of your plugin in the
|
||||
`extensions` user configuration. See |hop-config-extensions| for further
|
||||
details about that.
|
||||
|
||||
==============================================================================
|
||||
HIGHLIGHTS *hop-highlights*
|
||||
|
||||
Anywhere in the hint buffer that doesn’t contain a hint, the |hl-EndOfBuffer|
|
||||
highlight is used. For the rest:
|
||||
|
||||
`HopNextKey` *hop-hl-HopNextkey*
|
||||
Highlight used for the mono-sequence keys (i.e. sequence of 1).
|
||||
|
||||
`HopNextKey1` *hop-hl-HopNextKey1*
|
||||
Highlight used for the first key in a sequence.
|
||||
|
||||
`HopNextKey2` *hop-hl-HopNextKey2*
|
||||
Highlight used for the second and remaining keys in a sequence.
|
||||
|
||||
`HopUnmatched` *hop-hl-HopUnmatched*
|
||||
Highlight used for unmatched part of the buffer when running a Hop command
|
||||
/ Lua functions.
|
||||
|
||||
`HopCursor` *hop-hl-HopCursor*
|
||||
Highlight used for the fake cursor visible when running a Hop command /
|
||||
Lua functions.
|
||||
|
||||
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`.
|
||||
|
||||
==============================================================================
|
||||
LICENSE *hop-license*
|
||||
|
||||
Copyright (c) 2021-2022, Dimitri Sabadie <dimitri.sabadie@gmail.com>
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Dimitri Sabadie <dimitri.sabadie@gmail.com> nor the
|
||||
names of other contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
==============================================================================
|
||||
vim:tw=78:sw=4:ts=8:ft=help:norl:
|
@ -0,0 +1,32 @@
|
||||
local M = {}
|
||||
M.opts = {}
|
||||
|
||||
function M.hint_around_cursor(opts)
|
||||
-- the jump target generator; we are simply going to retreive the cursor position and hint around it as an example
|
||||
local jump_targets = function() -- opts ignored
|
||||
local cursor_pos = require'hop.window'.get_window_context().cursor_pos
|
||||
local line = cursor_pos[1] - 1
|
||||
local col = cursor_pos[2] + 1
|
||||
|
||||
local jump_targets = {}
|
||||
|
||||
-- left
|
||||
if col > 0 then
|
||||
jump_targets[#jump_targets + 1] = { line = line, column = col - 1, window = 0 }
|
||||
end
|
||||
|
||||
-- right
|
||||
jump_targets[#jump_targets + 1] = { line = line, column = col + 1, window = 0 }
|
||||
|
||||
return { jump_targets = jump_targets }
|
||||
end
|
||||
|
||||
require'hop'.hint_with(jump_targets, opts)
|
||||
end
|
||||
|
||||
function M.register(opts)
|
||||
vim.notify('registering the nice extension', 0)
|
||||
M.opts = opts
|
||||
end
|
||||
|
||||
return M
|
17
bundle/hop.nvim/lua/hop/defaults.lua
Normal file
17
bundle/hop.nvim/lua/hop/defaults.lua
Normal file
@ -0,0 +1,17 @@
|
||||
local M = {}
|
||||
|
||||
M.keys = 'asdghklqwertyuiopzxcvbnmfj'
|
||||
M.quit_key = '<Esc>'
|
||||
M.perm_method = require'hop.perm'.TrieBacktrackFilling
|
||||
M.reverse_distribution = false
|
||||
M.teasing = true
|
||||
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
|
||||
|
||||
return M
|
36
bundle/hop.nvim/lua/hop/health.lua
Normal file
36
bundle/hop.nvim/lua/hop/health.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local M = {}
|
||||
local hop = require'hop'
|
||||
|
||||
-- Initialization check.
|
||||
--
|
||||
-- This function will perform checks at initialization to ensure everything will work as expected.
|
||||
function M.check()
|
||||
local health = require'health'
|
||||
|
||||
health.report_start('Ensuring keys are unique')
|
||||
local existing_keys = {}
|
||||
local had_errors = false
|
||||
for i = 0, #hop.opts.keys do
|
||||
local key = hop.opts.keys:sub(i, i)
|
||||
|
||||
if existing_keys[key] then
|
||||
health.report_error(string.format('key %s appears more than once in opts.keys', key))
|
||||
had_errors = true
|
||||
else
|
||||
existing_keys[key] = true
|
||||
end
|
||||
end
|
||||
|
||||
if not had_errors then
|
||||
health.report_ok('Keys are unique')
|
||||
end
|
||||
|
||||
health.report_start('Checking for deprecated features')
|
||||
had_errors = false
|
||||
|
||||
if not had_errors then
|
||||
health.report_ok('All good')
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
31
bundle/hop.nvim/lua/hop/highlight.lua
Normal file
31
bundle/hop.nvim/lua/hop/highlight.lua
Normal file
@ -0,0 +1,31 @@
|
||||
-- This module contains everything for highlighting Hop.
|
||||
local M = {}
|
||||
|
||||
-- Insert the highlights that Hop uses.
|
||||
function M.insert_highlights()
|
||||
-- Highlight used for the mono-sequence keys (i.e. sequence of 1).
|
||||
vim.api.nvim_command('highlight default HopNextKey guifg=#ff007c gui=bold ctermfg=198 cterm=bold')
|
||||
|
||||
-- Highlight used for the first key in a sequence.
|
||||
vim.api.nvim_command('highlight default HopNextKey1 guifg=#00dfff gui=bold ctermfg=45 cterm=bold')
|
||||
|
||||
-- Highlight used for the second and remaining keys in a sequence.
|
||||
vim.api.nvim_command('highlight default HopNextKey2 guifg=#2b8db3 ctermfg=33')
|
||||
|
||||
-- Highlight used for the unmatched part of the buffer.
|
||||
-- ctermbg=bg is omitted because it errors if Normal does not have ctermbg set
|
||||
-- Luckily guibg=bg does not seem to error even if Normal does not have guibg set so it can be used
|
||||
vim.api.nvim_command('highlight default HopUnmatched guifg=#666666 guibg=bg guisp=#666666 ctermfg=242')
|
||||
|
||||
-- Highlight used for the fake cursor visible when hopping.
|
||||
vim.api.nvim_command('highlight default link HopCursor Cursor')
|
||||
end
|
||||
|
||||
function M.create_autocmd()
|
||||
vim.api.nvim_command('augroup HopInitHighlight')
|
||||
vim.api.nvim_command('autocmd!')
|
||||
vim.api.nvim_command("autocmd ColorScheme * lua require'hop.highlight'.insert_highlights()")
|
||||
vim.api.nvim_command('augroup end')
|
||||
end
|
||||
|
||||
return M
|
122
bundle/hop.nvim/lua/hop/hint.lua
Normal file
122
bundle/hop.nvim/lua/hop/hint.lua
Normal file
@ -0,0 +1,122 @@
|
||||
local perm = require'hop.perm'
|
||||
local prio = require'hop.priority'
|
||||
|
||||
local M = {}
|
||||
|
||||
M.HintDirection = {
|
||||
BEFORE_CURSOR = 1,
|
||||
AFTER_CURSOR = 2,
|
||||
}
|
||||
|
||||
M.HintPosition = {
|
||||
BEGIN = 1,
|
||||
MIDDLE = 2,
|
||||
END = 3,
|
||||
}
|
||||
|
||||
local function tbl_to_str(label)
|
||||
local s = ''
|
||||
|
||||
for i = 1, #label do
|
||||
s = s .. label[i]
|
||||
end
|
||||
|
||||
return s
|
||||
end
|
||||
|
||||
-- Reduce a hint.
|
||||
--
|
||||
-- This function will remove hints not starting with the input key and will reduce the other ones
|
||||
-- with one level.
|
||||
local function reduce_label(label, key)
|
||||
local snd_idx = vim.fn.byteidx(label, 1)
|
||||
if label:sub(1, snd_idx) == key then
|
||||
label = label:sub(snd_idx + 1)
|
||||
end
|
||||
|
||||
if label == '' then
|
||||
label = nil
|
||||
end
|
||||
|
||||
return label
|
||||
end
|
||||
|
||||
-- Reduce all hints and return the one fully reduced, if any.
|
||||
function M.reduce_hints(hints, key)
|
||||
local next_hints = {}
|
||||
|
||||
for _, h in pairs(hints) do
|
||||
local prev_label = h.label
|
||||
h.label = reduce_label(h.label, key)
|
||||
|
||||
if h.label == nil then
|
||||
return h
|
||||
elseif h.label ~= prev_label then
|
||||
next_hints[#next_hints + 1] = h
|
||||
end
|
||||
end
|
||||
|
||||
return nil, next_hints
|
||||
end
|
||||
|
||||
-- Create hints from jump targets.
|
||||
--
|
||||
-- This function associates jump targets with permutations, creating hints. A hint is then a jump target along with a
|
||||
-- label.
|
||||
--
|
||||
-- If `indirect_jump_targets` is `nil`, `jump_targets` is assumed already ordered with all jump target with the same
|
||||
-- score (0)
|
||||
function M.create_hints(jump_targets, indirect_jump_targets, opts)
|
||||
local hints = {}
|
||||
local perms = perm.permutations(opts.keys, #jump_targets, opts)
|
||||
|
||||
-- get or generate indirect_jump_targets
|
||||
if indirect_jump_targets == nil then
|
||||
indirect_jump_targets = {}
|
||||
|
||||
for i = 1, #jump_targets do
|
||||
indirect_jump_targets[i] = { index = i, score = 0 }
|
||||
end
|
||||
end
|
||||
|
||||
for i, indirect in pairs(indirect_jump_targets) do
|
||||
hints[indirect.index] = {
|
||||
label = tbl_to_str(perms[i]),
|
||||
jump_target = jump_targets[indirect.index]
|
||||
}
|
||||
end
|
||||
|
||||
return hints
|
||||
end
|
||||
|
||||
-- Create the extmarks for per-line hints.
|
||||
--
|
||||
-- Passing `opts.uppercase_labels = true` will display the hint as uppercase.
|
||||
function M.set_hint_extmarks(hl_ns, hints, opts)
|
||||
for _, hint in pairs(hints) do
|
||||
local label = hint.label
|
||||
if opts.uppercase_labels then
|
||||
label = label:upper()
|
||||
end
|
||||
|
||||
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, {
|
||||
virt_text = { { label, "HopNextKey" } },
|
||||
virt_text_pos = 'overlay',
|
||||
hl_mode = 'combine',
|
||||
priority = prio.HINT_PRIO
|
||||
})
|
||||
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
|
||||
virt_text = { { label:sub(1, snd_idx), "HopNextKey1" }, { label:sub(snd_idx + 1), "HopNextKey2" } },
|
||||
virt_text_pos = 'overlay',
|
||||
hl_mode = 'combine',
|
||||
priority = prio.HINT_PRIO
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
476
bundle/hop.nvim/lua/hop/init.lua
Normal file
476
bundle/hop.nvim/lua/hop/init.lua
Normal file
@ -0,0 +1,476 @@
|
||||
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.
|
||||
--
|
||||
-- Some options cannot be used together. For instance, multi_windows and current_line_only don’t really make sense used
|
||||
-- together. This function will notify the user of such ill-formed configurations.
|
||||
local function check_opts(opts)
|
||||
if not opts then
|
||||
return
|
||||
end
|
||||
|
||||
if opts.multi_windows and opts.current_line_only then
|
||||
vim.notify('Cannot use current_line_only across multiple windows', 3)
|
||||
end
|
||||
end
|
||||
|
||||
-- Allows to override global options with user local overrides.
|
||||
local function override_opts(opts)
|
||||
check_opts(opts)
|
||||
return setmetatable(opts or {}, {__index = M.opts})
|
||||
end
|
||||
|
||||
-- Display error messages.
|
||||
local function eprintln(msg, teasing)
|
||||
if teasing then
|
||||
vim.api.nvim_echo({{msg, 'Error'}}, true, {})
|
||||
end
|
||||
end
|
||||
|
||||
-- A hack to prevent #57 by deleting twice the namespace (it’s 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)
|
||||
end
|
||||
|
||||
-- Dim everything out to prepare the Hop session.
|
||||
--
|
||||
-- - 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 start_line = top_line
|
||||
local end_line = bottom_line
|
||||
local start_col = 0
|
||||
local end_col = nil
|
||||
|
||||
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
|
||||
|
||||
if current_line_only then
|
||||
if direction == hint.HintDirection.BEFORE_CURSOR then
|
||||
start_line = cursor_pos[1] - 1
|
||||
end_line = cursor_pos[1] - 1
|
||||
else
|
||||
start_line = cursor_pos[1] - 1
|
||||
end_line = cursor_pos[1]
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_extmark(buf_handle, hl_ns, start_line, start_col, {
|
||||
end_line = end_line,
|
||||
end_col = end_col,
|
||||
hl_group = 'HopUnmatched',
|
||||
hl_eol = true,
|
||||
priority = prio.DIM_PRIO
|
||||
})
|
||||
end
|
||||
|
||||
-- Add the virtual cursor, taking care to handle the cases where:
|
||||
-- - the virtualedit option is being used and the cursor is in a
|
||||
-- tab character or past the end of the line
|
||||
-- - the current line is empty
|
||||
-- - there are multibyte characters on the line
|
||||
local function add_virt_cur(ns)
|
||||
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
|
||||
local cur_offset = cur_info[4]
|
||||
local virt_col = cur_info[5] - 1
|
||||
local cur_line = vim.api.nvim_get_current_line()
|
||||
|
||||
-- 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, {
|
||||
virt_text = {{'█', 'Normal'}},
|
||||
virt_text_win_col = virt_col,
|
||||
priority = prio.CURSOR_PRIO
|
||||
})
|
||||
-- otherwise check to see if cursor is at end of line or on empty line
|
||||
elseif #cur_line == cur_col then
|
||||
vim.api.nvim_buf_set_extmark(0, ns, cur_row, cur_col, {
|
||||
virt_text = {{'█', 'Normal'}},
|
||||
virt_text_pos = 'overlay',
|
||||
priority = prio.CURSOR_PRIO
|
||||
})
|
||||
else
|
||||
vim.api.nvim_buf_set_extmark(0, ns, cur_row, cur_col, {
|
||||
-- end_col must be column of next character, in bytes
|
||||
end_col = vim.fn.byteidx(cur_line, vim.fn.charidx(cur_line, cur_col) + 1),
|
||||
hl_group = 'HopCursor',
|
||||
priority = prio.CURSOR_PRIO
|
||||
})
|
||||
end
|
||||
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).
|
||||
--
|
||||
-- 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 don’t 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 doesn’s have an API to «
|
||||
-- offset something by 1 visual column. »
|
||||
if inclusive then
|
||||
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)
|
||||
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})
|
||||
end
|
||||
|
||||
function M.hint_with(jump_target_gtr, opts)
|
||||
if opts == nil then
|
||||
opts = override_opts(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)
|
||||
end)
|
||||
end
|
||||
|
||||
function M.hint_with_callback(jump_target_gtr, opts, callback)
|
||||
if opts == nil then
|
||||
opts = override_opts(opts)
|
||||
end
|
||||
|
||||
if not M.initialized then
|
||||
vim.notify('Hop is not initialized; please call the setup function', 4)
|
||||
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 jump targets
|
||||
local generated = jump_target_gtr(opts)
|
||||
local jump_target_count = #generated.jump_targets
|
||||
|
||||
local h = nil
|
||||
if jump_target_count == 0 then
|
||||
eprintln(' -> there’s no such thing we can see…', opts.teasing)
|
||||
clear_namespace(0, hl_ns)
|
||||
clear_namespace(0, dim_ns)
|
||||
return
|
||||
elseif jump_target_count == 1 and opts.jump_on_sole_occurrence then
|
||||
local jt = generated.jump_targets[1]
|
||||
callback(jt)
|
||||
|
||||
clear_namespace(0, hl_ns)
|
||||
clear_namespace(0, 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)
|
||||
|
||||
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)
|
||||
vim.cmd('redraw')
|
||||
|
||||
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
|
||||
break
|
||||
end
|
||||
local not_special_key = true
|
||||
-- :h getchar(): "If the result of expr is a single character, it returns a
|
||||
-- number. Use nr2char() to convert it to a String." Also the result is a
|
||||
-- special key if it's a string and its first byte is 128.
|
||||
--
|
||||
-- Note of caution: Even though the result of `getchar()` might be a single
|
||||
-- character, that character might still be multiple bytes.
|
||||
if type(key) == 'number' then
|
||||
key = vim.fn.nr2char(key)
|
||||
elseif key:byte() == 128 then
|
||||
not_special_key = false
|
||||
end
|
||||
|
||||
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)
|
||||
vim.cmd('redraw')
|
||||
else
|
||||
-- If it's not, quit Hop
|
||||
for _, buf in ipairs(buf_list) do
|
||||
M.quit(buf, hint_state)
|
||||
end
|
||||
-- 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
|
||||
vim.api.nvim_feedkeys(key, '', true)
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Refine hints in the given buffer.
|
||||
--
|
||||
-- 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)
|
||||
local h, hints = hint.reduce_hints(hint_state.hints, key)
|
||||
|
||||
if h == nil then
|
||||
if #hints == 0 then
|
||||
eprintln('no remaining sequence starts with ' .. key, opts.teasing)
|
||||
return
|
||||
end
|
||||
|
||||
hint_state.hints = hints
|
||||
|
||||
for _, buf in ipairs(buf_list) do
|
||||
clear_namespace(buf, hint_state.hl_ns)
|
||||
end
|
||||
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
|
||||
|
||||
-- prior to jump, register the current position into the jump list
|
||||
vim.cmd("normal! m'")
|
||||
|
||||
callback(h.jump_target)
|
||||
return h
|
||||
end
|
||||
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)
|
||||
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
function M.hint_words(opts)
|
||||
opts = override_opts(opts)
|
||||
|
||||
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
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_word_start()),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
function M.hint_patterns(opts, pattern)
|
||||
opts = override_opts(opts)
|
||||
|
||||
-- The pattern to search is either retrieved from the (optional) argument
|
||||
-- or directly from user input.
|
||||
if pattern == nil then
|
||||
vim.fn.inputsave()
|
||||
|
||||
local ok
|
||||
ok, pattern = pcall(vim.fn.input, 'Search: ')
|
||||
vim.fn.inputrestore()
|
||||
|
||||
if not ok then
|
||||
return
|
||||
end
|
||||
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
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_case_searching(pattern, false, opts)),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
function M.hint_char1(opts)
|
||||
opts = override_opts(opts)
|
||||
|
||||
local ok, c = pcall(vim.fn.getchar)
|
||||
if not ok then
|
||||
return
|
||||
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
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_case_searching(vim.fn.nr2char(c), true, opts)),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
function M.hint_char2(opts)
|
||||
opts = override_opts(opts)
|
||||
|
||||
local ok, a = pcall(vim.fn.getchar)
|
||||
if not ok 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
|
||||
else
|
||||
generator = jump_target.jump_targets_by_scanning_lines
|
||||
end
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_case_searching(pattern, true, opts)),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
function M.hint_lines(opts)
|
||||
opts = override_opts(opts)
|
||||
|
||||
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
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_line_start()),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
function M.hint_lines_skip_whitespace(opts)
|
||||
opts = override_opts(opts)
|
||||
|
||||
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
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_line_start_skip_whitespace()),
|
||||
opts
|
||||
)
|
||||
end
|
||||
|
||||
function M.hint_anywhere(opts)
|
||||
opts = override_opts(opts)
|
||||
|
||||
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
|
||||
|
||||
M.hint_with(
|
||||
generator(jump_target.regex_by_anywhere()),
|
||||
opts
|
||||
)
|
||||
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.initialized = true
|
||||
|
||||
-- Insert the highlights and register the autocommand if asked to.
|
||||
local highlight = require'hop.highlight'
|
||||
highlight.insert_highlights()
|
||||
|
||||
if M.opts.create_hl_autocmd then
|
||||
highlight.create_autocmd()
|
||||
end
|
||||
|
||||
-- register Hop extensions, if any
|
||||
if M.opts.extensions ~= nil then
|
||||
for _, ext_name in pairs(opts.extensions) do
|
||||
local ok, extension = pcall(require, ext_name)
|
||||
if not ok then
|
||||
-- 4 is error; thanks Neovim… :(
|
||||
vim.notify(string.format('extension %s wasn’t correctly loaded', ext_name), 4)
|
||||
else
|
||||
if extension.register == nil then
|
||||
vim.notify(string.format('extension %s lacks the register function', ext_name), 4)
|
||||
else
|
||||
extension.register(opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
406
bundle/hop.nvim/lua/hop/jump_target.lua
Normal file
406
bundle/hop.nvim/lua/hop/jump_target.lua
Normal file
@ -0,0 +1,406 @@
|
||||
-- Jump targets.
|
||||
--
|
||||
-- Jump targets are locations in buffers where users might jump to. They are wrapped in a table and provide the
|
||||
-- required information so that Hop can associate label and display the hints.
|
||||
--
|
||||
-- {
|
||||
-- jump_targets = {},
|
||||
-- indirect_jump_targets = {},
|
||||
-- }
|
||||
--
|
||||
-- The `jump_targets` field is a list-table of jump targets. A single jump target is simply a location in a given
|
||||
-- buffer. So you can picture a jump target as a triple (line, column, window).
|
||||
--
|
||||
-- {
|
||||
-- line = 0,
|
||||
-- column = 0,
|
||||
-- window = 0,
|
||||
-- }
|
||||
--
|
||||
-- Indirect jump targets are encoded as a flat list-table of pairs (index, score). This table allows to quickly score
|
||||
-- and sort jump targets. The `index` field gives the index in the `jump_targets` list. The `score` is any number. The
|
||||
-- rule is that the lower the score is, the less prioritized the jump target will be.
|
||||
--
|
||||
-- {
|
||||
-- index = 0,
|
||||
-- score = 0,
|
||||
-- }
|
||||
--
|
||||
-- So for instance, for two jump targets, a jump target generator must return such a table:
|
||||
--
|
||||
-- {
|
||||
-- jump_targets = {
|
||||
-- { line = 1, column = 14, buffer = 0, window = 0 },
|
||||
-- { line = 2, column = 1, buffer = 0, window = 0 },
|
||||
-- },
|
||||
--
|
||||
-- indirect_jump_targets = {
|
||||
-- { index = 0, score = 14 },
|
||||
-- { index = 1, score = 7 },
|
||||
-- },
|
||||
-- }
|
||||
--
|
||||
-- This is everything you need to know to extend Hop with your own jump targets.
|
||||
|
||||
local hint = require'hop.hint'
|
||||
local window = require'hop.window'
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Manhattan distance with column and row, weighted on x so that results are more packed on y.
|
||||
function M.manh_dist(a, b, x_bias)
|
||||
local bias = x_bias or 10
|
||||
return bias * math.abs(b[1] - a[1]) + math.abs(b[2] - a[2])
|
||||
end
|
||||
|
||||
-- Mark the current line with jump targets.
|
||||
--
|
||||
-- Returns the jump targets as described above.
|
||||
local function mark_jump_targets_line(buf_handle, win_handle, regex, line_context, col_offset, win_width, direction_mode, hint_position)
|
||||
local jump_targets = {}
|
||||
local end_index = nil
|
||||
|
||||
if win_width ~= nil then
|
||||
end_index = col_offset + win_width
|
||||
else
|
||||
end_index = vim.fn.strdisplaywidth(line_context.line)
|
||||
end
|
||||
|
||||
local shifted_line = line_context.line:sub(1 + col_offset, vim.fn.byteidx(line_context.line, end_index))
|
||||
|
||||
-- modify the shifted line to take the direction mode into account, if any
|
||||
-- FIXME: we also need to do that for the cursor
|
||||
local col_bias = 0
|
||||
if direction_mode ~= nil then
|
||||
local col = vim.fn.byteidx(line_context.line, direction_mode.cursor_col + 1)
|
||||
if direction_mode.direction == hint.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 == hint.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)
|
||||
local b, e = regex.match(s)
|
||||
|
||||
if b == nil or (b == 0 and e == 0) then
|
||||
break
|
||||
end
|
||||
|
||||
local colp = col + b
|
||||
if hint_position == hint.HintPosition.MIDDLE then
|
||||
colp = col + math.floor((b + e) / 2)
|
||||
elseif hint_position == hint.HintPosition.END then
|
||||
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),
|
||||
buffer = buf_handle,
|
||||
window = win_handle,
|
||||
}
|
||||
|
||||
if regex.oneshot then
|
||||
break
|
||||
else
|
||||
col = col + e
|
||||
end
|
||||
end
|
||||
|
||||
return jump_targets
|
||||
end
|
||||
|
||||
-- Create jump targets for a given indexed line.
|
||||
--
|
||||
-- This function creates the jump targets for the current (indexed) line and appends them to the input list of jump
|
||||
-- targets `jump_targets`.
|
||||
--
|
||||
-- Indirect jump targets are used later to sort jump targets by score and create hints.
|
||||
local function create_jump_targets_for_line(
|
||||
buf_handle,
|
||||
win_handle,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
col_offset,
|
||||
win_width,
|
||||
cursor_pos,
|
||||
direction_mode,
|
||||
hint_position,
|
||||
line_context
|
||||
)
|
||||
-- first, create the jump targets for the ith line
|
||||
local line_jump_targets = mark_jump_targets_line(
|
||||
buf_handle,
|
||||
win_handle,
|
||||
regex,
|
||||
line_context,
|
||||
col_offset,
|
||||
win_width,
|
||||
direction_mode,
|
||||
hint_position
|
||||
)
|
||||
|
||||
-- then, append those to the input jump target list and create the indexed jump targets
|
||||
local win_bias = math.abs(vim.api.nvim_get_current_win() - win_handle) * 1000
|
||||
for _, jump_target in pairs(line_jump_targets) do
|
||||
jump_targets[#jump_targets + 1] = jump_target
|
||||
|
||||
indirect_jump_targets[#indirect_jump_targets + 1] = {
|
||||
index = #jump_targets,
|
||||
score = M.manh_dist(cursor_pos, { jump_target.line, jump_target.column }) + win_bias
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Create jump targets by scanning lines in the currently visible buffer.
|
||||
--
|
||||
-- This function takes a regex argument, which is an object containing a match function that must return the span
|
||||
-- (inclusive beginning, exclusive end) of the match item, or nil when no more match is possible. This object also
|
||||
-- contains the `oneshot` field, a boolean stating whether only the first match of a line should be taken into account.
|
||||
--
|
||||
-- This function returns the lined jump targets (an array of N lines, where N is the number of currently visible lines).
|
||||
-- Lines without jump targets are assigned an empty table ({}). For lines with jump targets, a list-table contains the
|
||||
-- jump targets as pair of { line, col }.
|
||||
--
|
||||
-- In addition the jump targets, this function returns the total number of jump targets (i.e. this is the same thing as
|
||||
-- traversing the lined jump targets and summing the number of jump targets for all lines) as a courtesy, plus «
|
||||
-- indirect jump targets. » Indirect jump targets are encoded as a flat list-table containing three values: i, for the
|
||||
-- ith line, j, for the rank of the jump target, and dist, the score distance of the associated jump target. This list
|
||||
-- is sorted according to that last dist parameter in order to know how to distribute the jump targets over the buffer.
|
||||
function M.jump_targets_by_scanning_lines(regex)
|
||||
return function(opts)
|
||||
-- get the window context; this is used to know which part of the visible buffer is to hint
|
||||
local all_ctxs = window.get_window_context(opts.multi_windows)
|
||||
local jump_targets = {}
|
||||
local indirect_jump_targets = {}
|
||||
|
||||
-- Iterate all buffers
|
||||
for _, bctx in ipairs(all_ctxs) do
|
||||
-- Iterate all windows of a same buffer
|
||||
for _, wctx in ipairs(bctx.contexts) do
|
||||
window.clip_window_context(wctx, opts.direction)
|
||||
-- Get all lines' context
|
||||
local lines = window.get_lines_context(bctx.hbuf, wctx)
|
||||
|
||||
-- in the case of a direction, we want to treat the first or last line (according to the direction) differently
|
||||
if opts.direction == hint.HintDirection.AFTER_CURSOR then
|
||||
-- the first line is to be checked first
|
||||
create_jump_targets_for_line(
|
||||
bctx.hbuf,
|
||||
wctx.hwin,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
wctx.col_offset,
|
||||
wctx.win_width,
|
||||
wctx.cursor_pos,
|
||||
{ cursor_col = wctx.cursor_pos[2], direction = opts.direction },
|
||||
opts.hint_position,
|
||||
lines[1]
|
||||
)
|
||||
|
||||
for i = 2, #lines do
|
||||
create_jump_targets_for_line(
|
||||
bctx.hbuf,
|
||||
wctx.hwin,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
wctx.col_offset,
|
||||
wctx.win_width,
|
||||
wctx.cursor_pos,
|
||||
nil,
|
||||
opts.hint_position,
|
||||
lines[i]
|
||||
)
|
||||
end
|
||||
elseif opts.direction == hint.HintDirection.BEFORE_CURSOR then
|
||||
-- the last line is to be checked last
|
||||
for i = 1, #lines - 1 do
|
||||
create_jump_targets_for_line(
|
||||
bctx.hbuf,
|
||||
wctx.hwin,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
wctx.col_offset,
|
||||
wctx.win_width,
|
||||
wctx.cursor_pos,
|
||||
nil,
|
||||
opts.hint_position,
|
||||
lines[i]
|
||||
)
|
||||
end
|
||||
|
||||
create_jump_targets_for_line(
|
||||
bctx.hbuf,
|
||||
wctx.hwin,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
wctx.col_offset,
|
||||
wctx.win_width,
|
||||
wctx.cursor_pos,
|
||||
{ cursor_col = wctx.cursor_pos[2], direction = opts.direction },
|
||||
opts.hint_position,
|
||||
lines[#lines]
|
||||
)
|
||||
else
|
||||
for i = 1, #lines do
|
||||
create_jump_targets_for_line(
|
||||
bctx.hbuf,
|
||||
wctx.hwin,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
wctx.col_offset,
|
||||
wctx.win_width,
|
||||
wctx.cursor_pos,
|
||||
nil,
|
||||
opts.hint_position,
|
||||
lines[i]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
M.sort_indirect_jump_targets(indirect_jump_targets, opts)
|
||||
|
||||
return { jump_targets = jump_targets, indirect_jump_targets = indirect_jump_targets }
|
||||
end
|
||||
end
|
||||
|
||||
-- Jump target generator for regex applied only on the cursor line.
|
||||
function M.jump_targets_for_current_line(regex)
|
||||
return function(opts)
|
||||
local context = window.get_window_context(false)[1].contexts[1]
|
||||
local line_n = context.cursor_pos[1]
|
||||
local line = vim.api.nvim_buf_get_lines(0, line_n - 1, line_n, false)
|
||||
local jump_targets = {}
|
||||
local indirect_jump_targets = {}
|
||||
|
||||
create_jump_targets_for_line(
|
||||
0,
|
||||
0,
|
||||
jump_targets,
|
||||
indirect_jump_targets,
|
||||
regex,
|
||||
context.col_offset,
|
||||
context.win_width,
|
||||
context.cursor_pos,
|
||||
{ cursor_col = context.cursor_pos[2], direction = opts.direction },
|
||||
opts.hint_position,
|
||||
{ line_nr = line_n - 1, line = line[1] }
|
||||
)
|
||||
|
||||
M.sort_indirect_jump_targets(indirect_jump_targets, opts)
|
||||
|
||||
return { jump_targets = jump_targets, indirect_jump_targets = indirect_jump_targets }
|
||||
end
|
||||
end
|
||||
|
||||
-- Apply a score function based on the Manhattan distance to indirect jump targets.
|
||||
function M.sort_indirect_jump_targets(indirect_jump_targets, opts)
|
||||
local score_comparison = nil
|
||||
if opts.reverse_distribution then
|
||||
score_comparison = function (a, b) return a.score > b.score end
|
||||
else
|
||||
score_comparison = function (a, b) return a.score < b.score end
|
||||
end
|
||||
|
||||
table.sort(indirect_jump_targets, score_comparison)
|
||||
end
|
||||
|
||||
-- Regex modes for the buffer-driven generator.
|
||||
local function starts_with_uppercase(s)
|
||||
if #s == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local f = s:sub(1, vim.fn.byteidx(s, 1))
|
||||
-- if it’s a space, we assume it’s not uppercase, even though Lua doesn’t agree with us; I mean, Lua is horrible, who
|
||||
-- would like to argue with that creature, right?
|
||||
if f == ' ' then
|
||||
return false
|
||||
end
|
||||
|
||||
return f:upper() == f
|
||||
end
|
||||
|
||||
-- Regex by searching a pattern.
|
||||
function M.regex_by_searching(pat, plain_search)
|
||||
if plain_search then
|
||||
pat = vim.fn.escape(pat, '\\/.$^~[]')
|
||||
end
|
||||
return {
|
||||
oneshot = false,
|
||||
match = function(s)
|
||||
return vim.regex(pat):match_str(s)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Wrapper over M.regex_by_searching to add support for case sensitivity.
|
||||
function M.regex_by_case_searching(pat, plain_search, opts)
|
||||
if plain_search then
|
||||
pat = vim.fn.escape(pat, '\\/.$^~[]')
|
||||
end
|
||||
|
||||
if vim.o.smartcase then
|
||||
if not starts_with_uppercase(pat) then
|
||||
pat = '\\c' .. pat
|
||||
end
|
||||
elseif opts.case_insensitive then
|
||||
pat = '\\c' .. pat
|
||||
end
|
||||
|
||||
return {
|
||||
oneshot = false,
|
||||
match = function(s)
|
||||
return vim.regex(pat):match_str(s)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Word regex.
|
||||
function M.regex_by_word_start()
|
||||
return M.regex_by_searching('\\k\\+')
|
||||
end
|
||||
|
||||
-- Line regex.
|
||||
function M.regex_by_line_start()
|
||||
return {
|
||||
oneshot = true,
|
||||
match = function(_)
|
||||
return 0, 1, false
|
||||
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")
|
||||
return {
|
||||
oneshot = true,
|
||||
match = function(s)
|
||||
return pat:match_str(s)
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
-- Anywhere regex.
|
||||
function M.regex_by_anywhere()
|
||||
return M.regex_by_searching('\\v(<.|^$)|(.>|^$)|(\\l)\\zs(\\u)|(_\\zs.)|(#\\zs.)')
|
||||
end
|
||||
|
||||
return M
|
163
bundle/hop.nvim/lua/hop/perm.lua
Normal file
163
bundle/hop.nvim/lua/hop/perm.lua
Normal file
@ -0,0 +1,163 @@
|
||||
local M = {}
|
||||
|
||||
-- Get the first key of a key set.
|
||||
local function first_key(keys)
|
||||
return keys:sub(1, vim.fn.byteidx(keys, 1))
|
||||
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)
|
||||
|
||||
if e == #keys then
|
||||
return nil
|
||||
end
|
||||
|
||||
local next = keys:sub(e + 1)
|
||||
local n = next:sub(1, vim.fn.byteidx(next, 1))
|
||||
return n
|
||||
end
|
||||
|
||||
-- Permutation algorithm based on tries and backtrack filling.
|
||||
M.TrieBacktrackFilling = {}
|
||||
|
||||
-- Get the sequence encoded in a trie by a pointer.
|
||||
function M.TrieBacktrackFilling:lookup_seq_trie(trie, p)
|
||||
local seq = {}
|
||||
local t = trie
|
||||
|
||||
for _, i in pairs(p) do
|
||||
local current_trie = t[i]
|
||||
|
||||
seq[#seq + 1] = current_trie.key
|
||||
t = current_trie.trie
|
||||
end
|
||||
|
||||
seq[#seq + 1] = t[#t].key
|
||||
|
||||
return seq
|
||||
end
|
||||
|
||||
-- Add a new permutation to the trie at the current pointer by adding a key.
|
||||
function M.TrieBacktrackFilling:add_trie_key(trie, p, key)
|
||||
local seq = {}
|
||||
local t = trie
|
||||
|
||||
-- find the parent trie
|
||||
for _, i in pairs(p) do
|
||||
local current_trie = t[i]
|
||||
|
||||
seq[#seq + 1] = current_trie.key
|
||||
t = current_trie.trie
|
||||
end
|
||||
|
||||
t[#t + 1] = { key = key; trie = {} }
|
||||
|
||||
return trie
|
||||
end
|
||||
|
||||
-- Maintain a trie pointer of a given dimension.
|
||||
--
|
||||
-- If a pointer has components { 4, 1 } and the dimension is 4, this function will automatically complete the missing
|
||||
-- dimensions by adding the last index, i.e. { 4, 1, X, X }.
|
||||
local function maintain_deep_pointer(depth, n, p)
|
||||
local q = vim.deepcopy(p)
|
||||
|
||||
for i = #p + 1, depth do
|
||||
q[i] = n
|
||||
end
|
||||
|
||||
return q
|
||||
end
|
||||
|
||||
-- Generate the next permutation with backtrack filling.
|
||||
--
|
||||
-- - `keys` is the input key set.
|
||||
-- - `trie` is a trie representing all the already generated permutations.
|
||||
-- - `p` is the current pointer in the trie. It is a list of indices representing the parent layer in which the current
|
||||
-- sequence occurs in.
|
||||
--
|
||||
-- Returns `perms` added with the next permutation.
|
||||
function M.TrieBacktrackFilling:next_perm(keys, trie, p)
|
||||
if #trie == 0 then
|
||||
return { { key = first_key(keys); trie = {} } }, p
|
||||
end
|
||||
|
||||
-- check whether the current sequence can have a next one
|
||||
local current_seq = self:lookup_seq_trie(trie, p)
|
||||
local key = next_key(keys, current_seq[#current_seq])
|
||||
|
||||
if key ~= nil then
|
||||
-- we can generate the next permutation by just adding key to the current trie
|
||||
self:add_trie_key(trie, p, key)
|
||||
return trie, p
|
||||
else
|
||||
-- we have to backtrack; first, decrement the pointer if possible
|
||||
local max_depth = #p
|
||||
local keys_len = vim.fn.strwidth(keys)
|
||||
|
||||
while #p > 0 do
|
||||
local last_index = p[#p]
|
||||
if last_index > 1 then
|
||||
p[#p] = last_index - 1
|
||||
|
||||
p = maintain_deep_pointer(max_depth, keys_len, p)
|
||||
|
||||
-- insert the first key at the new pointer after mutating the one already there
|
||||
self:add_trie_key(trie, p, first_key(keys))
|
||||
self:add_trie_key(trie, p, next_key(keys, first_key(keys)))
|
||||
return trie, p
|
||||
else
|
||||
-- we have exhausted all the permutations for the current layer; drop the layer index and try again
|
||||
p[#p] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- all layers are completely full everywhere; add a new layer at the end
|
||||
p = maintain_deep_pointer(max_depth, keys_len, p)
|
||||
|
||||
p[#p + 1] = #trie -- new layer
|
||||
self:add_trie_key(trie, p, first_key(keys))
|
||||
self:add_trie_key(trie, p, next_key(keys, first_key(keys)))
|
||||
|
||||
return trie, p
|
||||
end
|
||||
end
|
||||
|
||||
function M.TrieBacktrackFilling:trie_to_perms(trie, perm)
|
||||
local perms = {}
|
||||
local p = vim.deepcopy(perm)
|
||||
p[#p + 1] = trie.key
|
||||
|
||||
if #trie.trie > 0 then
|
||||
for _, sub_trie in pairs(trie.trie) do
|
||||
vim.list_extend(perms, self:trie_to_perms(sub_trie, p))
|
||||
end
|
||||
else
|
||||
perms = { p }
|
||||
end
|
||||
|
||||
return perms
|
||||
end
|
||||
|
||||
function M.TrieBacktrackFilling:permutations(keys, n)
|
||||
local perms = {}
|
||||
local trie = {}
|
||||
local p = {}
|
||||
|
||||
for _ = 1, n do
|
||||
trie, p = self:next_perm(keys, trie, p)
|
||||
end
|
||||
|
||||
for _, sub_trie in pairs(trie) do
|
||||
vim.list_extend(perms, self:trie_to_perms(sub_trie, {}))
|
||||
end
|
||||
|
||||
return perms
|
||||
end
|
||||
|
||||
function M.permutations(keys, n, opts)
|
||||
return opts.perm_method:permutations(keys, n, opts)
|
||||
end
|
||||
|
||||
return M
|
14
bundle/hop.nvim/lua/hop/priority.lua
Normal file
14
bundle/hop.nvim/lua/hop/priority.lua
Normal file
@ -0,0 +1,14 @@
|
||||
-- Magic constants for highlight priorities;
|
||||
--
|
||||
-- Priorities are ranged on 16-bit integers; 0 is the least priority and 2^16 - 1 is the higher.
|
||||
-- We want Hop to override everything so we use a very high priority for grey (2^16 - 3 = 65533); hint
|
||||
-- priorities are one level above (2^16 - 2) and the virtual cursor one level higher (2^16 - 1), which
|
||||
-- is the higher.
|
||||
|
||||
local M = {}
|
||||
|
||||
M.DIM_PRIO = 65533
|
||||
M.HINT_PRIO = 65534
|
||||
M.CURSOR_PRIO = 65535
|
||||
|
||||
return M
|
140
bundle/hop.nvim/lua/hop/window.lua
Normal file
140
bundle/hop.nvim/lua/hop/window.lua
Normal file
@ -0,0 +1,140 @@
|
||||
local hint = require'hop.hint'
|
||||
|
||||
local M = {}
|
||||
|
||||
local function window_context(win_handle, cursor_pos)
|
||||
-- get a bunch of information about the window and the cursor
|
||||
local win_info = vim.fn.getwininfo(win_handle)[1]
|
||||
local win_view = vim.fn.winsaveview()
|
||||
local top_line = win_info.topline - 1
|
||||
local bot_line = win_info.botline
|
||||
|
||||
-- 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
|
||||
-- local left_col_offset = win_info.variables.context.number_width + win_info.variables.context.sign_width
|
||||
local win_width = nil
|
||||
|
||||
-- hack to get the left column offset in nowrap
|
||||
if not vim.wo.wrap then
|
||||
vim.api.nvim_win_set_cursor(win_handle, { cursor_pos[1], 0 })
|
||||
local left_col_offset = vim.fn.wincol() - 1
|
||||
vim.fn.winrestview(win_view)
|
||||
win_width = win_info.width - left_col_offset
|
||||
end
|
||||
|
||||
return {
|
||||
hwin = win_handle,
|
||||
cursor_pos = cursor_pos,
|
||||
top_line = top_line,
|
||||
bot_line = bot_line,
|
||||
win_width = win_width,
|
||||
col_offset = win_view.leftcol
|
||||
}
|
||||
end
|
||||
|
||||
-- Collect all multi-windows's context:
|
||||
--
|
||||
-- {
|
||||
-- { -- context list that each contains one buffer
|
||||
-- hbuf = <buf-handle>,
|
||||
-- { -- windows list that display the same buffer
|
||||
-- hwin = <win-handle>,
|
||||
-- ...
|
||||
-- },
|
||||
-- ...
|
||||
-- },
|
||||
-- ...
|
||||
-- }
|
||||
function M.get_window_context(multi_windows)
|
||||
local all_ctxs = {}
|
||||
|
||||
-- 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)) },
|
||||
}
|
||||
|
||||
if not multi_windows then
|
||||
return all_ctxs
|
||||
end
|
||||
|
||||
for _, w in ipairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
local b = vim.api.nvim_win_get_buf(w)
|
||||
if w ~= cur_hwin then
|
||||
|
||||
-- check duplicated buffers; the way this is done is by accessing all the already known contexts and checking that
|
||||
-- the buffer we are accessing is already present in; if it is, we then append the window context to that buffer
|
||||
local bctx = nil
|
||||
for _, buffer_ctx in ipairs(all_ctxs) do
|
||||
if b == buffer_ctx.hbuf then
|
||||
bctx = buffer_ctx.contexts
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if bctx then
|
||||
bctx[#bctx + 1] = window_context(w, vim.api.nvim_win_get_cursor(w))
|
||||
else
|
||||
all_ctxs[#all_ctxs + 1] = {
|
||||
hbuf = b,
|
||||
contexts = { window_context(w, vim.api.nvim_win_get_cursor(w)) }
|
||||
}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
-- Move cursor back to current window
|
||||
vim.api.nvim_set_current_win(cur_hwin)
|
||||
|
||||
return all_ctxs
|
||||
end
|
||||
|
||||
-- Collect visible and unfold lines of window context
|
||||
--
|
||||
-- {
|
||||
-- { line_nr = 0, line = "" }
|
||||
-- }
|
||||
function M.get_lines_context(buf_handle, context)
|
||||
local lines = {}
|
||||
|
||||
local lnr = context.top_line
|
||||
while lnr < context.bot_line do -- top_line is inclusive and bot_line is exclusive
|
||||
local fold_end = vim.api.nvim_win_call(context.hwin,
|
||||
function()
|
||||
return vim.fn.foldclosedend(lnr + 1) -- `foldclosedend()` use 1-based line number
|
||||
end)
|
||||
if fold_end == -1 then
|
||||
lines[#lines + 1] = {
|
||||
line_nr = lnr,
|
||||
line = vim.api.nvim_buf_get_lines(buf_handle, lnr, lnr + 1, false)[1], -- `nvim_buf_get_lines()` use 0-based line index
|
||||
}
|
||||
lnr = lnr + 1
|
||||
else
|
||||
lines[#lines + 1] = {
|
||||
line_nr = lnr,
|
||||
line = "",
|
||||
}
|
||||
lnr = fold_end
|
||||
end
|
||||
end
|
||||
|
||||
return lines
|
||||
end
|
||||
|
||||
-- Clip the window context based on the direction.
|
||||
--
|
||||
-- If the direction is HintDirection.BEFORE_CURSOR, then everything after the cursor will be clipped.
|
||||
-- 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
|
||||
elseif direction == hint.HintDirection.AFTER_CURSOR then
|
||||
context.top_line = context.cursor_pos[1] - 1
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
63
bundle/hop.nvim/plugin/hop.vim
Normal file
63
bundle/hop.nvim/plugin/hop.vim
Normal file
@ -0,0 +1,63 @@
|
||||
if !has('nvim-0.5.0')
|
||||
echohl Error
|
||||
echom 'This plugin only works with Neovim >= v0.5.0'
|
||||
echohl clear
|
||||
finish
|
||||
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 })
|
||||
command! HopWordCurrentLine lua require'hop'.hint_words({ current_line_only = true })
|
||||
command! HopWordCurrentLineBC lua require'hop'.hint_words({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })
|
||||
command! HopWordCurrentLineAC lua require'hop'.hint_words({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })
|
||||
command! HopWordMW lua require'hop'.hint_words({ multi_windows = true })
|
||||
|
||||
" 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 })
|
||||
command! HopPatternCurrentLine lua require'hop'.hint_patterns({ current_line_only = true })
|
||||
command! HopPatternCurrentLineBC lua require'hop'.hint_patterns({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })
|
||||
command! HopPatternCurrentLineAC lua require'hop'.hint_patterns({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })
|
||||
command! HopPatternMW lua require'hop'.hint_patterns({ multi_windows = true })
|
||||
|
||||
" 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 })
|
||||
command! HopChar1CurrentLine lua require'hop'.hint_char1({ current_line_only = true })
|
||||
command! HopChar1CurrentLineBC lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })
|
||||
command! HopChar1CurrentLineAC lua require'hop'.hint_char1({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })
|
||||
command! HopChar1MW lua require'hop'.hint_char1({ multi_windows = true })
|
||||
|
||||
" 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 })
|
||||
command! HopChar2CurrentLine lua require'hop'.hint_char2({ current_line_only = true })
|
||||
command! HopChar2CurrentLineBC lua require'hop'.hint_char2({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })
|
||||
command! HopChar2CurrentLineAC lua require'hop'.hint_char2({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })
|
||||
command! HopChar2MW lua require'hop'.hint_char2({ multi_windows = true })
|
||||
|
||||
" 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 })
|
||||
command! HopLineMW lua require'hop'.hint_lines({ multi_windows = true })
|
||||
|
||||
" The jump-to-line command.
|
||||
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 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 })
|
||||
command! HopAnywhereAC lua require'hop'.hint_anywhere({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR })
|
||||
command! HopAnywhereCurrentLine lua require'hop'.hint_anywhere({ current_line_only = true })
|
||||
command! HopAnywhereCurrentLineBC lua require'hop'.hint_anywhere({ direction = require'hop.hint'.HintDirection.BEFORE_CURSOR, current_line_only = true })
|
||||
command! HopAnywhereCurrentLineAC lua require'hop'.hint_anywhere({ direction = require'hop.hint'.HintDirection.AFTER_CURSOR, current_line_only = true })
|
||||
command! HopAnywhereMW lua require'hop'.hint_anywhere({ multi_windows = true })
|
260
bundle/hop.nvim/rfcs/0001-hop-general-hint-modes.md
Normal file
260
bundle/hop.nvim/rfcs/0001-hop-general-hint-modes.md
Normal file
@ -0,0 +1,260 @@
|
||||
# Hop hint modes refined: an extensible model
|
||||
|
||||
This document is a design document presenting a redesign of Hop’s « hint modes » to allow for a better customization
|
||||
experience for people using Hop.
|
||||
|
||||
|
||||
<!-- vim-markdown-toc GFM -->
|
||||
|
||||
* [Context](#context)
|
||||
* [Analysis](#analysis)
|
||||
* [Prior and on-going work](#prior-and-on-going-work)
|
||||
* [Solution](#solution)
|
||||
* [Redesign `HintMode`](#redesign-hintmode)
|
||||
* [Rewrite the public interface to support already existing modes](#rewrite-the-public-interface-to-support-already-existing-modes)
|
||||
* [Part of the work that can be taken out of #123](#part-of-the-work-that-can-be-taken-out-of-123)
|
||||
* [Alternatives](#alternatives)
|
||||
* [Rationale](#rationale)
|
||||
* [Future work](#future-work)
|
||||
|
||||
<!-- vim-markdown-toc -->
|
||||
# Context
|
||||
|
||||
The current code uses the concept of _hint modes_ to work. Hop goes through all the visible lines and applies the hint
|
||||
mode on each line, extracting _jump targets_. The jump targets are then associated with permutations, and the sum of
|
||||
those properties makes a _hint_.
|
||||
|
||||
The goal is to be able to abstract away from this representation and create more general jump targets, so that the core
|
||||
of Hop can be built using this new model, but also dependent users can:
|
||||
|
||||
- Build other plugins using the Hop API to create their own jump target and then be able to jump to them.
|
||||
- Extend the possible hint modes to provide more Hop motion without necessarily having to merge their code upstream.
|
||||
This is especially true as some needs are not necessarily something that should be maintained in Hop directly, such
|
||||
as Treesitter targets which are considered not really interesting. Nevertheless, if some users would like to be able
|
||||
to use Treesitter as a source of targets, Hop should provide a powerful enough API to allow people to do just that.
|
||||
|
||||
Currently, there is no way (besides pushing code) to extend Hop features. Because we want to let _programmers_ extend
|
||||
Hop, there is no question to let _users_ extend it. What that means is that if a new motion is wanted, two possible
|
||||
options are available:
|
||||
|
||||
- The motion is implemented as a local Lua function / Vim command mapped in the user configuration.
|
||||
- Someone makes a plugin exposing the Lua function / a Vim command and implementing the motion.
|
||||
- A possible third option that is unlikely but still possible would be that the motion is small and useful enough to
|
||||
merge it upstream in https://github.com/phaazon/hop.nvim.
|
||||
|
||||
# Analysis
|
||||
|
||||
In order to understand how the code is currently working, we can have a look at it from a user perspective. They are
|
||||
likely to use, either:
|
||||
|
||||
- The Vim commands, exposed in `pugin/hop.vim`.
|
||||
- The Lua API public functions, in `lua/hop/init.lua`.
|
||||
|
||||
The Lua functions to use start with `hint_`. For instance, `hint_words()` (`:HopWord`). `hint_words` is defined as:
|
||||
|
||||
```lua
|
||||
function M.hint_words(opts)
|
||||
hint_with(hint.by_word_start, get_command_opts(opts))
|
||||
end
|
||||
```
|
||||
|
||||
`hint_patterns`, `hint_char1`, etc. are defined in a similar fashion. `hint_with` is the current (local) function used
|
||||
to build other hint modes. It takes a `HintMode` as argument and the user options, and builds applies the hint mode.
|
||||
This is the function that needs to be changed. It must, first, be publicly available. Then, the way the hint modes are
|
||||
applied need to change. For instance, if a user wants to use Hop with their own jump targets (without having to scan
|
||||
the visible part of the buffer), they should be able to.
|
||||
|
||||
The `hint_with` function is a pretty complex function that does a lot of things:
|
||||
|
||||
- It extracts a bunch of information about the current visible part of the buffer. This is useful not to create hints
|
||||
for text that the user cannot see.
|
||||
- It supports various optinos, such as direction hinting (before cursor, after cursor, current-line-only, etc.).
|
||||
- It creates the highlight groups.
|
||||
- Get the buffer lines so that hint modes can be applied on.
|
||||
- Call the hint modes and reduce hints until a match is found.
|
||||
- Do the actual jump.
|
||||
|
||||
`hint_mode`, the `HintMode` argument assed to `hint_with`, is used like this:
|
||||
|
||||
- It contains a `curr_line_only` boolean that allows to know whether the hinting should be restricted to the current
|
||||
line only.
|
||||
- It is passed to `hop.hint.create_hints` to create the hints. To do so, it is passed to other functions that will call
|
||||
the `match` function on it. What it does is to generate a pair of values allowing to pin-point where the jump
|
||||
targets are. It is not really an iterator as it returns _spans_ value (i.e. beginning / end), so we have to manually
|
||||
shift lines to know where and when to stop.
|
||||
- It contains a `oneshot` boolean that is mostly an implementation detail for the `match` loop to work. If it’s
|
||||
`oneshot`, the loop breaks at the first iteration. This is useful for line hinting for instance, where only one jump
|
||||
target should exist on each line.
|
||||
|
||||
So a couple of things to change here, obviously.
|
||||
|
||||
# Prior and on-going work
|
||||
|
||||
Some PRs have been pushed to attempt to solve this problem:
|
||||
|
||||
- [#123](https://github.com/phaazon/hop.nvim/pull/123): refactor hint strategies. Unfortunately, this PR wasn’t reviewed
|
||||
until late and had conflicting changes. Also, this PR changed too many things, refactoring things that don’t really
|
||||
have to be at this point (or not making sense to move around). However, the work can probably be partially taken out
|
||||
and rebased in other commits, so that this work is not lost.
|
||||
- [#133](https://github.com/phaazon/hop.nvim/pull/133): this one is a bit weird, as its scope could be have been split
|
||||
into several PRs. The multi-windows support is probably something that will come later once hint modes are refactored.
|
||||
The dict support to allow to pass a dict of things is interesting but that’s also a feature that should be added
|
||||
later, once the code is refactored.
|
||||
|
||||
So clearly, we need to do something about #123 first. #133 will then be rebased and should be smaller.
|
||||
|
||||
# Solution
|
||||
|
||||
## Redesign `HintMode`
|
||||
|
||||
The first thing that needs to be done is to change change `HintMode` so that it doesn’t assume to run line-by-line. The
|
||||
thing is, `hint_with` should be its own hint mode (that iterates over the lines of the currently visible part of the
|
||||
buffer and extract jump targets). A `HintMode` should then be:
|
||||
|
||||
- A function that provides the jump targets. We need to provide some functions to be able to get visible lines for
|
||||
instance for people who still want to operate on these. The idea is that once this function has run, it must provide a
|
||||
dict of jump targets by buffer. Something like:
|
||||
|
||||
```lua
|
||||
{
|
||||
-- jump target for a buffer
|
||||
{
|
||||
buffer_handle = 124,
|
||||
jump_targets = {
|
||||
{ line = 67, column = 4},
|
||||
{ line = 67, column = 7},
|
||||
-- …
|
||||
},
|
||||
},
|
||||
|
||||
-- another jump target for another buffer…
|
||||
}
|
||||
```
|
||||
|
||||
- The code that creates hints (`hop.hint.create_hints`) then must only call that function and do the regular, currently
|
||||
implemented algorithm associating jump targets with permutations to generate the actual hints.
|
||||
- The hint reduction can occur as it normally does.
|
||||
|
||||
This solution removes `oneshot` and `match`, leaving hint modes as a simple generator function providing the list of
|
||||
jump targets. However, doing this will require to move some code around to help writing those jump target generators.
|
||||
For instance, the logic that goes line-by-line, extracting word patterns for instance, is not trivial and is very tricky
|
||||
to implement (multi-byte, virtualedit, etc.). So this must stay around. Something we can probably do here is to provide
|
||||
a function that will output a `HintMode` going line-by-line and applying the logic passed as argument. Also, I suggest
|
||||
to change the name `HintMode` to `JumpTargetGenerator`, which makes more sense.
|
||||
|
||||
About the actual function generating the list, something that goes to mind: should we make this a fully synchronous
|
||||
function that will return all the targets at once, or should we make this an actual generator? I.e. calling it will
|
||||
return the first jump target, then calling it a second time will return the next jump target, etc. I think it can have
|
||||
interesting use-cases but it will probably slow everything down for probably not something super interesting.
|
||||
|
||||
This design seems to be pretty similar to what was planned in #123, so there is probably some commits to extract from
|
||||
that PR.
|
||||
|
||||
## Rewrite the public interface to support already existing modes
|
||||
|
||||
Currently, the following modes are available:
|
||||
|
||||
- `HopWord`: hint words.
|
||||
- `HopWordBC`: same as above, but _before cursor_.
|
||||
- `HopWordAC`: same as above, but _after cursor_.
|
||||
- `HopPattern`: hint pattern (manually entered by the user with `input`).
|
||||
- `HopPatternBC`: same as above, but _before cursor_.
|
||||
- `HopPatternAC`: same as above, but _after cursor_.
|
||||
- `HopChar1`: hint the current buffer by pressing one character to select which ones to jump to.
|
||||
- `HopChar1BC`: same as above but _before cursor_.
|
||||
- `HopChar1AC`: same as above but _after cursor_.
|
||||
- `HopChar2`: hint the current buffer by pressing two characters to select which ones to jump to.
|
||||
- `HopChar2BC`: same as above but _before cursor_.
|
||||
- `HopChar2AC`: same as above but _after cursor_.
|
||||
- `HopLine`: hint lines (first column).
|
||||
- `HopLineBC`: same as above but _before cursor_.
|
||||
- `HopLineAC`: same as above but _after cursor_.
|
||||
- `HopLineStart`: hint lines (first non whitespace character).
|
||||
- `HopLineStartBC`: same asbove but _before_cursor_.
|
||||
- `HopLineStartAC`: same asbove but _after cursor_.
|
||||
- `HopChar1Line`: same as `HopChar1` but applies only to the current line.
|
||||
- `HopChar1LineAC`: same asbove but _before_cursor_.
|
||||
- `HopChar1LineBC`: same asbove but _after cursor_.
|
||||
|
||||
All those commands need to be re-implemented with the new `JumpTargetGenerator` design. All the current commands are
|
||||
based on scanning line-by-line the currently visible part of the buffer, so we will want a function creating a
|
||||
`JumpTargetGenerator` that implements this logic. Its arguments should allow us to implement all of the commands above.
|
||||
The important thing to understand is that the `*BC` and `*AC` variations are actually the same mode but applied with the
|
||||
user configuration (i.e. `direction`). Restricting to the same line should probably also be a user-configuration option
|
||||
to allow using `HopWord` only on the current line, for instance.
|
||||
|
||||
## Part of the work that can be taken out of #123
|
||||
|
||||
Given all the work described here, here is a break down view of the PR:
|
||||
|
||||
- [5f93a87d](https://github.com/phaazon/hop.nvim/pull/123/commits/5f93a87d57c4926ceb1f71898c96e777c2ff33d6):
|
||||
refactoring / code hygiene. **Will pick**.
|
||||
- [bc449524](https://github.com/phaazon/hop.nvim/pull/123/commits/bc449524605317f48aff824f55e8a0e2ed40d87e): move
|
||||
`HintMode` to a new weird `constants.lua` module. This adds no value. **Will drop**.
|
||||
- [adbab40e](https://github.com/phaazon/hop.nvim/pull/123/commits/adbab40ef97f1516dd301bb7c9b9e66bc3638c39): move
|
||||
all the logic of getting the visible buffer part into a `get_window_context` function. This is interesting but will
|
||||
require a bit of fixup work. **Will pick**.
|
||||
- [30d35b94](https://github.com/phaazon/hop.nvim/pull/123/commits/30d35b9479a42feaca35707c5176d47ce7a6e58d): introduce
|
||||
the concept of _aggregate_ to refactor the process of mapping jump targets (`indirect_hints`) with permutations to
|
||||
yield `hints`. I’m not a huge fan of the terminology, it doesn’t really convey what the aggregates are for. We need to
|
||||
change the terminology, but I will probably use that too. **Will pick**.
|
||||
- [e8c84c11](https://github.com/phaazon/hop.nvim/pull/123/commits/e8c84c11a2a085d8df108772ad0c6d41571a3aa1): move out
|
||||
associating permutations to jump targets. I’m mostly okay with this, but we need to change the name of the function so
|
||||
that it’s clear that the function now only creates jump targets, and another function adds the permutations to it.
|
||||
This function still uses the _aggregate_ concept introduced in the previous commit, for which we need to change the
|
||||
terminology. **Will pick**.
|
||||
- [c8fa480c](https://github.com/phaazon/hop.nvim/pull/123/commits/c8fa480ce593296dcf49dd0ff7401ed930bafcf1): change the
|
||||
semantics of hint modes to use `get_hints` instead of scanning lines by lines. We need to change the name so that it’s
|
||||
something like `get_jump_targets` instead. **Will pick**.
|
||||
- [9fc5d517](https://github.com/phaazon/hop.nvim/pull/123/commits/9fc5d51785a2819ecc2f7ce72fbaf3f8ad092ff9): remove
|
||||
length from the output of the function creating the hints. This commit might be dangerous, because the reason for
|
||||
having the length is important (it allows to ensure we cut currently the hints if they overlap / are at the end of a
|
||||
`wrap` window). **Not sure, probably will drop**.
|
||||
- [8a482a98](https://github.com/phaazon/hop.nvim/pull/123/commits/8a482a98041433fb924807a0b53f231327db90c2): replace the
|
||||
concept of _aggregate_ with _hint list_ (should be _jump target list_) and general refactoring. Not sure whether I
|
||||
will use this as the rest of the design will probably be left to implement regarding this current document. **Not
|
||||
sure, probably will pick**.
|
||||
- [82117eab](https://github.com/phaazon/hop.nvim/pull/123/commits/82117eab12f6f3278927667623ed4c267608a22e):
|
||||
documentation enhancement. **Will pick**.
|
||||
|
||||
The actual commits that were picked were not the ones described just above because decisions to refactor / renames
|
||||
things in a different way that matches more the overall design.
|
||||
|
||||
# Alternatives
|
||||
|
||||
Besides merging code upstream, there is no real alternative to this problem. We have to expose the jump target retreival
|
||||
on the public API so that people can extend Hop the way they want.
|
||||
|
||||
# Rationale
|
||||
|
||||
This redesign should allow to people to extend Hop on their side without having to merge code upstream. Different
|
||||
motivations exist, among people wanting to use Treesitter-based motions, which will _not_ end up upstream as I think
|
||||
it’s not really interesting / useful / out of scope, because hints are more a visual thing than a semantics thing;
|
||||
people wanting to use Hop in menus / interfaces, etc.
|
||||
|
||||
The other good point of this redesign is that we still support the _user configuration_ that is very important to the
|
||||
author ([@phaazon](https://github.com/phaazon)).
|
||||
|
||||
Another interesting aspects of this redesign is to allow people to create Lua plugins that might end up upstream if
|
||||
needed, but people wanting to create « extensions » plugins will not have to depend on the upstream to have it possible.
|
||||
This is important for two reasons:
|
||||
|
||||
- People can implement their workflow.
|
||||
- Hop can remain small and thus is much easier to maintain.
|
||||
|
||||
In order to help plugin authors to write their Hop extension, we will have to keep the documentation updated and
|
||||
top of the notch.
|
||||
|
||||
# Future work
|
||||
|
||||
An important matter while I was writing this design doc: because we are probably going to have people implementing
|
||||
extensions, they are going to use the public API of Hop, which will probably have deprecations / breaking-change at some
|
||||
point. I have parallel work on going (i.e. [poesie.nvim](https://github.com/phaazon/poesie.nvim)) but ultimately, I
|
||||
really want a SemVer API, so that plugin authors don’t have to worry too much about this. It’s more about the end-users:
|
||||
I really dislike it when I update something and it breaks because of a deprecation somewhere. People have their lives,
|
||||
they won’t update immediately, so we need SemVer to prevent that kind of problems from occurring. We need to keep that
|
||||
in mind for later because this Hop extension thing is going to a perfect example about why we need this. I’m explicitely
|
||||
pinging [@mjlbach](https://github.com/mjlbach) as we mentioned that quite a few times lately, and to show that Hop is
|
||||
going to _really_ need this. I might probably implement a convention in poesie and givin what the core team want to do
|
||||
regarding plugins (whether their version will be checked in the core or whether poesie / something else should be
|
||||
responsible for it).
|
1
config/plugins/hop.vim
Normal file
1
config/plugins/hop.vim
Normal file
@ -0,0 +1 @@
|
||||
lua require('hop').setup()
|
@ -1977,6 +1977,29 @@ automatically. and the format is:
|
||||
>
|
||||
autosave_location/path+=to+=filename.ext.backup
|
||||
<
|
||||
5. `enable_hop`: by default, spacevim use easymotion plugin. and if you are
|
||||
using neovim 0.6.0 or above, hop.nvim will be enabled. You can disabled this
|
||||
plugin and still using easymotion.
|
||||
|
||||
KEY BINDINGS
|
||||
|
||||
The `edit` layer also provides many key bindings:
|
||||
>
|
||||
key binding description
|
||||
SPC x c count in the selection region
|
||||
<
|
||||
|
||||
The following key binding is to jump to targets. The default plugin is
|
||||
`easymotion`, and if you are using neovim 0.6.0 or above. The `hop.nvim` will
|
||||
be used.
|
||||
>
|
||||
key binding description
|
||||
SPC j j jump or select a character
|
||||
SPC j J jump to suite of two characters
|
||||
SPC j l jump or select to a line
|
||||
SPC j w jump to a word
|
||||
SPC j u jump to a url
|
||||
<
|
||||
|
||||
==============================================================================
|
||||
EXPRFOLD *SpaceVim-layers-exprfold*
|
||||
|
Loading…
Reference in New Issue
Block a user