How to test if there is only default buffer open? - vim

I'm writing a plugin that loads sessions only if no buffers have been open. So far I'm testing vs argc.
if (argc() != 0)
return
endif
This works fine if I pass in arguments from the command line: vim file1
However, the problem is that I have some scripts that wrap around vim:
function foo {
vim file1
}
$ foo
In the latter case, argc is empty and thus the above if condition fails.
How do you test if vim was invoked with only the default buffer open?

If you pass files to Vim, that will be reflected in argc (also when you wrap the Vim invocation). Rather, the corner case is launching Vim with an edit command, e.g. vim -c "edit foo". If you need to detect that, you need to check two things:
the current buffer is the default blank buffer
no other text buffers have been loaded
Here's a set of functions to implement that:
function! IsBlank( bufnr )
return (empty(bufname(a:bufnr)) &&
\ getbufvar(a:bufnr, '&modified') == 0 &&
\ empty(getbufvar(a:bufnr, '&buftype'))
\)
endfunction
function! ExistOtherBuffers( targetBufNr )
return ! empty(filter(range(1, bufnr('$')), 'buflisted(v:val) && v:val != a:targetBufNr'))
endfunction
function! IsEmptyVim()
let l:currentBufNr = bufnr('')
return IsBlank(l:currentBufNr) && ! ExistOtherBuffers(l:currentBufNr)
endfunction

The best I could come up with was to write a function. I thought bufnr('$') would do it but it lists that highest loaded buffer number. The bufnr('$') function returns 1 even though I have not opened a file (simply launched vim). Also buffers can be unloaded with :bw, which does not change what bufnr('$') returns.
Anyway this is the function:
function! NumBuffers()
let rc = 0
for idx in range(bufnr('$'))
if bufloaded(idx)
let rc += 1
endif
endfor
return rc
endfunction

Related

Vim how to leave cursor at the end of the line after autocommand

I'm trying to make my own snippets in pure vimscript using autocommands. I want to make it so that after I type a specific character sequence, it is replaced by another character sequence. I made one like this, that replaces "hello" with "bye" when you type it.
function F()
if (strpart (getline('.'), 0, col('.')-1) =~ 'hello$')
execute "normal 5i\<Backspace>"
normal! abye
normal! l
endif
endfunction
autocmd TextChangedI *.tex call F()
And it works fine if there are characters after the cursor. However, if I am writing at the end of the line, after the change the cursor is between 'y' and 'e', because the autocommand calls the function, the cursor is at the end of the line, and then it enters insert mode which starts inserting before the last character.
How can I make it so that the cursor is always left after 'bye'? I don't want to use iabbrev for a couple of reasons, one being that it doesn't expand as soon as I type.
I can do it with the option
set ve+=onemore
but I don't like it's effects on normal writing.
How about the following, which uses setline and cursor functions rather than normal keymaps:
function! F() abort
let [l:line, l:col] = [getline('.'), col('.')]
if strpart(l:line, 0, l:col-1) =~ 'hello$'
let l:left = strpart(l:line, 0, l:col-6)
let l:right = strpart(l:line, l:col-1)
call setline('.', l:left . 'bye' . l:right)
call cursor('.', l:col-2) " 2 = length diff between hello and bye
endif
endfunction
This seems working for me (on Neovim 0.6).

How to prompt a user for multiple entries in a list?

