Kakoune: How to map "jump forward" and "jump back" in goto mode (e.g. map 'gp' to <c-i>) - kakoune

I use the dvorak keyboard, and I'd like to reduce strain from reaching for the control key when using <c-i> and <c-o> (to jump backward or forward). I think the ',' and 'p' keys are perfect when combined with the goto menu (e.g. when hitting 'g' first). (For you qwerty folks, that means key positions 'w', 'e', and 'r' on the keyboard, when hit from the goto menu would be jump back, jump to last edit, and jump forward). Thus…
Here is what I want:
1) :map global goto , '<c-o>' -docstring 'jump back' // Does not work
2) :map global goto p '<c-i>' -docstring 'jump forward' // Does not work
However, for inexplicable reasons, neither of these commands work. I tried a bunch of experiments and found a few more strange things:
Tried using user mode instead of goto mode (as stated here: https://github.com/mawww/kakoune/wiki/Implementing-user-mode).
3) :map global user , '<c-o>' -docstring 'jump back' // Works
4) :map global user p '<c-i>' -docstring 'jump forward' // Does not work
Tried various changes to the command string in the map command (back to goto mode)
5) :map global goto p 'd' -docstring 'delete the selection' // Does not work
6) :map global goto p '/d' -docstring 'delete the selection' // Works! (Why??)
7) :map global goto p '\d' -docstring 'delete the selection' // Works! (Why?!?)
8) :map global goto p '\<c-o>' -docstring 'jump back' // Works! (Why?!!?!!?)
9) :map global goto p '\<c-i>' -docstring 'jump forward' // Does not work (Why!???)
Thus in summary:
a) Why does #3 work, but #4 doesn't?
b) Why does adding a slash (of either type) in front of a command string (e.g. in #6, #7, #8) make various commands mapped to the goto mode work?
c) How can I get my intentions with #1 and #2 to work? (Technically #8 solves my intention for #1, but I still have no answer for how to do #2)

Answer to question A:
As mentioned here,
Beware of keys homonyms like <tab> vs <c-i> and <ret> vs <c-j>
and <c-m>
I got <c-i> to work by replacing it with <tab>, like this:
:map global user p '<tab>' -docstring 'jump forward'
Question B:
I was unable to figure out why goto mode is different from user mode, but adding a slash before the command does seem to fix all issues. Another method I found was to add <esc> as the first part of the action keys, which brings it back to normal mode before performing the rest of the keys.
Answer to Question C:
By combining the discoveries from questions A and B, we get the following working map commands:
:map global goto , '\<c-o>' -docstring 'jump back' // Works!
:map global goto p '\<tab>' -docstring 'jump forward' // Works!
...or...
:map global goto , '<esc><c-o>' -docstring 'jump back' // Works!
:map global goto p '<esc><tab>' -docstring 'jump forward' // Works!

Related

Jump to current function declaration from middle of function

