vimscript replace line not working - vim

Newcomer to VimL trying to write a mapping that does the following:
foo
|----> cursor
bar
baz
lr2j should repalce foo with baz.
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
let saved_register = ##
silent execute "normal! S\<esc>my`]dd'yPjddk^ :delmarks y"
let ## = saved_register
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
Instead I get
Error detected while processing function <SNR>108_LineReplace: line 5: E20: Mark not set
I've tried different permutations of execute "normal! ..." command to no avail. Can anyone spot the error?
I should note that when I test out the normal commands everything works fine and the mark 'y exists.

Use :move and :delete to simply things:
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
']move '[
-delete_
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
For more help see:
:h :d
:h :m
:h :range

#xaizek is right; the correct way is to store the mark from the motion:
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
let lnum = getpos("']")[1]
let saved_register = ##
silent execute "normal S\<esc>my" . lnum . "Gdd'yPjddk^ :delmarks y"
let ## = saved_register
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#

Related

Vimscript - split function call to multiple lines

I'm trying to write a vimscript that converts a line like this:
myFuncCall(param1, param2, param3="hey", param4)
To:
myFuncCall(param1,
param2,
param3="hey",
param4
)
While maintaining and adding indentation. So far I have:
function SplitParamLines() abort
let f_line_num = line(".")
let indent_length = indent(f_line_num)
echom indent_length
echom f_line_num
.s/\s*,/,\r/g
nohlsearch
0t)a<cr>
endfunction
How do I indent lines using vimscript?
Why does the last line produces this?:
Error detected while processing function SplitParamLines:
line 7:
E14: Invalid address
So there are 2 options which I chose to use both:
Using this incredible plugin - Splitjoin.vim
Using this simple function:
" Every parameter in its own line
function SplitParamLines() abort
let f_line_num = line(".")
let indent_length = indent(f_line_num)
exe ".s/\s*,/,\r" . repeat(" ", indent_length + &shiftwidth - 1) . "/g"
nohlsearch
" execute "normal! 0t)a\<cr>\<esc>"
endfunction
nnoremap <silent> <leader>( :call SplitParamLines()<cr>
Although not perfect, it works :)
If you want to execute something that you would have typed, you need :normal. If there are special characters, then you'll also need :exe and to escape these special characters. IOW
:exe "normal! 0t)i\<cr>"

load external command in the same split after repeating it

