Closing files in Vim from directory and its subdirectories - vim

Is there a way in vim to close all files (buffers, let's not get into that) from some directory and its subdirectories?

Put the following into your .vimrc or in some custom file inside vim plugin folder.
function! s:close_buffers(name_regexp)
for buffer_number in range(1, bufnr('$'))
if !buflisted(buffer_number)
continue
endif
let name = fnamemodify(bufname( buffer_number ), ':p')
if name =~ a:name_regexp
exec 'bdelete '.buffer_number
endif
endfor
endfunction
command! -nargs=1 CloseBuffers call s:close_buffers(<f-args>)
Use commands like
:CloseBuffers include
:CloseBuffers in.*e
to close buffers which name matches passed regexp.
That means that to close all files from the certain folder you can use
:CloseBuffers workspace/cpp
:CloseBuffers /home/my/project
To close all the files from the current dir and all subdirs
:exec "CloseBuffers ".getcwd()

You can do it this way fairly concisely:
:silent! bufdo if expand('%')=~"some_regex"|bd|endif
Or if you want absolute pathnames instead of relative:
:silent! bufdo if expand('%:p')=~"some_regex"|bd|endif
Or if you want it to prompt you for the regex interactively you could set this up as a mapping:
:let regex=input("> ")|silent! bufdo if expand('%:p')=~regex|bd|endif
etc. etc.

Related

Vim Ack, Ag: Search in directory of file that you are editing in

I want to be able to search files that only reside in the directory of the file that I opened inside vim.
The documentary of Ack says:
:Ack[!] [options] {pattern n} [{directory}] *:Ack*
Search recursively in {directory} (which defaults to the current
directory) for the {pattern}. Behaves just like the |:grep| command, but
will open the |Quickfix| window for you. If [!] is not given the first
occurrence is jumped to.
On VimFandom I found that I could get the current directory of the file with
echo expand('%:p:h') but how could I get this to evaluate in the Ack command?
I'd need something like this:
:Ack searchpattern expand('%:p:h')
The expression register, "=, will let you evaluate an expression and put/paste the output. Using <c-r> on the command-line will insert content from a register.
:Ack pat <c-r>=expand('%:p:h')<cr>
For more help see:
:h "=
:h i_CTRL-R
Using :grep instead of :Ack
You can set 'grepprg' to use the silver searcher or other grep-like tool, e.g. ripgrep.
set grepprg=ag\ --vimgrep\ $*
set grepformat=%f:%l:%c:%m
:grep understands % and :h as parameters. This means you can do:
:grep pat %:h
For more help see:
:h 'grepprg'
:h :grep
If directory has no further children (otherwise is recursive search):
nnoremap <leader>f <Esc>:cd %:p:h<CR><Esc>:Ag<CR>
Where,
:cd %:p:h changes directory to the location of current file
:Ag<CR> directly goes to the interactive search window if you have fzf-vim
By "interactive search" I mean customizing your search pattern dynamically (try wildcard, test if adding more keywords, ...)
On the other hand, if you don't need the interactive search, you are sure what you look for, then:
nnoremap <leader>f <Esc>:cd %:p:h<CR><Esc>:Ag<Space>
Use :exe[cute]:
:exe 'Ack searchpattern ' . expand('%:p:h')
. (dot) means string concatenation.
I have a little mapping for cases like this: %% inserts the directory of the current file.
cnoremap <expr> %% filename#command_dir('%%')
And the function definition:
function filename#command_dir(keymap) abort
if getcmdtype() isnot# ':'
return a:keymap
endif
let l:dir = expand('%:h')
return empty(l:dir) ? '.' : (dir.'/')
endfunction

In Vim how to switch quickly between .h and .cpp files with the same name?

Suppose I have a folder with lots of .h and .cpp files. I frequently need to do the following:
open a file prefix_SomeReallyLongFileName.h,
make some changes to it,
and then open prefix_SomeReallyLongFileName.cpp.
I can do this using :e <filename> using auto-complete, but as the prefix is same for many of the files, this becomes inconvenient.
Is there a quick way to open a file with same name as current file, but a different extension?
Do other people come across this situation too, and if so what is your preferred way of navigating the C++ files in a directory? Thanks.
You can use the :r (root) filename modifier which removes the last extension (check out :h filename-modifiers for more information)
:e %:r.cpp
where
% is shorthand for current filename.
:r removes the extension
.cpp simply appends that string at the end.
This effectively substitutes the current file's extension with another, then open the file with the newer extension.
An even shorter way (courtesy of Peter Rincker),
:e %<.cpp
Relevant documentation at :h extension-removal
According to the Vim wiki there are quite a few suggested ways.
I will outline a few options from the article:
a.vim or FSwitch.vim plugins
using ctags
:e %<.c or :e %<.h. %< represents the current file w/o the extension
A quick mapping nnoremap <F4> :e %:p:s,.h$,.X123X,:s,.cpp$,.h,:s,.X123X$,.cpp,<CR>. Add this to your ~/.vimrc.
Install “unimpaired” and then use ]f and [f to go the previous and next file. Since source and header have they same name except for the suffix, they are next and previous files.
This is just using simple(?!) vimscript, so you can put it into your vimrc,
now it works for .c files, but can be modified pretty easily for .cpp (obviously), it even has some "error handling" in the inner if-statements (that is probably pointless), but if anyone needs it, hey, it's there! Without it it's way much shorter (just leave the :e %<.h, for example), so choose whatever you want.
function! HeaderToggle() " bang for overwrite when saving vimrc
let file_path = expand("%")
let file_name = expand("%<")
let extension = split(file_path, '\.')[-1] " '\.' is how you really split on dot
let err_msg = "There is no file "
if extension == "c"
let next_file = join([file_name, ".h"], "")
if filereadable(next_file)
:e %<.h
else
echo join([err_msg, next_file], "")
endif
elseif extension == "h"
let next_file = join([file_name, ".c"], "")
if filereadable(next_file)
:e %<.c
else
echo join([err_msg, next_file], "")
endif
endif
endfunction
then add further to your vimrc something along these lines:
let mapleader = "," " <Leader>
nnoremap <Leader>h :call HeaderToggle()<CR>
Now whenever you're in normal mode, you press comma , (this is our <Leader> button) then h and function from the above gets called, and you will toggle between files. Tada!
Adding my two cents ;) to the above great answers:
Install Exuberant Ctags
Put the following code into your .vimrc
" Jump to a file whose extension corresponds to the extension of the current
" file. The `tags' file, created with:
" $ ctags --extra=+f -R .
" has to be present in the current directory.
function! JumpToCorrespondingFile()
let l:extensions = { 'c': 'h', 'h': 'c', 'cpp': 'hpp', 'hpp': 'cpp' }
let l:fe = expand('%:e')
if has_key(l:extensions, l:fe)
execute ':tag ' . expand('%:t:r') . '.' . l:extensions[l:fe]
else
call PrintError(">>> Corresponding extension for '" . l:fe . "' is not specified")
endif
endfunct
" jump to a file with the corresponding extension (<C-F2> aka <S-F14>)
nnoremap <S-F14> :call JumpToCorrespondingFile()<CR>
inoremap <S-F14> <C-o>:call JumpToCorrespondingFile()<CR>
" Print error message.
function! PrintError(msg) abort
execute 'normal! \<Esc>'
echohl ErrorMsg
echomsg a:msg
echohl None
endfunction
https://github.com/ericcurtin/CurtineIncSw.vim is an option.
Once configured searches the current directory recursively and the directory your source file is in recursively for the file you want to switch to.
You can switch from .cc to .h files with :VH.

