Reputation: 165
I have relativenumber set, and my foldmethod is indent. Consider the following mwe:
I want to copy this
This is indented
Just like this
now copy
with folds, this becomes something like
I want to copy this
+-- 2 lines bla bla
now copy
If I'm on the last line, relativenumber will show something like
2 I want to copy this
1 +-- 2 lines bla bla
4 now copy
and I want to yank the line that is 2 lines before the current line, as relativenumber tells me. So I do
:.-2y
But this will actually yank the entire folded block.
Q: Is there a way to still yank the lines I need while not having to do any weird arithmetic*?
*The arithmetic should be
(Actual number) = (Relative number) + (Number of folded lines) - (Number of folds),
this is very impractical for speed
Upvotes: 2
Views: 70
Reputation: 349
As far as I know, there is no way to change the behavior of the yank command to work as you want it to work, since the values of ".+x" is expanded before they get to the command itself, and I don't know about any way to change the behavior of this.
But, with a little vimscript, this problem is solvable. I wrote a couple of vim function that would allow you to run the exact logic that you want to do.
This code snippet would allow you to run the yank command with relative range to the current location, and the command would work as you want it to:
" This file is created to handle the case in which the y ex command behaves
" different than expected when it is run with relative line number.
" Those functions behaves a bit different than the behavior of the y command,
" the y command gets the argument as range before it, with a range relative or
" absolute. The functions in this module gets a relative range from the current
" line (they can get a negative range as well).
" Those functions have commands at the bottom of the script, they should be used
" to run the function (and can be re-mapped to different values, for shorter
" typing).
" Yank a single line, getting only the line you want to yank. The value of the
" line should be relative to the current line.
function! s:YankByEnd(end_line)
" Save the current location, to know where to return to.
let current_line = line('.')
" Copy the wanted line.
if a:end_line > 0
execute 'normal ' . string(a:end_line) . 'jyy'
elseif a:end_line < 0
let move_by = a:end_line * -1
execute 'normal ' . string(move_by) . 'kyy'
else
execute 'normal yy'
endif
" Return to the original location.
execute 'normal ' . current_line . 'gg'
endfunction
" Yank a range of lines, getting the lines you want to yank. The value of the
" lines should be relative to the current line.
function! s:YankByStartAndEnd(start_line, end_line)
" Save the current location, to know where to return to.
let current_line = line('.')
" Get the start line.
if a:start_line > 0
execute 'normal ' . string(a:start_line) . 'j'
elseif a:start_line < 0
let move_by = a:start_line * -1
execute 'normal ' . string(move_by) . 'k'
endif
let start_line = line('.')
execute 'normal ' . current_line . 'gg'
" Get the end line.
if a:end_line > 0
execute 'normal ' . string(a:end_line) . 'j'
elseif a:end_line < 0
let move_by = a:end_line * -1
execute 'normal ' . string(move_by) . 'k'
endif
let end_line = line('.')
execute 'normal ' . current_line . 'gg'
" Copy the lines
execute start_line . "," end_line . "yank"
" Return to the original location.
execute 'normal ' . current_line . 'gg'
endfunction
command! -nargs=1 YankSingleLine call <SID>YankByEnd(<f-args>)
command! -nargs=* YankMultipleLines call <SID>YankByStartAndEnd(<f-args>)
If you would add this file and source it (or add it directly to your vimrc), you would be able to run the commands as you want them. For example, to run the command you wanted to run in the your original example, simply run the command:
:YankSingleLine -2
and the wanted line would be yanked.
I also have a script that does the same thing, but it is more generic, to enable running more command, and not only the specific yank command. This is the script:
" This file is created to handle the case in which an ex command behaves
" different than expected when it is run with relative line number and folds.
" Those functions behaves a bit different than the behavior of the regular ex
" command.
" A regular ex command gets the argument as range before it, with a range
" relative or absolute. The functions in this module gets a relative range
" from the current line (they can get a negative range as well) and the command
" to run.
" Run an ex command on a single line. The value of the line should be relative
" to the current line.
function! RunByEnd(end_line, command)
" Save the current location, to know where to return to.
let current_line = line('.')
" Get the line to run the command on.
if a:end_line > 0
execute 'normal ' . string(a:end_line) . 'j'
elseif a:end_line < 0
let move_by = a:end_line * -1
execute 'normal ' . string(move_by) . 'k'
endif
let end_line = line(".")
" Get back to the original location.
execute 'normal ' . current_line . 'gg'
" Run the command.
execute end_line . a:command
endfunction
" Run an ex command on a range of lines. The value of the lines should be
" relative to the current line.
function! RunByStartAndEnd(start_line, end_line, command)
" Save the current location, to know where to return to.
let current_line = line('.')
" Get the start line.
if a:start_line > 0
execute 'normal ' . string(a:start_line) . 'j'
elseif a:start_line < 0
let move_by = a:start_line * -1
execute 'normal ' . string(move_by) . 'k'
endif
let start_line = line('.')
execute 'normal ' . current_line . 'gg'
" Get the end line.
if a:end_line > 0
execute 'normal ' . string(a:end_line) . 'j'
elseif a:end_line < 0
let move_by = a:end_line * -1
execute 'normal ' . string(move_by) . 'k'
endif
let end_line = line('.')
" Return to the original location.
execute 'normal ' . current_line . 'gg'
" Run the given command.
execute start_line . "," end_line . a:command
endfunction
Using it, you would be able to run any command that you want, but you need to add the original command you wanted to run.
To fix you problem using this script, you can run the command:
:call RunByEnd(-2, "y")
Upvotes: 2