I want to load some text coming from a command line command into a new vim split. I got this working but if I run the command again it keeps opening new splits.
What I want to achieve is getting this into the same split. How can I do this?
nnoremap <leader>q :execute 'new <bar> 0read ! bq query --dry_run --use_legacy_sql=false < ' expand('%')<cr>
I would suggest using the preview window via the :pedit command.
nnoremap <leader>q :execute 'pedit <bar> wincmd p <bar> 0read ! bq query --dry_run --use_legacy_sql=false < ' expand('%')<cr>
However we can do even better by doing the following:
Making a "query" operator using g# and 'opfunc'
A query command (feels very vim-like to do so)
Using stdin instead of a filename
Example:
function! s:query(str)
pedit [query]
wincmd p
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal noswapfile
%delete _
call setline(1, systemlist('awk 1', a:str))
endfunction
function! s:query_op(type, ...)
let selection = &selection
let &selection = 'inclusive'
let reg = ##
if a:0
normal! gvy
elseif a:type == 'line'
normal! '[V']y
else
normal! `[v`]y
endif
call s:query(##)
let &selection = selection
let ## = reg
endfunction
command! -range=% Query call s:query(join(getline(<line1>, <line2>), "\n"))
nnoremap \qq :.,.+<c-r>=v:count<cr>Query<cr>
nnoremap \q :set opfunc=<SID>query_op<cr>g#
xnoremap \q :<c-u>call <SID>query_op(visualmode(), 1)<cr>
Note: I am using awk 1 as my "query" command. Change to meet your needs.
For more help see:
:h :pedit
:h :windcmd
:h operator
:h g#
:h 'opfunc'
:h systemlist()

Is it possible to cycle around marks in vim?

I am starting to use [' and ]' to jump between my marks in a file, as mentioned here:
http://vim.wikia.com/wiki/Using_marks
However, when I get to the last mark in the file these commands don't wrap around to the top.
I searched around for "cycling" or "wrapping" when it comes to navigating marks but everything I see mentions Ctrl-o and Ctrl-i which is nice but doesn't answer my question.
Is it possible to set an option to wrap top-to-bottom or bottom-to-top when using these shortcuts?
You can create a function to check if you have moved, and if not then go to the beginning of the file and call ]' again. Like this:
nnoremap ]' :call CycleMarksForward()<cr>
function! CycleMarksForward()
let currentPos = getpos(".")
execute "normal! ]'"
let newPos = getpos(".")
if newPos == currentPos
execute "normal! gg]'"
endif
endfunction
You'll need to the same thing for [` ]` and [', although there is probably a way to come up with a generic solution.
Fleshed out:
nnoremap <silent> ]' :call CycleMarks("]'")<cr>
nnoremap <silent> [' :call CycleMarks("['")<cr>
nnoremap <silent> ]` :call CycleMarks("]`")<cr>
nnoremap <silent> [` :call CycleMarks("[`")<cr>
function! CycleMarks(arg)
let currentPos = getpos(".")
execute "normal! " . a:arg
let newPos = getpos(".")
if newPos == currentPos
if a:arg == "]'" || a:arg == "]`"
execute "normal! gg0" . a:arg
else
execute "normal! G$" . a:arg
endif
endif
endfunction
Note: this solution does not handle marks on the first and last line very well, i.e. marks on the last line will be skipped when cycling backwards and marks on the first line will be skipped when cycling forward.

Vim doesn't indent when inserting carriage return from a function?

So I'm trying to write a function that makes inserting semi colons a bit more pleasant:
inoremap <leader>; <esc>:call InsertSemiColin()<cr>
Basically it checks to see if I'm standing at the end of the current line, if so I auto-format the code, insert the semi-colon at the end and break down to the next line (carriage return)
fun! InsertSemiColin()
if (!IsEOL()) | exec "normal! a;" | return | endif
exec "normal! \<esc>:OmniSharpCodeFormat\<cr>A;\<cr>"
endf:
fun! IsEOL()
" col number == length of current line?
return col('.') == strlen(getline(line('.'))) " or just getline('.')
endf
Expectation:
Result:
To try it out on your own, you can remove the code-formatting and just do:
exec "normal! a;\<cr>"
My indentation settings:
set smartindent
set tabstop=4
set shiftwidth=4
filetype plugin indent on
The weird thing is that if I don't insert the carriage return from a function, it works as expected!
inoremap <leader>; ;<cr>
Why is this happening? and how can I get the result I'm expecting?
Very frustrating, any help would be appreciated!
I would avoid leaving and re-entering insert mode for this via :help :map-expr:
inoremap <expr> <leader>; ';' . (IsEOL() ? '<esc>:OmniSharpCodeFormat<cr>A<cr>' : '')
For this to work, you need to change the comparison in the IsEOL() function:
fun! IsEOL()
" col number == length of current line?
return col('.') > strlen(getline(line('.'))) " or just getline('.')
endf
This also fixes the indent problem.

Can we create a new op-pending command in Vim?

Does anyone know if it's possible to create a new op-pending command?
e.g. I'd like to replace a sequence such as vf(r<space>w with ,cf(. Specifically here, the idea is to "clear" the text from the cursor position up to and including the next opening brace and then put the cursor at the beginning of the next word.
I may just be missing something in the help files (or my Google-fu is off today), so a pointer to the right place would be much appreciated.
You want to use :set opfunc and g#. The documentation is pretty good, :h g#.
nnoremap <silent> ,c :set opfunc=Clearing<cr>g#
vnoremap <silent> ,c :<c-u>set opfunc=Clearing<cr>g#
function! Clearing(type, ...)
let sel_save = &selection
let &selection = "inclusive"
let reg_save = ##
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>r "
elseif a:type == 'line'
silent exe "normal! '[V']r "
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]r "
else
silent exe "normal! `[v`]r "
endif
norm! `]w
let &selection = sel_save
let ## = reg_save
endfunction
I think :h map-operator is what you're looking for.

Resources