Vim script: Buffer/CheatSheet Toggle - vim

I want to make a vim cheat sheet plugin. It's real simple:
I want to toggle my cheatsheets. A vertsplit toggle, like Taglist or NERDTree.
I want the cheatsheet to be filetype specific. So I toggle my c++ cheatsheet when I have opened a .cpp file.
I want the cheatsheet to be horizontally split. So it shows two files, my syntax cheat sheet and my snippet trigger cheat sheet.
I already have a collection of these cheatsheets, in vimhelp format, but now I have to manually open them.
I haven't really done any vim scripting, but I imagine this would be really simple to put together. I'm sorta sick of googling unrelated codesnippets, so what I'm asking here is:
Could anyone give me a short sum-up of what I need to learn in regards to vim scripting to piece this together. What I have a hard time finding is how to toggle the buffer window.
If you know any intro tutorials that covers the material I need to get this up and running, please provide a link.
tx,
aktivb

The function below may not do exactly what you want, and I haven't tested it, but it should give you some ideas.
The main idea is that the function reads the filetype of the current buffer (you can test this by typing :echo &ft) and then sets the path of the appropriate cheat sheat. If it exists, this path is then opened (read-only and non-modifiable) in a split window. You can then call this function any way you wish, for example by mapping it to the {F5} key as shown.
I'm not sure about the toggling possibilities (is this really easier than just closing the split window?) but you could look at the bufloaded() function, which returns whether or not a given file is currently being accessed.
function! Load_Cheat_Sheet()
let l:ft = &ft
if l:ft == 'html'
let l:path = 'path/to/html/cheat/sheet'
elseif l:ft == 'c'
let l:path = 'path/to/c/cheat/sheet'
elseif l:ft == 'tex'
let l:path = 'path/to/tex/cheat/sheet'
endif
if l:path != '' && filereadable(l:path)
execute ':split +setlocal\ noma\ ro ' l:path
endif
endfunction
map <F5> :call Load_Cheat_Sheet()<CR>
Hope this helps. Just shout if anything is unclear, or you want to know more.

I had forgotten about this until I got a notice about Eduan's answer. Since I posted this question I've done quite a bit of vim scripting, including getting this to work:
let g:cheatsheet_dir = "~/.vim/bundle/cheatsheet/doc/"
let g:cheatsheet_ext = ".cs.txt"
command! -nargs=? -complete=customlist,CheatSheetComplete CS call ToggleCheatSheet(<f-args>)
nmap <F5> :CS<CR>
" strip extension from complete list
function! CheatSheetComplete(A,L,P)
return map(split(globpath(g:cheatsheet_dir, a:A.'*'.g:cheatsheet_ext)),
\ "v:val[".strlen(expand(g:cheatsheet_dir)).
\ ":-".(strlen(g:cheatsheet_ext) + 1)."]")
endfun
" specify cheatsheet or use filetype of open buffer as default
" instead of saving window status in a boolean variable,
" test if the file is open (by name). If a boolean is used,
" you'll run into trouble if you close the window manually with :wq etc
function! ToggleCheatSheet(...)
if a:0
let s:file = g:cheatsheet_dir.a:1.g:cheatsheet_ext
else
if !exists("s:file") || bufwinnr(s:file) == -1
let s:file = g:cheatsheet_dir.&ft.g:cheatsheet_ext
endif
endif
if bufwinnr(s:file) != -1
call ToggleWindowClose(s:file)
else
call ToggleWindowOpen(s:file)
endif
endfun
" stateless open and close so it can be used with other plugins
function! ToggleWindowOpen(file)
let splitr = &splitright
set splitright
exe ":vsp ".a:file
exe ":vertical resize 84"
if !splitr
set splitright
endif
endfun
function! ToggleWindowClose(file)
let w_orig = bufwinnr('%')
let w = bufwinnr(a:file)
exe w.'wincmd w'
exe ':silent wq!'
if w != w_orig
exe w_orig.'wincmd w'
endif
endfun

Thought I would add to Goulash's answer.
I think in order to implement the toggle you would simply use some if statements and a global variable.
let g:cheatsheet_toggle_on=0
if (g:cheatsheet_toggle_on == 0)
" Turn the cheatsheet on
" Also make sure to know that the toggle is on:
let g:cheatsheet_toggle_on=1
elseif (g:cheatsheet_toggle_on=1
" Do whatever you need to turn it off, here
endif
Hope this figures out that logic. :)

Related

Vim highlight a set of words in different colors each in all files