Vim: Vimscript to call netrw command

This is a followup (but a distinct question) to this question, which I'll reiterate here for completion.
I have a Vim mapping to start searching (ack-grep with ack.vim plugin) for a pattern from the directory that is the current directory (so the result after :pwd). This mapping works when I'm looking at a buffer.
I want to use the same mapping while I'm in netrw. But, I want to change the current directory (:pwd) to the directory netrw is showing me, so the search will be started from the directory I'm looking at. I know I can do this with the netrw c command. How do I give the c command from within a function?
I've tried:
function! StartAckSearch()
" If we're in netrw change the current directory to the directory we're
" viewing
if &ft ==# 'netrw'
echo 'in netrw'
c
endif
endfunction
nnoremap <Leader>a :call StartAckSearch()<CR>
And:
I've tried:
function! StartAckSearch()
" If we're in netrw change the current directory to the directory we're
" viewing
if &ft ==# 'netrw'
echo 'in netrw'
execute 'c'
endif
endfunction
nnoremap <Leader>a :call StartAckSearch()<CR>
But they both don't work.
Question
How do I call a netrw command using Vimscript? (If my question can be rephrased to be clearer, please go ahead)
I think you can use norm c to call it.
Another way is exe 'norm c'

How to redirect stdout output to a new Vim tab?