Is there a way to jump to the signature of the function my cursor is currently in, then jump back to where I was?
For example, when I have a 1000 line function, where the prefix x + y: refers to line numbers, is there a way from me to jump from my cursor location at x + 555 to the signature at x + 0 then back to where I was at (x + 555):
x + 000: void theFn(int arg) {
x + ...: ...
x + 555: /// where my cursor starts
x + ...: ...
x + 999: }
And, yes, I couldn't agree with you more that there shouldn't be 1000 line functions.
Also, is there a way to automatically jump to the end of function without being at the opening bracket of the function?
Useful motions in such case are [[, ][ and <C-o>.
As we can read in help:
*[[*
[[ [count] sections backward or to the previous '{' in
the first column. |exclusive|
Note that |exclusive-linewise| often applies.
*][*
][ [count] sections forward or to the next '}' in the
first column. |exclusive|
Note that |exclusive-linewise| often applies.
*CTRL-O*
CTRL-O Go to [count] Older cursor position in jump list
(not a motion command).
{not available without the |+jumplist| feature}
In short:
[[ to got to the beginning
<C-o> to go back to previous place
][ to go to end
Those motions will have the desired effect only when braces are in the first column, but from your example seems like this requirement is not met.
In such case at the end of :h section we can read:
If your '{' or '}' are not in the first column, and you would like to use "[["
and "]]" anyway, try these mappings: >
:map [[ ?{<CR>w99[{
:map ][ /}<CR>b99]}
:map ]] j0[[%/{<CR>
:map [] k$][%?}<CR>
Unfortunately, Vim doesn't offer better solution as it doesn't parse syntax.
It may change though as Neovim experiments with Tree-sitter.
It also wouldn't be surprising if there was a plugin which provides better support for such motion.
Tagbar could fit this role:
Toggle Tagbar window
Switch to it
Cursor should be already over the current tag
Press enter
Toggle window
You are at beginning of the function
Use <C-o> to get back
I also once found and had in my config a mapping which could also be useful in such case:
nnoremap <Leader>gd ?\v%(%(if|while|for|switch)\_s*)#<!\([^)]*\)\_[^;(){}]*\zs\{

How to toggle window position in vim

To move a window around I can do:
ctrlw shiftH, J, K, or L
Is there a way to just toggle the position of the windows? For example, pressing it five times would do:
ctrlw shiftK
ctrlw shiftL
ctrlw shiftJ
ctrlw shiftH
ctrlw shiftK
Is there something like that in vim, or I have to specify the direction explicitly when repositioning the window?
It seems there's no such a thing in Vim (maybe some plugin exists, though). If there was, we would have found it described at :help window-moving.
On the other hand, you can create your own mapping to handle this. The following, for instance, works as you require:
nnoremap <C-W><C-X> :call NextPost()<CR>
let g:mydic = {0: 'K', 1: 'L', 2: 'J', 3: 'H'}
let g:nextPosIndex = -1
function! NextPost()
if g:nextPosIndex == 3
let g:nextPosIndex = 0
else
let g:nextPosIndex += 1
endif
execute "normal! \<C-W>" . g:mydic[g:nextPosIndex]
endfunction
Note that the counter g:nextPosIndex is never reset, so after a K movement happened on a window, if you move to another window, and then move it, it will L-move.¹
[1] Based on D. Ben Knoble's comment, this limitation seems to be easly removed by using window-local variable w:nextPosIndex instead of a global one, g:nextPosIndex.

Copy block of code with increasing index

I have the following function declaration:
function f1(s)
real f1,s
f1 = 1/s
end
I would like to copy the same block but with increasing function name, i.e. f1, f2, f3, ... in order to get this:
function f1(s)
real f1,s
f1 = 1/s
end
function f2(s)
real f2,s
f2 = 1/s
end
function f3(s)
real f3,s
f3 = 1/s
end
My present approach is to copy the block in visual mode, paste it several times and then rename the functions manually. I would like to know how to achieve this faster using Vim. Thanks.
You can always use a recording (see :h recording). Assuming you have a blank line before and after your function and no blank lines in between
// empty line here, cursor here
function f1(s)
real f1,s
f1 = 1/s
end
// empty line here
With the cursor on the empty line above, make a recording to any register you like. Here I'm using register c. Press qc then press y}}Pjw*Ne^Ane^Ane^A{ and exit with q.
Explanation
y} - yank next paragraph
} - move down one paragraph
P - put above this line
j - move one line done
w - move to next word
* - search for word under cursor ( this is the function name here)
N - search backwards ( we moved with * to get the pattern into the register )
e - go to end of word
^A - Ctrl a to increase the number
n - go to next match / search forward ( this is the function name )
e - go to end of word
^A - increase the number
n - go to next match / search forward
e - go to end of word
^A - increase the number
{ - move up one paragraph (same relative position as in the beginning, but at the inserted function f2 )
Now you can use #c to copy the function and increase all numbers. Prefix with the count you want, e.g. 5#c will copy the function 5 times and adjust the numbering.
In case you don't want to remember the string y}}Pjw*Ne^Ane^Ane^A{ you can paste it in the vim buffer. You will have to replace the ^A before yanking though. Delete ^A and when in insert mode press Ctrl va. ( If you are inside a screen session you will have to press Ctrl aa, this is CTRL-a and a)
With the cursor on the line in normal mode press "cY to yank it in register c.
Then you can replay it with #c.
This way you can also modify it or yank it to another register.
Use let #c=y}}Pjw*Ne^Ane^Ane^A{ in your .vimrc to have it always load to register c when starting vim.
I figured out the solution. It may seems complex but does not, cause you have just to copy this function to the clipboard and load the function with:
:#+
Below the function with additional features
fun! CopyAndIncrease()
try
let l:old_copy = getreg('0')
normal yip
let #0 = substitute(#0,'\d\+','\=submatch(0) + 1','g')
exec "normal }O\<Esc>p"
finally
call setreg('0', l:old_copy)
endtry
endfun
command! -nargs=0 CopyIncrease silent call CopyAndIncrease() | exec "normal \<Esc>"
let mapleader = ','
nnoremap <Leader>c :CopyIncrease<CR>
Now pressing ,c Now you can use the keybinding and commands defined in the function.
Function's explanation:
let l:old_copy = getreg('0') -> save copy register
normal yip -> copy block
let #0 ... increase numbers on copy register
exec "normal }O\<Esc>p" -> paste increased block

Vim - Scroll Below the End of the Document

When we edit the last few lines of a document in vim, those lines are displayed at the bottom part of the screen, which is a little uncomfortable for me. Is there a way to scroll below the end of the document, so that the bottom lines in the document can be displayed at the top of the screen? (Currently Sublime Text has such capability.)
I've done some searches, the closest answer I could find is to use "set scrolloff=10". But that is not what I am looking for. Since it doesn't display the bottom lines of the document at the top of the screen.
Thanks in advance!
In addition to #Kent's answer, zz would allow you to bring the current line to the middle of the screen, which in my opinion is more convenient to see the context of the current line of text/code.
Also, zb would bring the current line to the bottom of the screen, which may also help sometimes.
Is there a way to scroll below the end of the document, so that the
bottom lines in the document can be displayed at the top of the
screen?
If I understood your requirement right, zt (or z<cr>) can do that when your cursor on the last line (in fact works on any line, :h zt for details)
example:
In normal mode you can use CTRL-E to scroll down and CTRL-Y to scroll up without moving the position of the cursor (unless the cursor would get pushed off the screen). If you're at the end of the document pressing CTRL-E will scroll past the end until the last line is at the top of the screen. I tend to like this method better than zt or zz since I can see it scrolling rather having the screen just jump ahead.
There are some caveats. For example, CTRL-Y when using the Windows key bindings is mapped to redo. Check out :help scrolling for more info.
In addition to set scrolloff=10, some of the other answers mentioned Ctrl+E.
I wanted the default motion key j to scroll the cursor as normal if the cursor isn't on the last line of the file, and then when the cursor is on the last line, j should scroll the file (in this case using Ctrl+E).
Placing the following in a .vimrc file enables this behaviour.
function! Scroll()
" what count was given with j? defaults to 1 (e.g. 10j to move 10 lines
" down, j the same as 1j)
let l:count = v:count1
" how far from the end of the file is the current cursor position?
let l:distance = line("$") - line(".")
" if the number of times j should be pressed is greater than the number of
" lines until the bottom of the file
if l:count > l:distance
" if the cursor isn't on the last line already
if l:distance > 0
" press j to get to the bottom of the file
execute "normal! " . l:distance . "j"
endif
" then press Ctrl+E for the rest of the count
execute "normal! " . (l:count - l:distance) . "\<C-e>"
" if the count is smaller and the cursor isn't on the last line
elseif l:distance > 0
" press j the requested number of times
execute "normal! " . l:count . "j"
else
" otherwise press Ctrl+E the requested number of times
execute "normal! " . l:count . "\<C-e>"
endif
endfunction
nnoremap j <Cmd>call Scroll()<CR>
nnoremap <Down> <Cmd>call Scroll()<CR>
Note: I'm not an expert Vim scripter, please edit with improvements
The last line also enables the same behaviour for the down arrow key ↓.
For Neovim, putting the following in init.lua would do the same:
local line = vim.fn.line
local nvim_input = vim.api.nvim_input
local function scroll()
-- what count was given with j? defaults to 1 (e.g. 10j to move 10 lines
-- down, j the same as 1j)
local count1 = vim.v.count1
-- how far from the end of the file is the current cursor position?
local distance = line("$") - line(".")
-- if the number of times j should be pressed is greater than the number of
-- lines until the bottom of the file
if count1 > distance then
-- if the cursor isn't on the last line already
if distance > 0 then
-- press j to get to the bottom of the file
nvim_input(distance.."<Down>")
end
-- then press Ctrl+E for the rest of the count
nvim_input((count1 - distance).."<C-e>")
-- if the count is smaller and the cursor isn't on the last line
elseif distance > 0 then
-- press j as much as requested
nvim_input(count1.."<Down>")
else
-- otherwise press Ctrl+E the requested number of times
nvim_input(count1.."<C-e>")
end
end
vim.keymap.set("n", "j", scroll, {
desc = "continue scrolling past end of file with j",
})
vim.keymap.set("n", "<Down>", scroll, {
desc = "continue scrolling past end of file with ↓",
})

Get the current line in visual mode from a function

I have a simple vim script that takes a visual block of text and stores it as a list. The problem with the function VtoList() is that it executes after the cursor returns to the start of the visual block, not before it. Because of this, I have no way of getting the line where the visual block ends.
nn <F2> :call VtoList()<CR>
func! VtoList()
firstline = line('v') " Gets the line where the visual block begins
lastline = line('.') " Gets the current line, but not the one I want.
mylist = getline(firstline, lastline)
echo mylist
endfunc
The problem is with line('.'). It should return the current line of the cursor, but before the function is called, the cursor has already returned to the start of the visual block. Thus, I'm only getting a single line instead of a range of lines.
I put together a solution that sets a mark everytime the user hits V and sets another mark before the function is called.
nnoremap V mV
nnoremap <F2> mZ:call VtoList()<CR>
The function works fine if I substitute line('v') and line('.') with line("'V") and line("'Z"), but I want to avoid this solution if I can because it could conflict with a user's mappings.
Is there a way I can get current line of a visual block within a function before the cursor has returned to the start of the visual block?
Don't use :, use <expr>:
function! s:NumSort(a, b)
return a:a>a:b ? 1 : a:a==a:b ? 0 : -1
endfunction
func! VtoList()
let [firstline, lastline]=sort([line('v'), line('.')], 's:NumSort')
let mylist = getline(firstline, lastline)
echo mylist
return ""
endfunc
vnoremap <expr> <F2> VtoList()
Note other changes: let (you forgot it), sort (line where selection starts may be after the line where selection ends), vnoremap (line("v") works only in visual mode), return (expr mappings return value is executed, but you don't need it, you need only side effects). You can replace the second line with
if mode()=~#"^[vV\<C-v>]"
let [firstline, lastline]=sort([line('v'), line('.')], 's:NumSort')
else
let [firstline, lastline]=sort([line("'<"), line("'>")], 's:NumSort')
endif
The reason why your solution is not working is that when : occurs in the mapping, you immediately exit visual mode and enter command mode. line("v") works only in visual mode.
Other note: vnoremap {lhs} : will produce command line already filled with '<,'>. You may have added range to the function definition and use let [firstline, lastline]=sort([a:firstline, a:lastline], 's:NumSort'). But you nevertheless will exit visual mode with :.

Resources