I need vim/Gvim to highlight a set of keywords each with mentioned color in all files (it may be a text file, c source file, or anything else).
For example TODO, FIXME are highlighted in C files. Like that, I want to highlight TODO and FIXME in all files each with different colors specified somewhere. This should happen as I open a vim file and do not require me to give a command for this to happen.
How can I achieve this?
Thanks in advance for the help.
Note this is not my answer. Points go to user Ralf, answer copied over from this stackexchange, adding it here for convenience. This is the best solution I could test (if what you want is add custom highlighted words for ALL syntaxes)
function! UpdateTodoKeywords(...)
let newKeywords = join(a:000, " ")
let synTodo = map(filter(split(execute("syntax list"), '\n') , { i,v -> match(v, '^\w*Todo\>') == 0}), {i,v -> substitute(v, ' .*$', '', '')})
for synGrp in synTodo
execute "syntax keyword " . synGrp . " contained " . newKeywords
endfor
endfunction
augroup now
autocmd!
autocmd Syntax * call UpdateTodoKeywords("NOTE", "NOTES")
augroup END

Prevent cursor jumping to bottom when the buffer is replaced

I have content stored in a variable (out) which I want to replace with the current buffer. I'm currently doing it like this (simplified version):
let splitted = split(out, '\n')
if line('$') > len(splitted)
execute len(splitted) .',$delete'
endif
call setline(1, splitted)
(Detailed: https://github.com/fatih/vim-go/blob/master/autoload/go/fmt.vim#L130)
However setline() here causes slowness on some machines and https://github.com/fatih/vim-go/issues/459. I've profilde it myself but for me setline was not a problem. Anyway, I need a solution which is more faster. So I've come up with several other solutions.
First one is, which puts the output to a register, deletes all lines and then puts it back:
let #a = out
% delete _
put! a
$ delete _
Second solution would be using append() (which was used previously in vim-go https://github.com/fatih/vim-go/commit/99a1732e40e3f064300d544eebd4153dbc3c60c7):
let splitted = split(out, '\n')
%delete _
call append(0, splitted)
$delete _
They both work! However they both also causes a side effect which I'm still couldn't solve and is also written in the title. The problem is described as:
If a buffer is opened in another view (say next to next), and
we call one of the two solutions above, it breaks the cursor of
the other view and jumps to the bottom
Here is a GIF showing it better (whenever I call :w one of the procedures above is called): http://d.pr/i/1buDZ
Is there a way, to replace the content of a buffer, which is fast and doesn't break the layout? Or how can I prevent it with one of the procedures above?
Thanks.
Did you try winsaveview() and winrestview()?
:let old_view=winsaveview()
:% delete _
:put! =out
:$ delete _
:call winrestview(old_view)
However I don't know anything about pasting text in a quicker way
Try using the redraw command.
I have faced similar issues of strange delays a few times, where profiling doesn't shows anything suspicious. But the redraw command solved it in most cases and it doesn't disrupt the window layout (the last time I found this problem was in vim-addon-qf-layout plugin).
If the problem still happens you could try using the following approach, which is slight different from your first example; I've been using it for quite some time without any delays:
function! s:setCurrentLine(content)
silent put =a:content
" delete original line
silent '[-1delete _
endfunction
What about this? It saves the view for each window with the current buffer opened inside, then restores all the views after the modifications. It seems to work for me.
function! BufListSave()
let cur_buf = winbufnr(0)
let cur_tab = tabpagenr()
let buflist = []
for i in range(tabpagenr('$'))
let tab_array = []
let tab_buflist = tabpagebuflist(i+1)
for j in range(len(tab_buflist))
if tab_buflist[j] == cur_buf
exe "tabn ".(i+1)
let cur_win = winnr()
exe (j+1)."wincmd w"
call add(tab_array, {"win":j+1, "view":winsaveview()})
exe cur_win."wincmd w"
endif
endfor
call add(buflist, tab_array)
endfor
exe "tabn ".cur_tab
return buflist
endfunction
function! BufListRest(buflist)
let cur_tab = tabpagenr()
for i in range(len(a:buflist))
let tab_array = a:buflist[i]
if len(tab_array) == 0
continue
endif
exe "tabn ".(i+1)
let cur_win = winnr()
for wi in tab_array
exe "".wi['win']."wincmd w"
call winrestview(wi['view'])
endfor
exe cur_win."wincmd w"
endfor
exe "tabn ".cur_tab
endfunction
function! Do_It()
let buf_list = BufListSave()
%delete _
put! =out
$delete _
call BufListRest(buf_list)
endfunction
function! Do_It_Silently()
silent call Do_It()
endfunction

How to obtain command completion list

I created unite source cmdmatch which lets you among other things fuzzy complete wildmenu items. I would like to find a better method to obtain the completion list (method I use is given here and is problematic because with large number of completion items screen will be filled up entirely [to see why just press :<c-a> if you didn't remap <c-a>])
The other solution would be to hide vim's cmd line completely while I am grabbing the list although I don't think that is possible in vim (or at least limit the amount of text it displays so it doesn't fill up screen). Any ideas ?
EDIT
First try has the same flickering problem although it looks like it works faster
fu! GetCompletion(input)
call feedkeys(":" . a:input . "FF\<esc>\<cr>")
endf
cno FF <C-A><C-\>eg:Save()<CR>
fu! g:Save()
let g:x = getcmdline()
endf
Result can be seen as:
:call GetCompletion('help a')
:echo x
SOLUTION
let g:cmdmatch = {}
fu! g:cmdmatch.set_c( base ) dict
let self.c = a:base
endfu
fu! GetCommandCompletion( base )
cno [MATCH] <c-a><c-\>eg:cmdmatch.set_c(getcmdline())<cr>
sil! exe 'norm :' . a:base . '[MATCH]'
cu [MATCH]
retu g:unite_cmdmatch.c
endf
Test: echo GetCommandCompletion("help '")
The trick is to execute the entire completion with :silent, so that the output won't actually happen (but completion is still magically done). The following function retrieves the completions already parsed into a List, by triggering the completion and then wrapping the output in :return split('...'). Of course, you can also :return '...' if you need a single string.
function! GetCommandCompletion( base )
silent execute "normal! :" a:base . "\<C-a>')\<C-b>return split('\<CR>"
endfunction
demo
:let cmds = GetCommandCompletion('no')
:echo cmds
['noautocmd', 'nohlsearch', 'noreabbrev', 'noremap', 'noremenu', 'normal']

UltiSnips and YouCompleteMe

I have bundles ultisnips and youcompleteme installed on my macvim.
The problem is that ultisnips doesn't work because tab is bound by ycm.
I tried putting let g:UltiSnipsExpandTrigger = "<s-tab>" so that I can trigger the snippet completion with shift-tab, but it doesn't work for some unknown reason. I could use caps as the trigger, but so far I've found no way to do that.
Do any of you use those two add-ons together?
What can I do to make shift-tab work?
Can you recommend another key to trigger snippets?
Another option is using the SuperTab plugin:
" if you use Vundle, load plugins:
Bundle 'ervandew/supertab'
Bundle 'Valloric/YouCompleteMe'
Bundle 'SirVer/ultisnips'
" make YCM compatible with UltiSnips (using supertab)
let g:ycm_key_list_select_completion = ['<C-n>', '<Down>']
let g:ycm_key_list_previous_completion = ['<C-p>', '<Up>']
let g:SuperTabDefaultCompletionType = '<C-n>'
" better key bindings for UltiSnipsExpandTrigger
let g:UltiSnipsExpandTrigger = "<tab>"
let g:UltiSnipsJumpForwardTrigger = "<tab>"
let g:UltiSnipsJumpBackwardTrigger = "<s-tab>"
Here YouCompleteMe is bound to a different combination Ctrln, but then that combination is bound to tab through SuperTab. UltiSnips and SuperTab play nice together, so you can then just bind UltiSnips to tab directly and everything will work out.
Try this suggestion on a page from the YouCompleteMe issue tracker. In your .vimrc:
let g:UltiSnipsExpandTrigger="<c-j>"
While this setting will make expanding a snippet share the default mapping for jumping forward within a snippet, it simulates TextMates' behavior as mentioned in the UltiSnips help tags.
Since I've mapped my Caps Lock key to Ctrl, this mapping works pretty smoothly.
copy the following code to your vimrc, and enjoy. This function will handle all issues between YCM and UltiSnips.
function! g:UltiSnips_Complete()
call UltiSnips#ExpandSnippet()
if g:ulti_expand_res == 0
if pumvisible()
return "\<C-n>"
else
call UltiSnips#JumpForwards()
if g:ulti_jump_forwards_res == 0
return "\<TAB>"
endif
endif
endif
return ""
endfunction
au BufEnter * exec "inoremap <silent> " . g:UltiSnipsExpandTrigger . " <C-R>=g:UltiSnips_Complete()<cr>"
let g:UltiSnipsJumpForwardTrigger="<tab>"
let g:UltiSnipsListSnippets="<c-e>"
" this mapping Enter key to <C-y> to chose the current highlight item
" and close the selection list, same as other IDEs.
" CONFLICT with some plugins like tpope/Endwise
inoremap <expr> <CR> pumvisible() ? "\<C-y>" : "\<C-g>u\<CR>"
i have this in my vimrc
"" YouCompleteMe
let g:ycm_key_list_previous_completion=['<Up>']
"" Ultisnips
let g:UltiSnipsExpandTrigger="<c-tab>"
let g:UltiSnipsListSnippets="<c-s-tab>"
thats what i did on my first try, but i misspelled UltiSnips with Ultisnips.. oh well, worked out in the end!
I personally chose to not use <tab> with YouCompleteMe but navigate it manually.
So I added this to my .vimrc:
let g:ycm_key_list_select_completion=[]
let g:ycm_key_list_previous_completion=[]
which simply disables the tab key for YCM. Instead you use the movement keys (arrows or CTRL-N/CTRL-P) and select the entry with CR. UltiSnips works default with tab.
Just putting together answers by Michaelslec, Joey Liu and along with solutions I found in this issue thread and this guy's vimrc, I now have this which solved pretty much all problems.
function! g:UltiSnips_Complete()
call UltiSnips#ExpandSnippet()
if g:ulti_expand_res == 0
if pumvisible()
return "\<C-n>"
else
call UltiSnips#JumpForwards()
if g:ulti_jump_forwards_res == 0
return "\<TAB>"
endif
endif
endif
return ""
endfunction
function! g:UltiSnips_Reverse()
call UltiSnips#JumpBackwards()
if g:ulti_jump_backwards_res == 0
return "\<C-P>"
endif
return ""
endfunction
if !exists("g:UltiSnipsJumpForwardTrigger")
let g:UltiSnipsJumpForwardTrigger = "<tab>"
endif
if !exists("g:UltiSnipsJumpBackwardTrigger")
let g:UltiSnipsJumpBackwardTrigger="<s-tab>"
endif
au InsertEnter * exec "inoremap <silent> " . g:UltiSnipsExpandTrigger . " <C-R>=g:UltiSnips_Complete()<cr>"
au InsertEnter * exec "inoremap <silent> " . g:UltiSnipsJumpBackwardTrigger . " <C-R>=g:UltiSnips_Reverse()<cr>"
Based on Siegfried's answer, I am using the following which seems more natural:
let g:ycm_key_list_select_completion = ['<C-j>']
let g:ycm_key_list_previous_completion = ['<C-k>']
let g:UltiSnipsExpandTrigger = "<C-l>"
let g:UltiSnipsJumpForwardTrigger = "<C-j>"
let g:UltiSnipsJumpBackwardTrigger = "<C-k>"
I also use the c-hjkl bindings somewhere else (switching from a pane to another), but that would only be in normal mode, so there's no problem.
Although I know this post is a little old, I have my own function that is a little more optimized than the one given above:
function! g:UltiSnips_Complete()
call UltiSnips#ExpandSnippetOrJump()
if g:ulti_expand_or_jump_res == 0
if pumvisible()
return "\<C-N>"
else
return "\<TAB>"
endif
endif
return ""
endfunction
Of course, if you just keep the settings that Joey Liu provided and then just use this function everything will work just perfectly!
EDIT: Also, I use another function to increase back-stepping functionality between YouCompleteMe and UltiSnips. I'll show you what I mean:
function! g:UltiSnips_Reverse()
call UltiSnips#JumpBackwards()
if g:ulti_jump_backwards_res == 0
return "\<C-P>"
endif
return ""
endfunction
Then just put this in your .vimrc:
au BufEnter * exec "inoremap <silent> " . g:UltiSnipsJumpBackwardTrigger . " <C-R>=g:UltiSnips_Reverse()<cr>"
As well as let g:UltiSnipsJumpBackwardTrigger="<s-tab>" and your set!
I use both of them together. By default YouCompleteMe binds <Tab> and <Down> to select the next completion item and also <S-Tab> and <Up> to select the previous completion item. You can change the YouCompleteMe bindings with the g:ycm_key_list_select_completion and g:ycm_key_list_previous_completion options. Note that the names of these options were recently changed when the option was changed from a single string to a list of strings.
While Many answer works fine in this post, I just want to say that the problem is caused by key binding collision between YCM and UltiSnip, while YCM support UltiSnip snippets by default, it takes the default UltiSnip expand trigger <tab> as its completion select key, so UltiSnip snippets will not be expaned by <tab>. Give them different key binding will solve the problem, I personally use <c-n and <c-p> for YCM and use the default <tab> for UltiSnip. You can get more details with help youcompleteme doc in vim.
I installed the UltiSnips plugin after the YouCompleteMe plugin so I thought they were conflicting, but in reality I had something more interfering:
set paste
Make sure to remove that from .vimrc if it's present.
I use ; to expand UltiSnips, it's so nifty for me
let g:UltiSnipsExpandTrigger = ";"
I use kj. This is what is in my .vimrc:
let g:UltisnipsExpandTrigger="kj".
It rarely happens that I run into word that has kj in it. If this is the case I would just wait a couple of seconds after typing k and that type j.
As mentioned by others, mapping C-j to ultisnips works great.
let g:UltiSnipsExpandTrigger="<c-j>"
Now, if you go a bit further and install xcape and use
xcape -e "Shift_L=Control_R|J"
You unleash the power of using just the shift key for utlitsnips.

How to modify existing highlight group in vim?

If I have an existing highlight group in vim via link, for example
hi link my_highlight_group my_default_color
Is it possible to add 'bold' to my_highlight_group without changing my_default_color? Following does not work:
hi my_highlight_group gui=bold
Surprisingly, I can add bold if my_highlight group is defined directly (not via link):
hi my_highlight_group guifg=#F0000
hi my_highlight_group gui=bold
As "too much php" has said, there is no direct way to say "create a value that looks like that one and add bold". The best way is to modify your colour scheme. If you're not using a custom colour scheme, copy one from the main vim installation directory to your ~/.vim/colors directory and edit it to suit you. Alternatively, search on the vim scripts page and try some of the many that are available.
Shameless plug: if you want one that's easier to edit than the standard format, try my "Bandit" colour scheme.
If you really want to be able to add bold on the fly, you'll need a fairly complex script, like the one below. Note that this won't be saved for your next session unless you call it automatically after loading your colour scheme or by doing something like:
:autocmd ColorScheme AddBoldToGroup my_highlight_group
The script in all it's enormity is below. As far as I am aware, there is no significantly quicker way of doing this! Obviously you could save a few lines by writing less verbose code, but the general idea of using redir and silent hi repeatedly is the only way.
" Call this with something like
"
" :AddBoldToGroup perlRepeat
"
command! -complete=highlight -nargs=1 AddBoldToGroup call AddBoldToGroup(<f-args>)
function! AddBoldToGroup(group)
" Redirect the output of the "hi" command into a variable
" and find the highlighting
redir => GroupDetails
exe "silent hi " . a:group
redir END
" Resolve linked groups to find the root highlighting scheme
while GroupDetails =~ "links to"
let index = stridx(GroupDetails, "links to") + len("links to")
let LinkedGroup = strpart(GroupDetails, index + 1)
redir => GroupDetails
exe "silent hi " . LinkedGroup
redir END
endwhile
" Extract the highlighting details (the bit after "xxx")
let MatchGroups = matchlist(GroupDetails, '\<xxx\>\s\+\(.*\)')
let ExistingHighlight = MatchGroups[1]
" Check whether there's an existing gui= block
let MatchGroups = matchlist(ExistingHighlight, '^\(.\{-}\) gui=\([^ ]\+\)\( .\{-}\)\?$')
if MatchGroups != []
" If there is, check whether "bold" is already in it
let StartHighlight = MatchGroups[1]
let GuiHighlight = MatchGroups[2]
let EndHighlight = MatchGroups[3]
if GuiHighlight =~ '.*bold.*'
" Already done
return
endif
" Add "bold" to the gui block
let GuiHighlight .= ',bold'
let NewHighlight = StartHighlight . GuiHighlight . EndHighlight
else
" If there's no GUI block, just add one with bold in it
let NewHighlight = ExistingHighlight . " gui=bold"
endif
" Create the highlighting group
exe "hi " . a:group . " " NewHighlight
endfunction
Changing the attributes on a group which is linked to another will disconnect the link. AFAIK there is no easy way to copy the colors from my_default_color into my_highlight_group. You will just have to copy the color values by hand.
This shouldn't be a big issue though, you should have all your highlight groups defined in your colorscheme file, so just put those two next to each other:
hi my_default_color guifg=#000088
hi my_highlight_group guifg=#000088 gui=bold
Years had passed and now the question has a much more straightforward solution: hlget() and hlset() functions.
It's still true that if you want to modify a color scheme, copying it first and modify the copied version would be a better solution.
An example of adding bold attribute to types' highlight:
" Get the highlight group, resolving links
let hl = hlget("Type", v:true)[0]
" Set GUI attributes
let hl.gui = hl->get("gui", {})->extend(#{ bold: v:true })
" Set the highlight group
call hlset([hl])
Assuming you only want this for one syntax type, you should just make a new group name, my_bold_default_color, and apply the bold attribute to that.

Resources