Comment & Uncomment using same command - No plugin - vim

I'm using a modified version of https://stackoverflow.com/a/1676672/618584
autocmd FileType php,styl,javascript let b:comment_leader = '// '
noremap <C-\> :<C-B>silent <C-E>s/^/<C-R>=escape(b:comment_leader,'\/')<CR>/<CR>:nohlsearch<CR>
"noremap <C-\> :<C-B>silent <C-E>s/^\V<C-R>=escape(b:comment_leader,'\/')<CR>//e<CR>:nohlsearch<CR>
I want to remove comments using the same command (see commented out line above). What I need to do is check if the line begins with '//' and if so, map the to removing the comment.
Any idea how to do this?
I was previously using tpope's commentary plugin, and to achieve what I wnt with that, I'd do:
" comments toggle
autocmd FileType php setlocal commentstring=\/\/\ %s
nmap <C-\> gcc
xmap <C-\> gcugin I'd do:
But again, I do not want to use a plugin because I only code in JS and PHP.

First, I would build abstractions via a custom function. Adding a custom command would make sense, too:
function! CommentToggle() range
silent execute a:firstline . ',' . a:lastline . 's/^/' . escape(b:comment_leader, '\/') . '/e'
"silent execute a:firstline . ',' . a:lastline . 's/^\V' . escape(b:comment_leader, '\/') . '//e'
endfunction
command! -range CT <line1>,<line2>call CommentToggle()|nohlsearch
noremap <C-\> :CT<CR>
With this, toggling just means checking (let's say the first line determines the on/off for all lines, i.e. you only operate on all-comments or nothing-commented) the line:
function! CommentToggle() range
if getline(a:firstline) =~ '^\V' . escape(b:comment_leader, '\')
silent execute a:firstline . ',' . a:lastline . 's/^\V' . escape(b:comment_leader, '\/') . '//e'
else
silent execute a:firstline . ',' . a:lastline . 's/^/' . escape(b:comment_leader, '\/') . '/e'
endif
endfunction

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.

Vim - pass register to operator function

I'm trying to implement ChangePaste operator. It should replace text with the one from register.
It's working fine with motions, so I can use cp<motion> and the text will be replaced from default register.
Now I would like to be able to use it with different registers. I'm looking for information how to pass selected register to operator function. So, if one type "acpiw I would like the script to replace an inner word with register a content. Is that possible at all?
Code so far:
nmap <silent> cp :set opfunc=ChangePaste<CR>g#
function! ChangePaste(type, ...)
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>\"_c" . #"
elseif a:type == 'line'
silent exe "normal! '[V']\"_c" . #"
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]\"_c" . #"
else
silent exe "normal! `[v`]\"_c" . #"
endif
endfunction
Edit:
Solution using v:register and buffer variable:
nmap <silent> cp :let b:changepaste_buffer = v:register<cr>:set opfunc=ChangePaste<CR>g#
function! ChangePaste(type, ...)
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>\"_c" . getreg(b:changepaste_register)
elseif a:type == 'line'
silent exe "normal! '[V']\"_c" . getreg(b:changepaste_register)
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]\"_c" . getreg(b:changepaste_register)
else
silent exe "normal! `[v`]\"_c" . getreg(b:changepaste_register)
endif
endfunction
As mentioned by #romainl in the comments, you can access the v:register variable in your function.
No need to even save it as a buffer variable.

Vim. set command line from a function

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

Custom script for git blame from vim

