Initial commit

This commit is contained in:
Jake Bauer 2022-11-30 18:02:51 -05:00
commit c33b970dd5
62 changed files with 38632 additions and 0 deletions

47
.Xresources-dark Normal file
View File

@ -0,0 +1,47 @@
#ifdef HIDPI
Xcursor.size: 48
*.font: xft:IBM Plex Mono:size=18,xft:noto emoji:size=18
xft.dpi: 164
*.internalBorder: 28
#else
*.font: xft:IBM Plex Mono:size=10,xft:noto emoji:size=10
*.internalBorder: 12
#endif
Xcursor.theme: Paper
*.termName: xterm-256color
*.loginShell: true
*.scrollTtyOutput: false
*.scrollBar: false
*.saveLines: 20000
*.selectToClipboard: true
*.trimSelection: true
! special
*.foreground: #DDDDDD
*.background: #111111
*.cursorColor: #DDDDDD
! black
*.color0: #121212
*.color8: #454545
! red
*.color1: #994444
*.color9: #CC7777
! green
*.color2: #449944
*.color10: #77CC77
! yellow
*.color3: #999444
*.color11: #CCC777
! blue
*.color4: #336699
*.color12: #6699CC
! magenta
*.color5: #994499
*.color13: #CC77CC
! cyan
*.color6: #449999
*.color14: #77CCCC
! white
*.color7: #DDDDDD
*.color15: #FFFFFF

47
.Xresources-light Normal file
View File

@ -0,0 +1,47 @@
#ifdef HIDPI
Xcursor.size: 48
*.font: xft:IBM Plex Mono:size=18
xft.dpi: 164
*.internalBorder: 28
#else
*.font: xft:IBM Plex Mono:size=10
*.internalBorder: 12
#endif
Xcursor.theme: Paper
*.termName: xterm-256color
*.loginShell: true
*.scrollTtyOutput: false
*.scrollBar: false
*.saveLines: 20000
*.selectToClipboard: true
*.trimSelection: true
! special
*.foreground: #000000
*.background: #eeeeee
*.cursorColor: #000000
! black
*.color0: #000000
*.color8: #686868
! red
*.color1: #b21818
*.color9: #cd2e2b
! green
*.color2: #106c14
*.color10: #327b36
! yellow
*.color3: #91632a
*.color11: #ddaa55
! blue
*.color4: #3232dd
*.color12: #585ce0
! magenta
*.color5: #b218b2
*.color13: #b81fc3
! cyan
*.color6: #0d5861
*.color14: #117880
! white
*.color7: #b2b2b2
*.color15: #ffffff

30
.config/fish/config.fish Normal file
View File

@ -0,0 +1,30 @@
# Fish shell configuration
set PATH "$PATH:$HOME/.local/bin"
set EDITOR "nvim"
if status is-interactive
# Theming
fish_config theme choose "Mono Lace"
set fish_color_cwd black --bold
set fish_greeting ""
# Functions
function fish_prompt -d "The almighty shell prompt"
printf '[%s@%s %s%s%s]$ ' $USER $hostname \
(set_color $fish_color_cwd) (prompt_pwd) (set_color normal)
end
function pastesrv -d "Upload a file and share a link"
ssh cerberus "cat > /var/www/ftp.paritybit.ca/paste/$1"
echo "https://ftp.paritybit.ca/paste/$1" | tee -a "$HOME/.pastesrvhist"
end
# Aliases
alias ls='ls -lF'
alias rm='rm -iv'
alias cp='cp -iv'
alias mv='mv -iv'
alias vi='nvim'
alias open='xdg-open'
alias pubip='curl -s https://ifconfig.me; printf "\n"'
end

32
.config/git/config Normal file
View File

@ -0,0 +1,32 @@
[core]
editor = nvim
autocrlf = input
[user]
email = jbauer@paritybit.ca
name = Jake Bauer
[sendemail]
smtpserver = mail.paritybit.ca
smtpuser = jbauer
smtpencryption = tls
smtpserverport = 587
annotate = yes
[alias]
co = checkout
sw = switch
br = branch
r = restore
c = commit
cm = commit -m
s = status
p = push
pu = pull
u = unstage
a = add
hist = log --graph --topo-order --date=short --pretty=format:'%C(auto)%h %C(reset)%<(52)%s %C(bold blue)%<(30)%ae %C(reset)%C(green)%cr (%cd) %C(auto)%d'
[pull]
rebase = true
[push]
default = simple
followTags = true
[init]
defaultBranch = release

158
.config/nvim/init.vim Normal file
View File