I'm editing an XML file in Vim, and then I want to transform it a plain-text file with xsltproc, which by default outputs to a stdout (something like : !xsltproc TXTRULE.XSL %). Is it possible to redirect that xsltproc output to a new tab in Vim without creating any intermediate files?
(I've tried to read :help redir and some wiki notes, but still can't get it. would be greateful for some kind of simple example.)
You can use read like in the following:
:read !ls
Obviously you should change ls with your command. If you want to open a new tab prepend tabnew with a bar to the command like:
:tabnew|read !ls
To expand on lucapette's answer, you could create a map like this:
:map ,x :tabnew<Bar>read !xsltproc TXTRULE.XSL #
# expands to the previously opened buffer, which is the file you were editing, while % would expand to the new buffer opened by :tabnew.
<Bar> has to be used instead of |, because otherwise, the :map command would end at the |.
I am using the following to view my program outputs (very useful for a makefile with a make run rule)
It opens a new tab next to current one only if one was not already opened before for that purpose:
fu! RedirStdoutNewTabSingle(cmd)
let a:newt= expand('%:p') . ".out.tmp"
tabnext
if expand('%:p') != a:newt
tabprevious
exec "tabnew" . a:newt
else
exec "%d"
endif
exec 'silent r !' . a:cmd
set nomodified
endfunc
au FileType xml noremap <buffer> <F6> :call RedirStdoutNewTabSingle("xsltproc")<CR>

how to get :bwipe *.ext to wipe everyone matching the wildcard in vim

I often want to wipe all buffers loaded with a given extension (usually .rej files produced by patch). Just doing :bw[!] *.rej will complain if there is more than one match. Does anyone have any good tips? Currently I either repeatedly use :bw *.rej + tab-complete or, if there are a lot of buffers, use :ls and :bw a set of buffers by number.
Globbing in vim is a bit difficult (apart from for files on the file system). Therefore, the best way seems to be to convert the wildcard into a regular expression and then check each buffer in the buffer list to see whether it matches. Something like this:
" A command to make invocation easier
command! -complete=buffer -nargs=+ BWipe call BWipe(<f-args>)
function! BWipe(...)
let bufnames = []
" Get a list of all the buffers
for bufnumber in range(0, bufnr('$'))
if buflisted(bufnumber)
call add(bufnames, bufname(bufnumber))
endif
endfor
for argument in a:000
" Escape any backslashes, dots or spaces in the argument
let this_argument = escape(argument, '\ .')
" Turn * into .* for a regular expression match
let this_argument = substitute(this_argument, '\*', '.*', '')
" Iterate through the buffers
for buffername in bufnames
" If they match the provided regex and the buffer still exists
" delete the buffer
if match(buffername, this_argument) != -1 && bufexists(buffername)
exe 'bwipe' buffername
endif
endfor
endfor
endfunction
It can be used as:
:BWipe *.rej
or:
:BWipe *.c *.h
By the way, I ended up going with a very low-tech solution (I personally like to modify vim as little as possible so that I am at home on any machine):
I added a mapping:
:map <C-f> :bw *.rej
Then I repeatedly press
<C-f> <Tab> <CR>
a little bit around the corner, but works for me, with a lot of files:
:mksession!
:q
vi Session.vim
a) remove all 'badd' lines with files you do not want
b) :wq
vim -S Session.vim # restart with the state you had, but without the files
Do CTRL-A to insert all the matches of the pattern in front of the cursor. See :help c_CTRL-A. For example, if you have the files a.rej, b.rej, and c.rej loaded, then doing
:bw *.rej<C-A>
will leave you with
:bw a.rej b.rej c.rej
Then you can press Enter to wipe those buffers.

Resources