I want a minimum way of using git blame from vim (I don't want to use the whole Fugitive plugin). What I have right now is this:
This function is from the vim help page and enables me to open shell commands in a scratch buffer.
function! s:ExecuteInShell(command)
let command = join(map(split(a:command), 'expand(v:val)'))
let winnr = bufwinnr('^' . command . '$')
silent! execute winnr < 0 ? 'botright new ' . fnameescape(command) : winnr . 'wincmd w'
setlocal buftype=nowrite bufhidden=wipe nobuflisted noswapfile nowrap number
echo 'Execute ' . command . '...'
silent! execute 'silent %!'. command
silent! execute 'resize ' . line('$')
silent! redraw
silent! execute 'au BufUnload <buffer> execute bufwinnr(' . bufnr('#') . ') . ''wincmd w'''
silent! execute 'nnoremap <silent> <buffer> <LocalLeader>r :call <SID>ExecuteInShell(''' . command . ''')<CR>'
echo 'Shell command ' . command . ' executed.'
endfunction
command! -complete=shellcmd -nargs=+ Shell call s:ExecuteInShell(<q-args>)
Together with the above function I would like to do:
noremap <leader>b :Shell git blame -L line(".") - 5, line(".") + 5 %<cr>
to get a git blame window for the rows around the cursor position in the current buffer.
Now I have two questions:
1: How can I make the scratch buffer that opens read-only so I can close it using only q? I would like to make this change in the function so that all: Shell commands can be closed with q.
2: How can i get line(".") - 5 expand into current line - 5 row number?
To make a buffer read-only and not modifiable, you can put
setlocal readonly nomodifiable
at the end of your function.
In the case of your next question, you can use execute and eval
noremap <leader>b :execute "Shell git blame -L " . eval(line(".")-5)) . ",+10 %"<cr>
I recommend to read these descriptions, and help in general:
:h execute
:h eval
:h readonly
:h nomodifiable
Also here is the link to the mentioned function on wikia.
I use a simple hack to get my vim git integration:
This solves my git commit/add , blame and op.
map <F5> :!git add %;git commit -m "commit" %<CR>
map <F3> :!git blame % > %.blame<CR>:vsplit %.blame<CR>
map <F4> :!git log --abbrev-commit % > %.log<CR>:vsplit %.log<CR>
i have something here, the script can be copied and added to the .vimrc file.
command Blame will run git blame on the current file, will split the screen and put the result of 'git blame -w' into the lower buffer (buffer is put into read only mode), your cursor will be in the blame buffer (current line as in source file)
while in the lower buffer: GShow will split the lower screen and open the commit 'git show -w [hash]' for the current line in the right hand buffer (buffer again in read only mode).
"======================================================
" Function runs git blame on file in current buffer and
" puts output into a new window
" move to current line in git output
" (can't edit output)
"======================================================
command! -nargs=* Blame call s:GitBlame()
function! s:GitBlame()
let cmdline = "git blame -w " . bufname("%")
let nline = line(".") + 1
botright new
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap
execute "$read !" . cmdline
setlocal nomodifiable
execute "normal " . nline . "gg"
execute "set filetype=cpp"
endfunction
"======================================================
" function runs git show on report of git blame;
" the first token of a line is taken as SHA checsum of the
" git commit
"======================================================
command! -nargs=* GShow call s:GitShowFromBlame()
function! s:GitShowFromBlame()
let curline = getline( "." )
let tokens = split(curline)
let cmdline = "git show -w " . tokens[0]
"botright new
"topleft new
"vsplit new
"vnew new
vertical new
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap
execute "$read !" . cmdline
setlocal nomodifiable
execute "normal 1gg"
execute "set filetype=cpp"
endfunction
My take on this
fun! GbSyncLines(is_ow2bw)
let l:ow = getbufvar(+expand('<abuf>'), 'origWindow')
let l:bw = getbufvar(+expand('<abuf>'), 'blameWindow')
let l:origLine=line('.', win_getid(l:ow))
let l:blameLine=line('.', win_getid(l:bw))
if a:is_ow2bw == 1
eval win_execute(win_getid(l:bw), "windo call cursor(" . l:origLine . ", 0)")
else
eval win_execute(win_getid(l:ow), "windo call cursor(" . l:blameLine . ", 0)")
endif
endfun
fun! GbMake(view, origWindow)
let l:origWindow=a:origWindow
let l:file_dir=expand('#'.winbufnr(a:origWindow).':h')
let l:file_name=expand('#'.winbufnr(a:origWindow).':t')
let l:origLine=line('.', win_getid(a:origWindow))
sil exe a:view
setl buftype=nofile bufhidden=wipe
exe "lcd ".l:file_dir
exe "0r ! git blame " . l:file_name
setl nomodifiable
setl nonumber norelativenumber
if v:shell_error != 0
exe l:origWindow . "wincmd w"
return
end
eval searchpos('(')
eval searchpairpos('(', '', ')')
norm! gE
exe "vertical resize " . col('.')
let l:blameWindow = winnr()
let b:blameWindow = winnr()
let b:origWindow = a:origWindow
let l:origLine=line('.', win_getid(b:origWindow))
eval cursor(l:origLine , 1)
nnoremap <buffer> <C-]> :call GcommitShow(expand("<cword>"), line(".")) <CR>
au BufWipeout <buffer> call win_execute(win_getid(getbufvar(+expand('<abuf>'), 'origWindow')), "windo setl scrollbind&")
au WinEnter <buffer> :call GbSyncLines(1)
au WinLeave <buffer> :call GbSyncLines(0)
setl scrollbind
exe a:origWindow . "wincmd w"
setl scrollbind
exe "syncbind"
if a:view == "enew"
exe l:blameWindow . "wincmd w"
end
endfun
fun! GcommitShowClose()
let l:w=getbufvar(expand('<abuf>'), 'origWindow')
let l:l=getbufvar(expand('<abuf>'), 'origLine')
let l:v=getbufvar(expand('<abuf>'), 'origView')
eval GbMake("enew", l:w)
eval winrestview(l:v)
eval cursor(l:l, 1)
endfun
fun! GcommitShow(commit, linenr)
let l:origWindow=b:origWindow
let l:viewsave=winsaveview()
sil exe "enew | setlocal buftype=nofile | setlocal bufhidden=wipe | 0r ! git show " . a:commit
setl nomodifiable
setl nonumber norelativenumber
if v:shell_error != 0
eval GbMake('enew', l:origWindow)
return
end
let b:origWindow=l:origWindow
let b:origLine=a:linenr
let b:origView=l:viewsave
nnoremap <buffer> <C-O> :call GcommitShowClose()<CR>
eval cursor(1, 1)
endfun
fun! Gb()
eval GbMake('vnew', winnr())
endfun
command! Gb call Gb()
while in the blame window, CTRL+] shortcut will show git show for commit the cursor is on. Then CTRL+O will get back to the git blame.
Note lcd on start - command will work even when you are not in the git dir. The gist

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