I'm working on a vim plugin. There are a set of valid options for a configuration parameter. I would like the user to be able to select the options they want from a list.
This is similar to inputlist, but inputlist only returns the index of the single chosen element. I'd prefer it return the indexes of all chosen elements.
How would I create a mutliselect in vim?
I don't know exactly which kind of interface you have in mind, but since you mentioned inputlist(), I thought you could simply write a loop whose body would invoke it.
Maybe something like this:
let options_chosen = []
let options_valid = [
\ 'foo',
\ 'bar',
\ 'baz',
\ 'qux',
\ 'norf'
\ ]
for i in range(1,len(options_valid))
let choice = inputlist([ 'Select your options:' ]
\ + map(copy(options_valid), '(v:key+1).". ".v:val'))
if choice >= 1 && choice <= len(copy(options_valid))
let options_chosen += [copy(options_valid)[choice - 1]]
let options_valid = filter(options_valid, 'v:val !=# options_chosen[-1]')
else
break
endif
redraw
endfor
If you execute this code, it should let you choose an option from the list options_valid. After each iteration, it should add the chosen item inside the list options_chosen and remove it from the list options_valid. The loop iterates as many times as there are items in options_valid initially. When you're done, you can stop the loop by hitting Escape.
It may not be what you want, because I don't know what interface you want to present to the user: a command, a mapping, an interactive buffer... But it may be a start, upon which you could build something else.
With an interactive buffer as the interface, I came up with this:
let s:options_valid = ['foo', 'bar', 'baz', 'qux', 'norf']
com! MultipleOptions call s:multiple_options()
fu! s:multiple_options() abort
vnew | exe 'vert resize '.(&columns/3)
setl bh=wipe bt=nofile nobl noswf nowrap
if !bufexists('Multiple Options') | sil file Multiple\ Options | endif
sil! 0put =s:options_valid
sil! $d_
setl noma ro
nno <silent> <buffer> <nowait> q :<c-u>close<cr>
nno <silent> <buffer> <nowait> <cr> :<c-u>call <sid>toggle_option()<cr>
augroup multi_op_close
au!
au WinLeave <buffer> call s:close()
augroup END
endfu
fu! s:close() abort
let g:selected_options = exists('w:options_chosen')
\ ? map(w:options_chosen.lines, 's:options_valid[v:val-1]')
\ : []
au! multi_op_close | aug! multi_op_close
close
endfu
fu! s:toggle_option() abort
if !exists('w:options_chosen')
let w:options_chosen = { 'lines' : [], 'pattern' : '', 'id' : 0 }
else
if w:options_chosen.id
call matchdelete(w:options_chosen.id)
let w:options_chosen.pattern .= '|'
endif
endif
if !empty(w:options_chosen.lines) && count(w:options_chosen.lines, line('.'))
call filter(w:options_chosen.lines, "v:val != line('.')")
else
let w:options_chosen.lines += [ line('.') ]
endif
let w:options_chosen.pattern = '\v'.join(map(
\ copy(w:options_chosen.lines),
\ "'%'.v:val.'l'"
\ ), '|')
let w:options_chosen.id = !empty(w:options_chosen.lines)
\ ? matchadd('IncSearch', w:options_chosen.pattern)
\ : 0
endfu
If you execute the command :MultipleOptions, it should open a temporary vertical viewport, in which the options stored inside the list s:options_valid should be displayed.
From there, you can hit Enter to select or deselect the current line. When an option is selected, its line is colored with the highlighting group IncSearch.
When you're done, you can close the window hitting q, and all your chosen options should be inside g:selected_options.
In lh-vim-lib I provide a lh#ui#check() function that does exactly that. Its behaviour is similar to confirm() in text mode. i.e. I rely on an old trick that hacks the statusline. (This needs to play with "\r", :redraw and so on)
You can see it live in the screencast I've made for lh-tags. Wait for the "Which kinds to you wish to display?" question. (In the screencast you should see the old code with CHECK, CONFIRM and CHOOSE)
BTW, the dialog used in lh-tags to choose one entry here can also be used (with the tagged parameter set to one) to select several entries at once.

Favourite places in vim

