Vim. set command line from a function - vim

I'm trying to write a function that replaces text in all buffers. So I call Ack to search all the matches and next step I want to set into Quickfix command line this code
:QuickFixDoAll %s/foo/boo/gc
Seems like I can only call 'exec' function which runs this command immediately and there is no ablility to edit it or cancel at all
I also tried "input" function to read user input but got this error at runtime
not an editor command
Any ideas?
.vimrc:
function! ReplaceInFiles(o, n)
exec "Ack '" . a:o . "'"
exec "QuickFixDoAll %s/" . a:o . "/" . a:n . "/gc"
endfunction
" QuickFixDoAll
function! QuickFixDoAll(command)
if empty(getqflist())
return
endif
let s:prev_val = ""
for d in getqflist()
let s:curr_val = bufname(d.bufnr)
if (s:curr_val != s:prev_val)
exec "edit " . s:curr_val
exec a:command
endif
let s:prev_val = s:curr_val
endfor
exec "quit"
endfunction
command! -nargs=+ QuickFixDoAll call QuickFixDoAll(<f-args>)

Using input()
This queries both values interactively:
function! ReplaceInFiles()
let l:o = input('search? ')
let l:n = input('replace? ')
exec "Ack '" . l:o . "'"
exec "QuickFixDoAll %s/" . l:o . "/" . l:n . "/gc"
endfunction
nnoremap <Leader>r :call ReplaceInFiles()<CR>
Incomplete mapping
nnoremap <Leader>r :let o = ''<Bar>exec "Ack '" . o . "'"<Bar>exec "QuickFixDoAll %s/" . o . "//gc"<Home><Right><Right><Right><Right><Right><Right><Right><Right><Right>
This one puts the cursor on the right spot for the search. As this value is used twice (Ack and QuickFixDoAll), it is assigned to a variable. After that, move to the end of the command and fill in the replacement in between the //gc.
Custom parsing
The most comfortable option would be a custom command :AckAndSubstAll/search/replacement/. For that, you'd need to parse the two parts in the custom command (like :s does). You could do that with matchstr(), or use ingo#cmdargs#substitute#Parse() from my ingo-library plugin.

First use vim-qargs to copy all files from the quickfix window into Vim's arglist by calling :Qargs.
Then run your replace on all arguments in the arglist by doing :argdo %s/search/replace/gc

Related

vim: escaping strings for substitution (vimscript)