@ -0,0 +1,158 @@
"Plugins Used:
" vim-monochrome — (colorscheme)
" vim-commentary — (shortcuts for (un)commenting lines/blocks of code)
" vim-fastline — (custom status bar)
" vim-surround — (quickly surround a selection with brackets/quotes/etc)
" gemini-vim-synatx — (syntax highlighting for gemini)
" General Settings
" Leader key
let mapleader = ","
" Leap.nvim
lua require('leap').add_default_mappings()
" Syntax Highlighting and Colours
syntax on
colorscheme monochrome-light
" Show Matching Parens/Brackets
set showmatch
" Disable folding by default (but use Tab to expand folds if they exist)
set nofoldenable
nnoremap <Tab> za
" Shortcut to open the file underneath the cursor that mimics the 'gf' command
:noremap <leader>gf :e <cfile><cr>
" Enable Filetype Plugins/Indentation
filetype plugin indent on
" Make common typos behave as their lowercase counterparts
cabbr W w
cabbr Q q
cabbr E e
" Convenient shortcut for :center
nnoremap cc :center<CR>
" Longer History
set history=100
" Ensure Externally Modified Files Stay Up To Date
set autoread
au FocusGained,BufEnter * :checktime
" Before Writing to File, Remove Trailing White Spaces
autocmd BufWritePre * :%s/\s\+$//e
" Undo File to Allow Undoing From Previous Sessions
set undofile
set undodir=~/.local/share/nvim/undo/
" Set the Location of the viminfo File
set viminfo+=n~/.local/share/nvim/viminfo
" Move visually selected lines up and down
xnoremap K :move '<-2<CR>gv-gv
xnoremap J :move '>+1<CR>gv-gv
" Toggle display of special chars for tab, eol, space
nmap <leader>l :set list!<CR>
set listchars=tab:▸\ ,eol,space:.
" Highlight entire line that cursor is on
set cursorline
" Make < and > shifts retain selection
vnoremap < <gv
vnoremap > >gv
" Line Number Settings
set number relativenumber
" Courtesy: Jeff Kreeftmeijer @ jeffkreeftmeijer.com/vim-number/
augroup numbertoggle
autocmd!
autocmd BufEnter,FocusGained,InsertLeave * set relativenumber
autocmd BufLeave,FocusLost,InsertEnter * set norelativenumber
augroup END
" Indentation Settings
set smartindent
set autoindent
set textwidth=80
set colorcolumn=+1
" Toggle between 4 spaces and pure tab indentation styles
func! ToggleIndentStyle()
if &expandtab == 1
set noexpandtab softtabstop& shiftwidth&
echom "Switched to: Indent with tabs."
else
set expandtab softtabstop=4 shiftwidth=4
echom "Switched to: Indent with 4 spaces."
endif
endfu
noremap <C-_> :call ToggleIndentStyle()<CR>
" Search Settings
set hlsearch
set ignorecase
set smartcase
nnoremap \\ :noh<CR>
" Printing
set printfont=IBM\ Plex\ Mono\ Text:h10
command! -range=% HardcopyPdf <line1>,<line2> hardcopy > %.ps | !ps2pdf %.ps && rm %.ps && echo "Created: %.pdf"
" Buffer Settings and Fuzzy Search
set hidden
set splitright splitbelow
set wildmenu
set wildcharm=<C-Z>
set path+=**
nnoremap <C-N> :bn<CR>
nnoremap <C-P> :bp<CR>
nnoremap <C-H> <C-W><C-H>
nnoremap <C-J> <C-W><C-J>
nnoremap <C-K> <C-W><C-K>
nnoremap <C-L> <C-W><C-L>
nnoremap <Up> :resize +2<CR>
nnoremap <Down> :resize -2<CR>
nnoremap <Left> :vertical resize +2<CR>
nnoremap <Right> :vertical resize -2<CR>
" Enable Spell Check
" Thesaurus: gutenberg.org/files/3202/files/mthesaur.txt
" SpellFile: ftp.vim.org/pub/vim/runtime/spell/en/
func! SpellCheckToggle()
set thesaurus+="~/.config/nvim/mthesaurus.txt"
set spellfile="~/.config/nvim/spell_en_CA.diff"
set complete+=s
setlocal spell! spelllang=en_ca
endfu
noremap <silent> <C-S> :call SpellCheckToggle()<CR>
func! WordProcessor()
map j gj
map k gk
setl formatoptions=1
setl noexpandtab
setl wrap
setl linebreak
setl textwidth=0
setl nosmartindent
call SpellCheckToggle()
endfu
noremap <silent> <C-W> :call WordProcessor()<CR>
" Preferences for File Formats
autocmd FileType gmi call WordProcessor()
autocmd FileType gitcommit call SpellCheckToggle()
autocmd FileType gitcommit setl tw=72
autocmd FileType mail call SpellCheckToggle()
autocmd FileType mail setl tw=72
autocmd FileType markdown call WordProcessor()
autocmd FileType python setl expandtab tabstop=4 shiftwidth=4
autocmd FileType vim setl foldmethod=marker

30260
.config/nvim/mthesaurus.txt Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View File

@ -0,0 +1,3 @@
au BufRead,BufNewFile *.gmi,*.gemini set filetype=gmi
" Gemini protocol syntax for text/gemini files (*.gmi)
" will also work for *.gemini files

View File