Is there a command in vim that can bookmark a place (path to the file, line number in that file), so that I can go to that place easily later?
It would be similar as NERDTree :Bookmark command. You can open your file with NERDTreeFromBookmark. I'm looking for the same functionality with the difference that bookmark is not only a file but file + line number.
Thank you
Yes you can do so with the 'mark' command. There are two types of bookmarks you can create, local and global. You are referring to a global bookmark.
You can type 'mP' to create a bookmark called P. Notice the case, uppercase indicates it is a global bookmark. To go to that bookmark, type `P.
Hope this helps
Source
The viminfo setting can contain the option !, which makes it store any global variables with uppercase letters in the viminfo file. Using this, you can define a variable called g:BOOKMARKS and store your bookmarks in there.
Here's some vimscript you could use to do that:
set viminfo+=!
if !exists('g:BOOKMARKS')
let g:BOOKMARKS = {}
endif
" Add the current [filename, cursor position] in g:BOOKMARKS under the given
" name
command! -nargs=1 Bookmark call s:Bookmark(<f-args>)
function! s:Bookmark(name)
let file = expand('%:p')
let cursor = getpos('.')
if file != ''
let g:BOOKMARKS[a:name] = [file, cursor]
else
echom "No file"
endif
wviminfo
endfunction
" Delete the user-chosen bookmark
command! -nargs=1 -complete=custom,s:BookmarkNames DelBookmark call s:DelBookmark(<f-args>)
function! s:DelBookmark(name)
if !has_key(g:BOOKMARKS, a:name)
return
endif
call remove(g:BOOKMARKS, a:name)
wviminfo
endfunction
" Go to the user-chosen bookmark
command! -nargs=1 -complete=custom,s:BookmarkNames GotoBookmark call s:GotoBookmark(<f-args>)
function! s:GotoBookmark(name)
if !has_key(g:BOOKMARKS, a:name)
return
endif
let [filename, cursor] = g:BOOKMARKS[a:name]
exe 'edit '.filename
call setpos('.', cursor)
endfunction
" Completion function for choosing bookmarks
function! s:BookmarkNames(A, L, P)
return join(sort(keys(g:BOOKMARKS)), "\n")
endfunction
I'm not sure how readable the code is, but basically, the Bookmark command accepts a single parameter to use as a name. It will store the current filename and cursor position to the g:BOOKMARKS dictionary. You can use the GotoBookmark command with a mark name to go to it. DelBookmark works in the same way, but deletes the given mark. Both functions are tab-completed.
Another way to jump through them is by using this command:
" Open all bookmarks in the quickfix window
command! CopenBookmarks call s:CopenBookmarks()
function! s:CopenBookmarks()
let choices = []
for [name, place] in items(g:BOOKMARKS)
let [filename, cursor] = place
call add(choices, {
\ 'text': name,
\ 'filename': filename,
\ 'lnum': cursor[1],
\ 'col': cursor[2]
\ })
endfor
call setqflist(choices)
copen
endfunction
CopenBookmarks will load the bookmarks in the quickfix window, which seems like a nice interface to me.
This solution is similar to Eric's -- it uses the .viminfo file, so if something goes wrong with it, you'll probably lose your marks. And if you save your marks in one vim instance, they won't be immediately available in another.
I don't know how comfortable your are with vimscript, so just in case -- to use this, you can put the code in a file under your plugin vimfiles directory, for example plugin/bookmarks.vim. Should be completely enough. Here's the entire code in a gist as well: https://gist.github.com/1371174
EDIT: Changed the interface for the solution a bit. Original version can be found in the gist history.
I have used this script (number marks). There might be better ones though. Wait for other answers!
This doesn't solve your problem as stated, but you may find it helps.
MRU.vim - Most Recently Used files plugin
Type :MRU and you get a nice searchable list of your most recently used files. Pressing enter on one brings you to it.
" When editing a file, always jump to the last known cursor position.
" And open enough folds to make the cursor is not folded
" Don't do it when the position is invalid or when inside an event handler
" (happens when dropping a file on gvim).
autocmd BufWinEnter *
\ if line("'\"") <= line("$") |
\ exe "normal! g`\"" | exe "normal! zv" |
\ endif

vim: jump to buffer that contains /string/

I normally have quite a few buffers opened, which I navigate using combination of Bufexplorer and FuzzyFinder. Finding the right buffer still involves going through file names. But often, it could be much easier to say something like 'jump to buffer that contains "wip"'. Anyone knows how?
I am using a small function I put inside my .vimrc:
function! s:GrepOpenBuffers(search, jump)
call setqflist([])
let cur = getpos('.')
silent! exe 'bufdo vimgrepadd /' . a:search . '/ %'
let matches = len(getqflist())
if a:jump && matches > 0
sil! cfirst
else
call setpos('.', cur)
endif
echo 'BufGrep:' ((matches) ? matches : 'No') 'matches found'
endfunction
com! -nargs=1 -bang BufGrep call <SID>GrepOpenBuffers('<args>', <bang>0)
You could use something like the above to grep for a search term in all opened buffers.
Check out buffer grep: http://www.vim.org/scripts/script.php?script_id=2545

how to remove file name from VIM dictionary menu?

I've installed pydiction dictionary in vim so that I'd be able to get a list of python commands when I press tab after partially typed command. Everything is working fine, except every time the menu shows up, there is a file name besides the each command in the list. How do I remove that filename from the menu?
plz, take a look at the picture: http://www.uzbozor.com/uploads/vim.png
(copy and paste the link if clicking doesn't work)
Thanks
I haven't managed to solve this very elegantly, but there's a workaround by writing a custom completion function that simply greps the dictionary file for matches:
function! MyCompleteFunction( findstart, base )
if a:findstart
let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '[A-Za-z_]'
let start -= 1
endwhile
return start
else
silent call DictGrep( a:base, 'path\to\dictionary\file' )
let matches = []
for thismatch in getqflist()
call add(matches, thismatch.text)
endfor
return matches
endif
endfunction
Note that I have defined a function DictGrep() that actually performs the vimgrep. This is so I can call it silently and not be troubled by error messages:
function! DictGrep( leader, file )
try
exe "vimgrep /^" . a:leader . ".*/j " . a:file
catch /.*/
echo "no matches"
endtry
endfunction
Then simply define set the completefunc:
setlocal completefunc=MyCompleteFunction()
and then use for insert-mode completion (which could be mapped to replace your current dictionary completion binding).
The vimgrep could be quite a slow operation, but I haven't noticed any problems unless there are hundreds of matches in the dictionary file.
Hope this helps.

Resources