I currently write a substitute function that I often need for programming in vim.
The functions I already wrote look like this and run basically okay for searching and replacing strings which do not have any special characters inside. I already realized to escape the "/" automatically. My question is, how do I have to adapt the escape() function in the line
execute ':silent :argdo %s/' . escape(searchPattern, '/') . '/' . escape(replacePattern, '/') . '/ge'
So that automatically all of the characters that have to be escaped will be escaped?
" MAIN FUNCTION
" inspired by http://vimcasts.org/episodes/project-wide-find-and-replace/
function! SubstituteProjectwide(searchInput)
:set hidden
let cwd = getcwd()
let filename=expand('%:p')
call inputsave()
let searchPattern = input('Search for: ', a:searchInput)
let replacePattern = input('Replace "' . searchPattern . '" with: ')
let filePattern = input('Filepattern: ', cwd . '/**/*.*')
call inputrestore()
execute ':silent :args ' . filePattern
execute ':silent :vimgrep /' . searchPattern . '/g ##'
execute ':silent :Qargs'
execute ':silent :argdo %s/' . escape(searchPattern, '/\') . '/' . escape(replacePattern, '/\') . '/ge'
execute ':silent :edit ' . filename
echo 'Replaced "' . searchPattern . '" with "' . replacePattern . '" in ' . filePattern
endfunction
" VISUAL ENTRYPOINT WITH SELECTED TEXT
function! SubstituteProjectwideVisual()
let v = #*
call SubstituteProjectwide(GetVisualSelectedText())
endfunction
:vnoremap <F6> :call SubstituteProjectwideVisual()<cr>
" NORMAL ENTRYPOINT WIHT WORD UNDER CURSOR
function! SubstituteProjectwideNormal()
let wordUnderCursor = expand("<cword>")
call SubsituteProjectwide(wordUnderCursor)
endfunction
:nnoremap <F6> :call SubstituteProjectwideNormal()<cr>
" GETTING THE FILES WICH CONTAIN SEARCH PATTERN
" copied from http://vimcasts.org/episodes/project-wide-find-and-replace/
command! -nargs=0 -bar Qargs execute 'args' QuickfixFilenames()
function! QuickfixFilenames()
let buffer_numbers = {}
for quickfix_item in getqflist()
let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
endfor
return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
endfunction
" GETTING THE CURRENT VISUAL SELECTION
" copied from: https://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript
function! GetVisualSelectedText()
let [line_start, column_start] = getpos("'<")[1:2]
let [line_end, column_end] = getpos("'>")[1:2]
let lines = getline(line_start, line_end)
if len(lines) == 0
return ''
endif
let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
let lines[0] = lines[0][column_start - 1:]
return join(lines, "\n")
endfunction
UPDATE
I managed to escape many characters like that
escape(searchPattern, ' / \') . '/' . escape(replacePattern, ' / \')
But how do I know which list of characters i have to escape, when it is basically possible, that every character can be inside the search and also the replace string?
To do a literal substitution, specify "very-nomagic" (:help /\V) , and escape the separator (/) and \
in the source.
In the replacement, & and ~ must be escaped, too, if the 'magic' option is set. (\V doesn't work here.)
execute ':silent :argdo %s/\V' . escape(searchPattern, '/\') . '/' . escape(replacePattern, '/\' . (&magic ? '&~' : '')) . '/ge'
Line breaks (if possible) must be changed from ^M to \n:
execute ':silent :argdo %s/\V' . substitute(escape(searchPattern, '/\'),"\n",'\\n','ge') . '/' . escape(replacePattern, '/\' . (&magic ? '&~' : '')) . '/ge'
This doesn't exactly answer your question but is another way of looking at the problem you're trying to solve. I don't entirely follow what the :args setup is doing for you since the quickfix has all of the info you need after the :vimgrep.
I have this in my vimrc:
nnoremap <F3> :vimgrep // $PROJECT_ROOT_DIR/src/**/*.{cpp,h,c,inl,msg}<C-Left><C-Left><Right>
Obviously you'll want to customize the search path, as this focuses on just the above five file extensions in a specific file hierarchy that was configured each time I launched Vim...
Anyway, once you've got that, :cr makes sure you're at the beginning, then do the search&replace you want inside of a macro. You can actually test it out on the first few finds if you want, but then...
qbq Clear the 'b' register.
qa Start recording the 'a' macro'
:%s/this/that/g Start the macro and substitute 'that' for 'this'. (Press enter)
:w|cnf write the file and go to the next one (Press enter)
q Stop recording the 'a' macro.
Then qb#a#bq will run the macro once, saving it in #b. Then just run
(type) #b once more and it'll keep calling itself until it's done.

How to make vim's `:global` command confirm-able before executing the ex Command?

How can I make vim's :global command ask the user if they want to execute the ex command? Similar to what happens with the :substite command with the 'c' option, for example %s:Foo:Fighters:gc
I tried:
:g/mypattern/.s:.*\n::gc
and
:g/mypattern/s:.*\n::gc
but if there is a match on below line it is jumped. For example:
MATCH
NONMATCH
MATCH
MATCH
MATCH
The result is:
NONMATCH
MATCH <<-- this should be erased.
A promptable g/FOO/d would be perfect.
There is no native way to do this. The typical method would be to record a macro and repeat a macro. Making sure you n or / at the end of the macro to advance to the next match. Skipping is now simply n and ## to execute the macro.
Custom :Global command
However if you truly want to have :global command with a confirm you can sort of mimic this by using confirm() inside the your command. The general idea is to do something like this:
:g/pat/if confirm("&yes\n&no", 2) == 1 | cmd | endif
This doesn't quite work for the following reasons:
You have no idea where your cursor is. Need something like :match and :redraw
Does not abort well. Need a way to throw an exception to abort
Very unwieldily to type this all out
I have come up with the following confirming :Global/:G command
command! -nargs=+ -range=% -complete=command Global <line1>,<line2>call <SID>global_confirm(<q-args>)
command! -nargs=+ -range=% -complete=command G <line1>,<line2>call <SID>global_confirm(<q-args>)
function! s:global_confirm(args) range
let args = a:args
let sep = args[0]
let [pat, cmd; _] = split(args[1:], '\v([^\\](\\\\)*\\)#<!%d' . char2nr(sep), 1) + ['', '']
match none
let options = ['throw "Global: Abort"', cmd, '', 'throw "Global: Abort"']
let cmd = 'exe ''match IncSearch /\c\%''.line(''.'').''l''.#/.''/'''
let cmd .= '| redraw'
let cmd .= '| exe get(options, confirm("Execute?", "&yes\n&no\n&abort", 2))'
try
execute a:firstline . ',' . a:lastline . 'g'.sep.pat.sep.cmd
catch /Global: Abort/
finally
match none
endtry
endfunction
Note: Use as-is. Uses IncSearch for highlight and forces \c.
Now you can run :G/foo/d.
Custom :Confirm command
If you rather use a similar technique to the one #Randy Morris provided and use the following :Confirm {cmd} command to confirm {cmd} before execution.
command! -nargs=+ -complete=command Confirm execute <SID>confirm(<q-args>) | match none
function! s:confirm(cmd)
let abort = 'match none | throw "Confirm: Abort"'
let options = [abort, a:cmd, '', abort]
match none
execute 'match IncSearch /\c\%' . line('.') . 'l' . #/ . '/'
redraw
return get(options, confirm('Execute?', "&yes\n&no\n&abort", 2), abort)
endfunction
This will allow you to use :g/foo/Confirm d
For more help see:
:h #
:h q
:h confirm()
:h :exe
:h get()
:h :match
:h :redraw
As far as I know there is no way to do this natively. I think I've hacked together a way to do this but it's probably buggy as I haven't written vimscript in a long time. In this I've defined a command C which accepts an ex command as its arguments. Each line returned via :global is then passed to this ex command if you press y or Y. Any other key causes this line to be skipped.
let s:GlobalConfirmSignNumber = 42
sign define GlobalConfirmMarker text=>> texthl=Search
function GlobalConfirm(cmd)
let line = getpos(".")[1]
execute "sign place " . s:GlobalConfirmSignNumber . " line=" . line . " name=GlobalConfirmMarker file=" . expand("%:p")
redraw!
echomsg "Execute? (y/N) "
try
let char = nr2char(getchar())
if (char == "y" || char == "Y")
execute a:cmd
endif
finally
" Ensure signs are cleaned up if execution is aborted.
execute "sign unplace " . s:GlobalConfirmSignNumber
endtry
redraw!
endfunction
command -nargs=* C call GlobalConfirm(<q-args>)
Here's a gif of it in action. In this gif I'm running the command norm! gUU for every line which contains ba. In this case I confirmed every match by pressing y three times.
If anyone can make improvements to this (especially the signs bit) please edit at will.

How to create a directory search and replace with vimscript and fzf

I'm trying to create a directory search and replace function in vimscript using fzf. The place where I block is when trying to use Alt-a fzf binding to select the whole list. I'm not even sure if that is possible given fzf is an external process but I may be wrong.
Here's my current function.
function! CWDSearchAndReplace()
" Try to get word under cursor else prompt user for a word
let wordToReplace = expand("<cword>")
if wordToReplace == ""
call inputsave()
let wordToReplace = input("Replace: ")
call inputrestore()
endif
" Prompt for replacement
call inputsave()
let replacement = input("Replace \"" . wordToReplace . "\" with: ")
call inputrestore()
execute "Ag " . wordToReplace
" =====> Here I'd like to execute Alt-a followed by <CR>
execute "cdo %s//" . replacement . "/gc"
end function
Thanks for your help!
As pointed out by the creator of fzf.vim, there is no need to use fzf here, one can simply use ag.
function! s:ag_to_qf(line)
let parts = split(a:line, ':')
echo parts
return {'filename': parts[0], 'lnum': parts[1], 'col': parts[2],
\ 'text': join(parts[3:], ':')}
endfunction
function! AgToQF(query)
call setqflist(map(systemlist('ag --column '.a:query), 's:ag_to_qf(v:val)'))
endfunction
function! CWDSearchAndReplace()
let wordUnderCursor = expand("<cword>")
call inputsave()
let wordToReplace = input("Replace : ", wordUnderCursor)
call inputrestore()
call inputsave()
let replacement = input("Replace \"" . wordUnderCursor . "\" with: ")
call inputrestore()
call AgToQF(wordUnderCursor)
execute "cdo %s/" . wordToReplace . "/" . replacement ."/gc"
endfunction
nnoremap <leader>r :call FileSearchAndReplace()<CR>

How to write a .vimrc function to match a filename in a given file and open it with the vsplit command?

I have a file called Index.txt with the following lines:
/Project/A/B/C/D/main.c
/Project/A/B/C/D/main_backend.c
/Project/A/B/C/D/main_frontend.c
I'd like to create a command called Fsearch to execute a search in Index.txt using regular expressions, match the first occurrence and execute :vsplit with it. For example, if I execute:
:Fsearch main_backend.c
Vim should execute:
:vsplit /Project/A/B/C/D/main_backend.c
and If I execute:
:Fsearch main*.c
Vim should execute:
:vsplit /Project/A/B/C/D/main.c
This is what I've tried so far but I'm pretty sure it could be improved:
function! CopyMatches(reg)
let l:file = grep -m 1 a:reg ~/Index.txt
echom l:file
if len(l:file) > 0
exec ':vsp ' . l:file
else
echom 'File not found: ' . l:file
end
endfunction
command! -nargs=* Fsearch call CopyMatches('<args>')
Any suggestion?
You could try this:
function! CopyMatches(reg)
execute "silent! grep!" a:reg " ~/Index.txt"
redraw!
let l:file = getqflist()
if len(l:file) > 0
let l:path_head = fnamemodify( "~/workspace", ":p" )
for l:item in l:file
let l:current_file = l:path_head . "/" . l:item["text"]
if match( l:current_file, getcwd() ) != -1
execute 'vsplit' fnamemodify( l:current_file, ":~:.")
return
endif
endfor
echom "File not found:" a:reg
else
echom "File not found:" a:reg
endif
endfunction
command! -nargs=* Fsearch call CopyMatches('<args>')
Explanation:
The :grep built-in command is a wrap used by Vim to execute an external grep (see :help grep for more information).
The :grep! form of the :grep command doesn't jump to the first match automatically (i.e., :grep! won't open Index.txt).
The :silent! command is used to suppress the default full screen grep output.
Vim uses quickfix list with :grep so you can get all occurrences from getqflist() function (see :help getqflist() for details)

Configure Vim to insert text for Lines of Code

I'm using Vim for all program editing and I have a standard header I use at the top of all my source code files. I have a .vimrc file set up to update certain fields in this header (like Last Modified) when I save any changes using :w
My question is, how do I put in a function to count lines of code, following the basic rule that only non-blank lines are counted?
I know within an open vim buffer, I can use
:%s/\n//gn
to count all lines, and
:%s/\n\n//gn
to count blank lines (basically count how many times two newlines appear in a row, indicating a blank line). But how do I put this in my .vimrc file?
Here's the code fragment from my .vimrc that updates the header fields:
function! LastModified()
if &modified
let save_cursor = getpos(".")
let n = min([20, line("$")])
keepjumps exe '1,' . n . 's#^\(.\{,10}Last Modified:\).*#\1' .
\ strftime(' %a %b %d, %Y %I:%M%p') . '#e'
keepjumps exe '1,' . n . 's#^\(.\{,10}Filename:\).*#\1' .
\ ' ' . #% . '#e'
keepjumps exe '1,' . n . 's#^\(.\{,10}LOC:\).*#\1' .
\ ' ' . '' . '#e'
call histdel('search', -1)
call setpos('.', save_cursor)
endif
endfun
Also, I would just like to add, I know there are numerous other ways to do this (like using wc --lines from the shell) but I'm interested in learning how to really configure my editor (so call it a learning exercise).
You actually should not want to use :s here:
function! CountNonEmpty()
return len(filter(getline(1, line('$')), '!empty(v:val)'))
endfunction
By the way, I would have used getline+map+setline to implement your header updater:
function! LastModified()
if &modified
" If number of buffer lines is < 20, then getline(1, 20)"
" will return only existing lines without any errors "
call setline(1, map(getline(1, 20), 'substitute(substitute(substitute(v:val, '.
\'"^\\v(.{,10}Last Modified:).*", "\\1 ".strftime("%s %b %d, %Y %I:%M%p"), ""),'.
\'"^\\v(.{,10}Filename:).*", "\\1 ".escape(#%, "&\\~"), ""),'.
\'"^\\v(.{,10}LOC:).*", "\\1 ", "")'))
endif
endfunction
This might help:
function! CountNonEmpty()
redir => g:nonblank
silent %s/^.\+$/&/n
redir END
return substitute(g:nonblank, '\n\s*\(\d\+\)\D.*$', '\1', '')
endfunction
:redir => Stores the output of the following ex commands into the given variable. See :help :redir

Resources