@ -0,0 +1,48 @@
" Vim syntax file
" " Language: gemini
" " Maintainer: sloum < sloum AT rawtext.club >
" " Latest Revision: 08 Jun 2020
"
" For version 5.x: Clear all syntax items
" For version 6.x: Quit when a syntax file was already loaded
if version < 600
syntax clear
elseif exists("b:current_syntax")
finish
endif
" Handle monospaced blocks
syn region gmiMono start=/^```/ end=/^```/
" Handle between one and three heading levels
syn match gmiHeader /^#\{1,3}[^#].*$/
" Start a link line
syn match gmiLinkStart /^=>/ nextgroup=gmiLinkUrl skipwhite
" An extremely naive way of handling the URL portion of the link line
" This is left naive in a deliberate attempt to be unambiguous about
" what part of a link line gemini considers to be the URL, regardless
" of whether or not it is a valid URL
syn match gmiLinkUrl /\S\+/ contained nextgroup=gmiLinkTitle skipwhite
" Skipping whitespace from the URL match all text, including whitespace,
" until the end of the line
syn match gmiLinkTitle /.*$/ contained
" Handle list items
syn match gmiListItem /^\* .*$/
" Handle quotes
syn match gmiQuoteLine /^>.*/
let b:current_syntax = "gmi"
hi def link gmiMono Special
hi def link gmiHeader Constant
hi def link gmiLinkStart Todo
hi def link gmiLinkUrl Underlined
hi def link gmiLinkTitle String
hi def link gmiListItem Identifier
hi def link gmiQuoteLine Comment

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2022 György Andorka
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,745 @@
<img align="left" width="150" height="85" src="../media/kangaroo.png?raw=true">
# leap.nvim
Leap is a general-purpose motion plugin for [Neovim](https://neovim.io/), with
the ultimate goal of establishing a new standard interface for moving around in
the visible area in Vim-like modal editors.
![showcase](../media/showcase.gif?raw=true)
### How to use it (TL;DR)
Leap allows you to jump to any positions in the visible editor area by entering
a 2-character search pattern, and then potentially a "label" character to pick
your target from multiple matches, similar to
[Sneak](https://github.com/justinmk/vim-sneak). The novel idea in Leap is its
"clairvoyant" ability: you get a **live preview** of the target labels - by
mapping possible futures, Leap can show you which key(s) you will need to press
_before_ you actually need to do that.
- Initiate the search in the forward (`s`) or backward (`S`) direction, or in
the other windows (`gs`).
- Start typing a 2-character pattern (`{c1}{c2}`).
- After typing the first character, you see "labels" appearing next to some of
the `{c1}{?}` pairs. You cannot _use_ the labels yet.
- Enter `{c2}`. If the pair was not labeled, then voilà, you're already there.
No need to be bothered by remaining labels, just continue editing.
- Else: select a label. In case of multiple groups, first switch to the desired
one, using `<space>` (step back with `<tab>`, if needed).
### Why is this method cool?
It is **ridiculously fast**: not counting the trigger key, leaping to literally
anywhere on the screen rarely takes more than 3 keystrokes in total, that can be
typed in one go. Often 2 is enough.
At the same time, it **reduces mental effort to almost zero**:
- You _don't have to weigh alternatives_: a single universal motion type can be
used in all non-trivial situations.
- You _don't have to compose in your head_: one command achieves one logical
movement.
- You _don't have to be aware of the context_: the eyes can keep focusing on the
target the whole time.
- You _don't have to make decisions on the fly_: the sequence you should enter
is determined from the very beginning.
- You _don't have to pause in the middle_: if typing at a moderate speed, at
each step you already know what the immediate next keypress should be, and
your mind can process the rest in the background.
### Down the kangaroo hole
This was just a teaser - mind that Leap is extremely flexible, and offers much
more beyond the defaults: you can configure it to resemble other similar
plugins, extend it with custom targeting methods, and even do arbitrary actions
with the selected target(s) - read on to dig deeper.
- [Design considerations in detail](#design-considerations-in-detail)
- [Background](#background)
- [Status](#status)
- [FAQ](#faq)
- [Getting started](#getting-started)
- [Usage](#usage)
- [Configuration](#configuration)
- [Extending Leap](#extending-leap)
- [Plugins using Leap](#plugins-using-leap)
## Design considerations in detail
### The ideal
Premise: jumping from point A to B on the screen should not be some [exciting
puzzle](https://www.vimgolf.com/), for which you should train yourself; it
should be a _non-issue_. An ideal keyboard-driven interface would impose almost
**no more cognitive burden than using a mouse**, without the constant
context-switching required by the latter.
That is, **you do not want to think about**
- **the context**: it should be enough to look at the target, and nothing else
(↔ vanilla Vim motion combinations using relative line numbers and/or repeats)
- **the command**: we need one fundamental targeting method that can bring you
anywhere: a "jetpack" instead of a "railway network" (↔ EasyMotion and its
derivatives)
- **the steps**: the motion should be atomic (↔ Vim motion combos), and you
should be able to type the sequence in one go, without having to make
semi-conscious decisions on the fly ("Shall I start a `<C-g>` streak, or try
one more input character?"), or instantly react to events (labels appearing).
All the while using **as few keystrokes as possible**, and getting distracted by
**as little incidental visual noise as possible**.
### How do we measure up?
It is obviously impossible to achieve all of the above at the same time, without
some trade-offs at least; but Leap comes pretty close, occupying a sweet spot in
the design space.
The one-step shift between perception and action cuts the Gordian knot: on the
one hand, the input sequence can be extended dynamically, to scale to any number
of targets, by adding new groups you can switch to; however, thanks to
ahead-of-time labeling, it still behaves as if it would be an already known
pattern, that you just have to type out. Leaping is like incremental search on
some kind of autopilot, where you know it in advance when to finish.
Fortunately, a 2-character search pattern - the shortest one with which we can
play this trick - is also long enough to sufficiently narrow down the matches in
the vast majority of cases. It is very rare that you should type more than 3
characters altogether to reach a given target.
### Auxiliary principles
- Optimize for the common case (not the pathological): a good example of this is
the Sneak-like "one-character labels and multiple groups"-approach (instead of
multi-character labels and no groups, EasyMotion-style), which can become
awkward for, say, 200 targets, but usually more comfortable, eliminates all
kinds of edge cases and implementation problems, and allows for features like
[multiselect](#extending-leap).
- [Sharpen the saw](http://vimcasts.org/blog/2012/08/on-sharpening-the-saw/):
build on Vim's native interface, and aim for synergy as much as possible. The
plugin supports macros, operators, dot-repeat (`.`), inclusive/exclusive
toggle (`v`), multibyte text and
[keymaps](http://vimdoc.sourceforge.net/htmldoc/mbyte.html#mbyte-keymap)
(language mappings), autocommands via `User` events, among others, and intends
to continuously improve in this respect.
- [Mechanisms instead of
policies](https://cacm.acm.org/magazines/2018/11/232214-a-look-at-the-design-of-lua/fulltext)
(or "be opinionated, but not stubborn"): aim for a small, maintainable core,
with reasonable defaults; at the same time, keep the plugin flexible and
future-proof via [extension points](#extending-leap).
## Background
Leap is a reboot of [Lightspeed](https://github.com/ggandor/lightspeed.nvim); a
streamlined but in many respects enhanced version of its ancestor. Compared to
Lightspeed, Leap:
- gets rid of some gimmicks with a low benefit/cost ratio (like Lightspeed's
"shortcut" labels), but works the same way in the common case; all the really
important features are there
- has a smaller and simpler visual footprint; it feels like using Sneak
- is more flexible and extensible; it can be used as an engine for selecting
arbitrary targets, and performing arbitrary actions on them
## Status
The plugin is not fully stable yet, but don't let that stop you - the usage
basics are extremely unlikely to change. To follow breaking changes, subscribe
to the corresponding [issue](https://github.com/ggandor/leap.nvim/issues/18).
## FAQ
<details>
<summary>Bidirectional search</summary>
```lua
-- Beware that the trade-off in this mode is that you always have to
-- select a label, as there is no automatic jump to the first target (it
-- would be very confusing if the cursor would suddenly jump in the
-- opposite direction than your goal). Former vim-sneak users will know
-- how awesome a feature that is. I really suggest trying out the plugin
-- with the defaults for a while first.
-- An additional disadvantage is that operations cannot be dot-repeated
-- if the search is non-directional.
-- Now that you have carefully considered my wise advice above, I'll
-- tell you the simple trick: just initiate multi-window mode with the
-- current window as the only target.
require('leap').leap { target_windows = { vim.fn.win_getid() } }
```
</details>
<details>
<summary>Search in all windows</summary>
```lua
-- The same caveats as above about bidirectional search apply here.
require('leap').leap { target_windows = vim.tbl_filter(
function (win) return vim.api.nvim_win_get_config(win).focusable end,
vim.api.nvim_tabpage_list_wins(0)
)}
```
</details>
<details>
<summary>Enhanced f/t motions</summary>
Check flit.nvim, an extension plugin for Leap.
</details>
<details>
<summary>Linewise motions</summary>
See the "Extending Leap" section below for an example snippet.
</details>
<details>
<summary>Other supernatural powers besides clairvoyance?</summary>
You might be interested in leap-spooky.nvim.
</details>
<details>
<summary>Disable auto-jumping to the first match</summary>
```lua
require('leap').opts.safe_labels = {}
```
</details>
<details>
<summary>Greying out the search area</summary>
```lua
vim.api.nvim_set_hl(0, 'LeapBackdrop', { link = 'Comment' })
```
</details>
<details>
<summary>Lightspeed-style highlighting</summary>
```lua
-- The below settings make Leap's highlighting a bit closer to what you've been
-- used to in Lightspeed.
vim.api.nvim_set_hl(0, 'LeapBackdrop', { link = 'Comment' })
vim.api.nvim_set_hl(0, 'LeapMatch', {
fg = 'white', -- for light themes, set to 'black' or similar
bold = true,
nocombine = true,
})
require('leap').opts.highlight_unlabeled_phase_one_targets = true
```
</details>
<details>
<summary>How to live without `s`/`S`/`x`/`X`?</summary>
All of them have aliases or obvious equivalents:
- `s` = `cl`
- `S` = `cc`
- `v_s` = `v_c`
- `v_S` = `Vc`, unless already in linewise mode (then = `v_c`)
- `v_x` = `v_d`
- `v_X` -> `vnoremap D X`, and use `$D` for vanilla `v_b_D` behaviour
</details>
<details>
<summary>I am too used to using `x` instead of `d` in Visual mode</summary>
```lua
-- Getting used to `d` shouldn't take long - after all, it is more comfortable
-- than `x`, and even has a better mnemonic.
-- If you still desperately want your old `x` back, then just delete these
-- mappings set by Leap:
vim.keymap.del({'x', 'o'}, 'x')
vim.keymap.del({'x', 'o'}, 'X')
-- To set alternative keys for "exclusive" selection:
vim.keymap.set({'x', 'o'}, <some-other-key>, '<Plug>(leap-forward-till)')
vim.keymap.set({'x', 'o'}, <some-other-key>, '<Plug>(leap-backward-till)')
```
</details>
<details>
<summary>Was the name inspired by Jef Raskin's Leap?</summary>
To paraphrase Steve Jobs, I wish it were, but it is a coincidence. "Leap" is
just another synonym for "jump", that happens to rhyme with Sneak. That said, in
some respects you can indeed think of leap.nvim as a spiritual successor to
Raskin's work, and thus the name as a little tribute to the great pioneer of
interface design, even though embracing the modal paradigm is a fundamental
difference in our approach.
</details>
## Getting started
### Requirements
* Neovim >= 0.7.0
### Dependencies
* For the moment, [repeat.vim](https://github.com/tpope/vim-repeat) is required
for dot-repeats (`.`) to work as intended.
### Installation
Use your preferred plugin manager. No extra steps needed besides defining
keybindings - to use the default ones, put the following into your config:
`require('leap').add_default_mappings()` (init.lua)
`lua require('leap').add_default_mappings()` (init.vim)
## Usage
Without further ado, let's cut to the chase, and learn by doing.
([Permalink](https://github.com/neovim/neovim/blob/8215c05945054755b2c3cadae198894372dbfe0f/src/nvim/window.c#L1078)
to the file, if you want to follow along.)
The search is invoked with `s` in the forward direction, and `S` in the backward
direction. Let's target some word containing `ol`. After entering the letter
`o`, the plugin processes all character pairs starting with it, and from here
on, you have all the visual information you need to reach your specific target.
![quick example 1](../media/quick_example_1.png?raw=true)
To reach an unlabeled match, just finish the pattern, i.e., type the second
character. For the rest, you also need to type the label character that is
displayed right next to the match. (Note: the highlighting of unlabeled matches
\- green underlined on the screenshots - is opt-in, turned on for clarity here.)
To continue with the example, type `l`.
If you aimed for the first match (in `oldwin->w_frame`), you are good to go,
just continue your work! The labels for the subsequent matches of `ol` remain
visible until the next keypress, but they are carefully chosen "safe" letters,
guaranteed to not interfere with your following editing command.
![quick example 2](../media/quick_example_2.png?raw=true)
If you aimed for some other match, then type the label, for example `u`, and
move on to that.
To show the last important feature, let's go back to the start position, and
target the struct member on the line `available = oldwin->w_frame->fr_height;`
near the bottom, using the pattern `fr`, by first pressing `s`, and then `f`:
![quick example 3](../media/quick_example_3.png?raw=true)
The blue labels indicate the "secondary" group of matches, where we start to
reuse the available labels for a given pair (`s`, `f`, `n`... again). You can
reach those by prefixing the label with `<space>`, that switches to the
subsequent match group. For example, to jump to the "blue" `j` target, you
should now press `r<space>j`.
In very rare cases, if the large number of matches cannot be covered even by two
label groups, you might need to press `<space>` multiple times, until you see
the target labeled, first with blue, and then, after one more `<space>`, green.
(Substitute "green" and "blue" with the actual colors in the current theme.)
### Cross-window motions
`gs` searches in all the other windows on the tab page. In this case, the
matches are sorted by their screen distance from the cursor, advancing in
concentric circles.
### Visual and Operator-pending mode
In these modes, there are two different pairs of directional motions available,
providing the necessary additional comfort and precision.
`s`/`S` are like their Normal-mode counterparts, except that `s` includes _the
whole match_ in the selection/operation (which might be considered the more
intuitive behaviour for these modes).
On the other hand, `x`/`X` are like `t`/`T` for `f`/`F` - they exclude the
matched pair:
```
abcd| |bcde
████e ← Sab sde → █████
ab██e ← Xab xde → ███de
```
Note that each of the forward motions are inclusive (`:h inclusive`), and the
`v` modifier (`:h o_v`) works as expected on them.
### Jumping to the end of the line and to empty lines
A character at the end of a line can be targeted by pressing `<space>` after it.
There is no special mechanism behind this: you can set aliases for the newline
character simply by defining a set in `opts.equivalence_classes` that contains
it. Empty lines can also be targeted, by pressing the newline alias twice
(`<space><space>` by default). This latter is a slightly more magical feature,
but fulfills the principle that any visible position you can move to with the
cursor should be reachable by Leap too.
### Repeat and traversal
Pressing `<enter>` after invoking any of Leap's motions sets the search pattern
to the previous one (`special_keys.repeat_search`).
After entering at least one input character, `<enter>` initiates "traversal"
mode, moving on to the next match on each keypress
(`special_keys.next_phase_one_target` or `special_keys.next_target`).
`<tab>` can revert the previous jump(s) in case you accidentally overshoot your
target (`special_keys.prev_target`).
#### Tips
- When repeating the previous search, you can immediately move on:
`s<enter><enter>...`
- Accepting the first match after one input character is a useful shortcut in
operator-pending mode (e.g. `ds{char}<enter>`).
- Traversal mode can be used as a substitute for normal-mode `f`/`t` motions.
`s{char}<enter><enter>` is the same as `f{char};`, but works over multiple
lines.
#### Notes
- If the safe label set is in use, the labels will remain available during the
whole time.
- For cross-window search, traversal mode is not supported (since there's no
direction to follow), but you can still accept the first (presumably only)
match with `<enter>`, even after one input.
### Resolving highlighting conflicts in phase one
If a directly reachable match covers a label, the match will get highlighted
(telling the user, "Label underneath!"), and the label will only be displayed
after the second input, that resolves the ambiguity. If a label gets positioned
over another label (this might occur before EOL or the window edge, when the
labels need to be shifted left), an "empty" label will be displayed until
entering the second input.
### Smart autojump
Leap automatically jumps to the first match if the remaining matches can be
covered by a limited set of "safe" target labels (keys you would not use right
after a jump), but stays in place, and switches to an extended, more comfortable
label set otherwise. For fine-tuning, see `:h leap-config`.
The rationale behind this is that the probability of the user aiming for the
very first target lessens with the number of targets; at the same time, the
probability of being able to reach the first target by other means (`www`, `f`,
etc.) increases. That is, staying in place in exchange for more comfortable
labels becomes a more and more acceptable trade-off.
Smart autojump gives the best of both worlds between Sneak (jumps
unconditionally, can only use a seriously limited label set) and Hop (labels
everything, always requires that one extra keystroke).
## Configuration
Below is a list of all configurable values in the `opts` table, with their
defaults. Set them like: `require('leap').opts.<key> = <value>`. For details on
the particular fields, see `:h leap-config`.
```Lua
max_phase_one_targets = nil
highlight_unlabeled_phase_one_targets = false
max_highlighted_traversal_targets = 10
case_sensitive = false
equivalence_classes = { ' \t\r\n', }
substitute_chars = {}
safe_labels = { 's', 'f', 'n', 'u', 't', . . . }
labels = { 's', 'f', 'n', 'j', 'k', . . . }
special_keys = {
repeat_search = '<enter>',
next_phase_one_target = '<enter>',
next_target = {'<enter>', ';'},
prev_target = {'<tab>', ','},
next_group = '<space>',
prev_group = '<tab>',
multi_accept = '<enter>',
multi_revert = '<backspace>',
}
```
### Mappings
You can add the default mappings (listed in `:h leap-default-mappings`) by
calling `require('leap').add_default_mappings()`. Note that the function will
check for conflicts with any custom mappings created by you or other plugins,
and will not overwrite them, unless explicitly told so (called with a `true`
argument).
To define alternative mappings, you can use the `<Plug>` keys listed in `:h
leap-custom-mappings`.
Note: To create custom motions, see [Extending Leap](#extending-leap) below.
### Highlight groups
For customizing the highlight colors, see `:h leap-highlight`.
In case you - as a user - are not happy with a certain colorscheme's
integration, you could force reloading the default settings by calling
`leap.init_highlight(true)`. The call can even be wrapped in an
autocommand to automatically re-init on every colorscheme change:
```Vim
autocmd ColorScheme * lua require('leap').init_highlight(true)
```
This can be tweaked further, you could e.g. check the actual colorscheme, and
only execute for certain ones, etc.
## Extending Leap
There is more to Leap than meets the eye. On a general level, you should think
of it as less of a motion plugin and more of an engine for selecting visible
targets on the screen (acquired by arbitrary means), and doing arbitrary things
with them.
There are lots of ways you can extend the plugin and bend it to your will, and
the combinations of them give you almost infinite possibilities.
### Calling `leap` with custom arguments
Instead of using the provided `<Plug>` keys, you can also call the `leap`
function directly. The following arguments are available:
`opts`: A table just like `leap.opts`, to override any default setting for the
specific call. E.g.:
```lua
require('leap').leap { opts = { labels = {} } }
```
`offset`: Where to land with the cursor compared to the target position (-1, 0,
1, 2).
`inclusive_op`: A flag indicating whether an operation should behave as
inclusive (`:h inclusive`).
`backward`: Search backward instead of forward in the current window.
`target_windows`: A list of windows (as `winid`s) to be searched.
<details>
<summary>Example: bidirectional and all-windows search</summary>
```lua
-- Bidirectional search in the current window is just a specific case of the
-- multi-window mode.
require('leap').leap { target_windows = { vim.fn.win_getid() } }
-- Searching in all windows (including the current one) on the tab page.
require('leap').leap { target_windows = vim.tbl_filter(
function (win) return vim.api.nvim_win_get_config(win).focusable end,
vim.api.nvim_tabpage_list_wins(0)
)}
```
</details>
This is where things start to become really interesting:
`targets`: Either a list of targets, or a function returning such a list. The
elements of the list are tables of arbitrary structure, with the only mandatory
field being `pos` - a (1,1)-indexed tuple; this is the position of the label,
and also the jump target, if there is no custom `action` provided. If you have
targets in multiple windows, you also need to provide a `wininfo` field for each
(`:h getwininfo()`). Targets can represent anything with a position, like
Tree-sitter nodes, etc.
<details>
<summary>Example: linewise motions</summary>
```lua
local function get_line_starts(winid)
local wininfo = vim.fn.getwininfo(winid)[1]
local cur_line = vim.fn.line('.')
-- Get targets.
local targets = {}
local lnum = wininfo.topline
while lnum <= wininfo.botline do
local fold_end = vim.fn.foldclosedend(lnum)
-- Skip folded ranges.
if fold_end ~= -1 then
lnum = fold_end + 1
else
if lnum ~= cur_line then table.insert(targets, { pos = { lnum, 1 } }) end
lnum = lnum + 1
end
end
-- Sort them by vertical screen distance from cursor.
local cur_screen_row = vim.fn.screenpos(winid, cur_line, 1)['row']
local function screen_rows_from_cur(t)
local t_screen_row = vim.fn.screenpos(winid, t.pos[1], t.pos[2])['row']
return math.abs(cur_screen_row - t_screen_row)
end
table.sort(targets, function (t1, t2)
return screen_rows_from_cur(t1) < screen_rows_from_cur(t2)
end)
if #targets >= 1 then
return targets
end
end
-- Usage:
local function leap_to_line()
winid = vim.api.nvim_get_current_win()
require('leap').leap {
target_windows = { winid },
targets = get_line_starts(winid),
}
end
```
</details>
`action`: A Lua function that will be executed by Leap in place of the jump. (You
could obviously implement some custom jump logic here too.) Its only argument is
either a target, or a list of targets (in multiselect mode).
<details>
<summary>Example: pick a window</summary>
```lua
function leap_to_window()
target_windows = require('leap.util').get_enterable_windows()
local targets = {}
for _, win in ipairs(target_windows) do
local wininfo = vim.fn.getwininfo(win)[1]
local pos = { wininfo.topline, 1 } -- top/left corner
table.insert(targets, { pos = pos, wininfo = wininfo })
end
require('leap').leap {
target_windows = target_windows,
targets = targets,
action = function (target)
vim.api.nvim_set_current_win(target.wininfo.winid)
end
}
end
```
</details>
`multiselect`: A flag allowing for selecting multiple targets for `action`. In
this mode, you can just start picking labels one after the other. You can revert
the most recent pick with `<backspace>`, and accept the selection with
`<enter>`.
<details>
<summary>Example: multi-cursor `:normal`</summary>
```lua
-- The following example showcases a custom action, using `multiselect`. We're
-- executing a `normal!` command at each selected position (this could be even
-- more useful if we'd pass in custom targets too).
function paranormal(targets)
-- Get the :normal sequence to be executed.
local input = vim.fn.input("normal! ")
if #input < 1 then return end
local ns = vim.api.nvim_create_namespace("")
-- Set an extmark as an anchor for each target, so that we can also execute
-- commands that modify the positions of other targets (insert/change/delete).
for _, target in ipairs(targets) do
local line, col = unpack(target.pos)
id = vim.api.nvim_buf_set_extmark(0, ns, line - 1, col - 1, {})
target.extmark_id = id
end
-- Jump to each extmark (anchored to the "moving" targets), and execute the
-- command sequence.
for _, target in ipairs(targets) do
local id = target.extmark_id
local pos = vim.api.nvim_buf_get_extmark_by_id(0, ns, id, {})
vim.fn.cursor(pos[1] + 1, pos[2] + 1)
vim.cmd("normal! " .. input)
end
-- Clean up the extmarks.
vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
end
-- Usage:
require('leap').leap {
target_windows = { vim.fn.win_getid() },
action = paranormal,
multiselect = true,
}
```
</details>
### Accessing the arguments passed to `leap`
The arguments of the current call are always available at runtime, in the
`state.args` table.
### Setting up autocommands
Leap triggers `User` events on entering/exiting (with patterns `LeapEnter` and
`LeapLeave`), so that you can set up autocommands, e.g. to change the values of
some editor options while the plugin is active (`:h leap-events`).
### Customizing specific invocations
Using autocommands together with the `args` table, you can customize practically
anything on a per-call basis - keep in mind that nothing prevents you from
passing arbitrary flags when calling `leap`:
```Lua
function my_custom_leap_func()
require('leap').leap { my_custom_flag = true, ... }
end
vim.api.nvim_create_autocmd('User', {
pattern = 'LeapEnter',
callback = function ()
if require('leap').state.args.my_custom_flag then
-- Implement some special logic here, that will only apply to
-- my_custom_leap_func() (e.g., change the style of the labels),
-- and clean up with an analogous `LeapLeave` autocommand.
end
end
})
```
## Plugins using Leap
- [leap-spooky.nvim](https://github.com/ggandor/leap-spooky.nvim) (remote
operations on text objects)
- [flit.nvim](https://github.com/ggandor/flit.nvim) (enhanced f/t motions)
- [leap-ast.nvim](https://github.com/ggandor/leap-ast.nvim) (Tree-sitter nodes)

View File

@ -0,0 +1,496 @@
*leap.txt*
For Neovim version 0.7.0
Last change: 2022 November 18
==============================================================================
CONTENTS *leap.nvim* *leap-contents*
Introduction ············································· |leap-introduction|
Usage ··························································· |leap-usage|
Configuration ·················································· |leap-config|
Default mappings ······································|leap-default-mappings|
Custom mappings ······································· |leap-custom-mappings|
Highlighting ················································ |leap-highlight|
Events ························································· |leap-events|
Extending Leap ············································· |leap-extensions|
==============================================================================
INTRODUCTION *leap-introduction*
Leap is a motion plugin that allows you to jump to any positions in the
visible editor area by entering a 2-character search pattern, and then
potentially a "label" character to pick your target from multiple matches,
similar to Justin M. Keyes' Sneak plugin. The novel idea in Leap is its
"clairvoyant" ability: you get a live preview of the target labels - by
mapping possible futures, Leap can show you which key(s) you will need to
press before you actually need to do that, resulting in a much more
frictionless experience.
==============================================================================
USAGE *leap-usage*
- Initiate the search in the forward (`s`) or backward (`S`) direction, or in
the other windows (`gs`).
- Start typing a 2-character pattern (`{c1}{c2}`).
- After typing the first character, you see "labels" appearing next to some of
the `{c1}{?}` pairs. You cannot _use_ the labels yet.
- Enter `{c2}`. If the pair was not labeled, then voilà, you're already there.
No need to be bothered by remaining labels, just continue editing.
- Else: select a label. In case of multiple groups, first switch to the desired
one, using `<space>` (step back with `<tab>`, if needed).
*leap-cross-window*
`gs` searches in all the other windows on the tab page. In this case, the
matches are sorted by their screen distance from the cursor, advancing in
concentric circles.
*leap-visual-mode* *leap-operator-pending-mode*
In Visual and Operator-pending mode, there are two different pairs of
directional motions available, providing the necessary additional comfort and
precision.
`s`/`S` are like their Normal-mode counterparts, except that `s` includes the
whole match in the selection/operation (which might be considered the more
intuitive behaviour for these modes).
On the other hand, `x`/`X` are like `t`/`T` for `f`/`F` - they exclude the
matched pair:
```
abcd| |bcde
████e ← Sab sde → █████
ab██e ← Xab xde → ███de
```
Note that each of the forward motions are |inclusive|, and |o_v| works as
expected on them.
*leap-match-newline*
A character at the end of a line can be targeted by pressing `<space>` after it.
There is no special mechanism behind this: you can set aliases for the
newline character simply by defining a set in |leap.opts.equivalence_classes|
that contains it. Empty lines can also be targeted, by pressing the newline
alias twice (`<space><space>` by default). This latter is a slightly more
magical feature, but fulfills the principle that any visible position you can
move to with the cursor should be reachable by Leap too.
*leap-repeat*
Pressing `<enter>` after invoking any of Leap's motions sets the search
pattern to the previous one (`special_keys.repeat_search`).
*leap-traversal*
After entering at least one input character, `<enter>` initiates "traversal"
mode, moving on to the next match on each keypress
(`special_keys.next_phase_one_target` or `special_keys.next_target`).
`<tab>` can revert the previous jump(s) in case you accidentally overshoot
your target (`special_keys.prev_target`).
Tips:
- When repeating the previous search, you can immediately move on:
`s|S <enter> <enter> ...`
- Accepting the first match after one input character is a useful shortcut in
operator-pending mode (e.g. `ds{char}<enter>`).
- Traversal mode can be used as a substitute for normal-mode `f`/`t` motions.
`s{char}<enter><enter>` is the same as `f{char};`, but works over multiple
lines.
Note: If |leap.opts.safe_labels| is in use, the labels will remain available
during the whole time.
Note: For cross-window search, traversal mode is not supported (since there's
no direction to follow), but you can still accept the first (presumably only)
match with `<enter>`, even after one input.
*leap-dot-repeat*
You can repeat change and delete operations with the `.` character, if
repeat.vim (https://github.com/tpope/vim-repeat) is installed.
==============================================================================
CONFIGURATION *leap-config* *leap.opts*
Below is the description of all configurable values in the `opts` table, with
their defaults.
Example configuration: >
local leap = require('leap')
leap.opts.case_sensitive = true
leap.opts.substitute_chars = { ['\r'] = '¬' }
leap.opts.special_keys.prev_target = { '<s-enter>', ',' }
<
Available options~
*leap.opts.max_phase_one_targets*
`max_phase_one_targets = nil`
By default, the plugin shows labels and/or highlights matches right after
the first input character. This option disables ahead-of-time displaying
of target beacons beyond a certain number of phase one targets (to
mitigate visual noise in extreme cases). Setting it to 0 disables
two-phase processing altogether.
*leap.opts.highlight_unlabeled_phase_one_targets*
`highlight_unlabeled_phase_one_targets = false`
Whether to highlight all unlabeled matches in phase one. (Matches covering
labels will get highlighted anyway.)
*leap.opts.max_highlighted_traversal_targets*
`max_highlighted_traversal_targets = 10`
Number of targets to be highlighted after the cursor in |leap-traversal|
mode (when there are no labels at all).
*leap.opts.case_sensitive*
`case_sensitive = false`
Whether to consider case in search patterns.
*leap.opts.equivalence_classes*
`equivalence_classes = { ' \t\r\n' }`
A character will match any other in its equivalence class. The sets can
either be defined as strings or tables.
Example: >
{
'\r\n',
')]}>',
'([{<',
{ '"', "'", '`' },
}
<
Note: Make sure to have a set containing `\n` if you want to be able to
target characters at the end of the line.
Note: Non-mutual aliases are not possible in Leap, for the same reason
that supporting |smartcase| is not possible: we would need to show two
different labels, corresponding to two different futures, at the same
time.
*leap.opts.substitute_chars*
`substitute_chars = {}`
The keys in this table will be substituted in labels and highlighted
matches by the given characters. This way special (e.g. whitespace)
characters can be made visible in matches, or even be used as labels.
Example: `{ ['\r'] = '¬' }`
*leap.opts.safe_labels*
`safe_labels` >
{ "s", "f", "n", "u", "t", "/",
"S", "F", "N", "L", "H", "M", "U", "G", "T", "?", "Z" }
<
When the number of matches does not exceed the number of these "safe"
labels plus one, the plugin jumps to the first match automatically after
entering the pattern. Obviously, for this purpose you should choose keys
that are unlikely to be used right after a jump!
Setting the list to `{}` effectively disables the autojump feature.
Note: Operator-pending mode ignores this, since we need to be able to
select the actual target before executing the operation.
*leap.opts.labels*
`labels` >
{ "s", "f", "n",
"j", "k", "l", "h", "o", "d", "w", "e", "m", "b",
"u", "y", "v", "r", "g", "t", "c", "x", "/", "z",
"S", "F", "N",
"J", "K", "L", "H", "O", "D", "W", "E", "M", "B",
"U", "Y", "V", "R", "G", "T", "C", "X", "?", "Z" }
<
Target labels to be used when there are more matches than the number of
safe labels plus one.
Setting the list to `{}` forces autojump to always be on (except for
Operator-pending mode, where it makes no sense). In this case, do not
forget to set `special_keys.next_group` to something "safe" too.
Heuristics behind the defaults:
- At least the first few labels should be the same on the two lists, since
those become muscle memory, and might be used automatically, a bit like
[count] values.
- Since the commands invoking the motions are mapped to left-hand keys by
default, we tend to prioritize right-hand keys to get a better balance
for the whole sequence on average.
*leap.opts.special_keys*
`special_keys` >
{
repeat_search = '<enter>',
next_phase_one_target = '<enter>',
next_target = {'<enter>', ';'},
prev_target = {'<tab>', ','},
next_group = '<space>',
prev_group = '<tab>',
multi_accept = '<enter>',
multi_revert = '<backspace>',
}
<
Keys captured by the plugin at runtime, to:
- repeat with the previous input after invocation
- jump to the next/previous match in traversal mode (|leap-traversal|)
- switch to the next/previous group of matches, when there are more
matches than available labels
- accept the selection, and deselect the last selected target in
|leap-multiselect| mode
==============================================================================
DEFAULT MAPPINGS *leap-default-mappings*
The defaults can be set by calling `require('leap').add_default_mappings()`.
Note that the function will check for conflicts with any custom mappings
created by you or other plugins, and will not overwite them, unless explicitly
told so (called with a `true` argument).
Trigger keys~
Normal mode
*leap_s*
s{char1}{char2} Jump forward to a labeled or [count]'th visible
occurrence of {char1}{char2}. The cursor is placed on
{char1}.
*leap_S*
S{char1}{char2} Jump backward to a labeled or [count]'th visible
occurrence of {char1}{char2}. The cursor is placed on
{char1}.
Visual and Operator-pending mode
*leap_v_s* *leap_o_s*
s{char1}{char2} Jump forward to a labeled or [count]'th visible
occurrence of {char1}{char2}. The cursor is
placed on {char2} |inclusive|.
*leap_v_S* *leap_o_S*
S{char1}{char2} Jump backward to a labeled or [count]'th visible
occurrence of {char1}{char2}. The cursor
is placed on {char1}.
*leap_v_x* *leap_o_x*
x{char1}{char2} Jump forward to a labeled or [count]'th visible
occurrence of {char1}{char2}. The cursor is
placed on the character preceding {char1}
|inclusive|.
Mnemonic: "exclusive (s)".
*leap_v_X* *leap_o_X*
X{char1}{char2} Jump backward to a labeled or [count]'th visible
occurrence of {char1}{char2}. The cursor
is placed on the character following {char2}.
Mnemonic: "eXclusive (S)".
All modes
*leap_gs*
gs{char1}{char2} Jump to a labeled occurrence of {char1}{char2} in one
of the other windows on the tab page. The cursor is
placed on {char1} |exclusive|.
Mnemonic: "global s".
Special keys~
<enter> Repeat search with the previous input (after
invocation); jump to the next match in
|leap-traversal| mode; accept the selection in
|leap-multiselect| mode.
<space> Substitute for an EOL character (after the first
input); shift to the next group of labeled matches
(after the second input).
<tab> Revert the previous jump (|leap-traversal| mode) or
the previous group shift.
<backspace> Deselect the last selected target in
|leap-multiselect| mode.
<esc> Exit Leap at any stage cleanly.
==============================================================================
CUSTOM MAPPINGS *leap-custom-mappings*
<Plug> keys are aliases for right hand sides of mappings - in our case, calls
to |leap.leap()|. If you are not familiar with the concept, see |<Plug>| and
|using-<Plug>|.
Available <Plug> keys (with their corresponding argument tables):
*<Plug>(leap-forward-to)*
arguments: `{}` in Normal mode, otherwise `{ offset = +1, inclusive_op = true }`
default mapping: `s`
*<Plug>(leap-forward-till)*
arguments: `{ offset = -1, inclusive_op = true }`
default mapping: `x` (Visual and Operator-pending mode only)
*<Plug>(leap-backward-to)*
arguments: `{ backward = true }`
default mapping: `S`
*<Plug>(leap-backward-till)*
arguments: `{ backward = true, offset = 2 }`
default mapping: `X` (Visual and Operator-pending mode only)
*<Plug>(leap-cross-window)*
arguments: `{ target_windows = require('leap.util').get_enterable_windows() }`
default mapping: `gs`
Example: `vim.keymap.set({'n', 'x', 'o'}, 'f', '<Plug>(leap-forward-to)')`
For creating custom motions with behaviours different from the above, see
|leap.leap()|.
The keys for repeating the search, and for switching between groups of matches
can be set via |leap.opts.special_keys|.
==============================================================================
HIGHLIGHTING *leap-highlight*
Leap uses the following highlight groups that you can configure to your own
liking (using |nvim_set_hl()|):
*LeapMatch*
Matches that can be reached directly, without having to use a label.
(By default, this is only used for traversal mode, or when a match
covers a label, indicating the conflict.)
*LeapLabelPrimary*
The character needed to be pressed to jump to the match position,
after the whole search pattern has been given. It appears once the
first input has been entered, right next to the pair.
*LeapLabelSecondary*
If the number of matches exceeds the available target labels, the next
group of labeled targets are shown with a different color. Those can
be reached by pressing `<space>` (`special_keys.next_group`)
before the label character.
*LeapLabelSelected*
Labels that are selected for the action in |leap-multiselect| mode.
*LeapBackdrop*
In some cases it might be useful or even necessary to apply certain
settings on the rest of the area, like disabling all `gui` attributes,
or adding a uniform grey foreground color, to make Leap matches and
labels more distinguishable. This group is not set by default.
Example: `vim.api.nvim_set_hl(0, 'LeapBackdrop', { fg = 'grey' })`
In order to preserve your custom settings after changing the colorscheme, you
might want to wrap them in a function, and define an autocommand like below.
(Leap does the same, but with |:hi-default|, so it will not overwrite yours.)
>
vim.api.nvim_create_autocmd('ColorScheme', {
callback = function ()
vim.api.nvim_set_hl(0, 'LeapMatch', { <def. map> })
-- etc.
end
})
==============================================================================
EVENTS *leap-events*
The |User| event is triggered with the following patterns on entering/exiting
Leap (not in traversal mode):
*LeapEnter*
*LeapLeave*
Example: >
vim.api.nvim_create_autocmd('User', {
pattern = 'LeapEnter',
command = 'nohlsearch',
})
vim.api.nvim_create_autocmd('User', {
pattern = 'LeapLeave',
command = 'let &hlsearch=&hlsearch',
})
<
==============================================================================
EXTENDING LEAP *leap-extensions*
There is more to Leap than meets the eye. On a general level, you should think
of it as less of a motion plugin and more of an engine for selecting visible
targets on the screen (acquired by arbitrary means), and doing arbitrary things
with them.
There are lots of ways you can extend the plugin and bend it to your will, and
the combinations of them give you almost infinite possibilities.
Instead of using the provided `<Plug>` keys, you can also call the `leap()`
function directly:
leap({opts}) *leap.leap()*
Entry point for all |leap.nvim| actions.
Parameters ~
{opts} Optional parameters.
• opts: A table just like |leap.opts|, to override any default
setting for the specific call.
E.g.: `require('leap').leap { opts = { labels = {} } }`
• offset: Where to land with the cursor compared to the target
position (-1, 0, 1, 2).
• inclusive_op: A flag indicating whether an operation should
behave as |inclusive|.
• backward: Search backward instead of forward in the current
window.
• target_windows: A list of windows (as |winid|s) to be
searched.
*leap-custom-targets*
• targets: Either a list of targets, or a function returning
such a list. The advantage of the latter is that the function
will be evaluated after |LeapEnter| (that is, after setting
temporary editor options, etc.), so that you can even prompt
the user for input while already "in" Leap.
The elements of the list are tables of arbitrary structure,
with the only mandatory field being `pos` - a (1,1)-indexed
tuple; this is the position of the label, and also the jump
target, if there is no custom `action` provided. If you have
targets in multiple windows, you also need to provide a
`wininfo` field for each (|getwininfo()|). Targets can
represent anything with a position, like Tree-sitter nodes,
etc.
*leap-custom-action*
• action: A Lua function that will be executed by Leap in place
of the jump. (You could obviously implement some custom jump
logic here too.) Its only argument is either a target, or a
list of targets (in multiselect mode).
*leap-multiselect*
• multiselect: A flag allowing for selecting multiple targets
for `action`. In this mode, you can just start picking labels
one after the other. You can revert the most recent pick with
`<backspace>` (`special_keys.multi_revert`), and accept the
selection with `<enter>` (`special_keys.multi_accept`).
*leap.state* *leap-runtime-args*
Accessing the arguments passed to leap() ~
The arguments of the current call are always available at runtime, in the
`state.args` table.
Using |leap-events| together with the `args` table, you can customize practically
anything on a per-call basis. Keep in mind that you can even pass arbitrary
flags when calling |leap()|:
>
function my_custom_leap_func()
require'leap'.leap { my_custom_flag = true, ... }
end
vim.api.nvim_create_autocmd('User', {
pattern = 'LeapEnter',
callback = function ()
if require'leap'.state.args.my_custom_flag then
-- Implement some special logic here, that will only apply to
-- my_custom_leap_func() (e.g., change the style of the labels),
-- and clean up with an analogous `LeapLeave` autocommand.
end
end
})
<
==============================================================================
vim:tw=78:ts=8:ft=help:norl:

View File

@ -0,0 +1,101 @@
(local util (require "leap.util"))
(local {: inc : dec} util)
(local api vim.api)
(local map vim.tbl_map)
(local M {:ns (api.nvim_create_namespace "")
:extmarks []
:group {:label-primary "LeapLabelPrimary"
:label-secondary "LeapLabelSecondary"
:label-selected "LeapLabelSelected"
:match "LeapMatch"
:backdrop "LeapBackdrop"}
:priority {:label 65535
:cursor 65534
:backdrop 65533}})
(fn M.cleanup [self affected-windows]
; Clear beacons & cursor.
(each [_ [bufnr id] (ipairs self.extmarks)]
(api.nvim_buf_del_extmark bufnr self.ns id))
(set self.extmarks [])
; Clear backdrop.
(when (pcall api.nvim_get_hl_by_name self.group.backdrop false) ; group exists?
(each [_ wininfo (ipairs affected-windows)]
(api.nvim_buf_clear_namespace
wininfo.bufnr self.ns (dec wininfo.topline) wininfo.botline))
; Safety measure for scrolloff > 0: we always clean up the current view too.
(api.nvim_buf_clear_namespace 0 self.ns
(dec (vim.fn.line "w0"))
(vim.fn.line "w$"))))
(fn M.apply-backdrop [self backward? ?target-windows]
(when (pcall api.nvim_get_hl_by_name self.group.backdrop false) ; group exists?
(if ?target-windows
(each [_ win (ipairs ?target-windows)]
(vim.highlight.range win.bufnr self.ns self.group.backdrop
[(dec win.topline) 0]
[(dec win.botline) -1]
{:priority self.priority.backdrop}))
(let [[curline curcol] (map dec [(vim.fn.line ".") (vim.fn.col ".")])
[win-top win-bot] [(dec (vim.fn.line "w0")) (dec (vim.fn.line "w$"))]
[start finish] (if backward?
[[win-top 0] [curline curcol]]
[[curline (inc curcol)] [win-bot -1]])]
; Expects 0,0-indexed args; `finish` is exclusive.
(vim.highlight.range 0 self.ns self.group.backdrop start finish
{:priority self.priority.backdrop})))))
(fn M.highlight-cursor [self ?pos]
"The cursor is down on the command line during `getchar`,
so we set a temporary highlight on it to see where we are."
(let [[line col &as pos] (or ?pos (util.get-cursor-pos))
; nil means the cursor is on an empty line.
ch-at-curpos (or (util.get-char-at pos {}) " ") ; get-char-at needs 1,1-idx
; (Ab)using extmarks even here, to be able to highlight the cursor on empty lines too.
id (api.nvim_buf_set_extmark 0 self.ns (dec line) (dec col)
{:virt_text [[ch-at-curpos :Cursor]]
:virt_text_pos "overlay"
:hl_mode "combine"
:priority self.priority.cursor})]
(table.insert self.extmarks [(api.nvim_get_current_buf) id])))
(fn M.init-highlight [self force?]
(let [bg vim.o.background
defaults {self.group.match {:fg (match bg
:light "#222222"
_ "#ccff88")