How to modify existing highlight group in vim? - 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.

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

A particular text tagging system in Vim

Amongst other things I use Vim for my work reports. Not exactly reports but I cannot find a better word for it now.
Those are of a form similar to
20-01-2015 14:43h
<bop> <modular system> <iva>
Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report.
02-03-2015 14:43h
<pob> <some other tag> <some other tag 2>
Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report. Text of report.
Date and time. Tags in second row, and the description that goes describing what was done.
Now this can get lengthy, since these entries are added several times a day, and the text can be significantly longer than two or three lines.
Several people read these, not all of them knowledgeable on the subject in hand. Often progress on some subject is tracked by tracking a certain tag, through the file.
Since I often have other things on my mind as well, I sometimes make a mistake and instead of <modular system> tag write <system modular> tag which makes keeping history very difficult.
So I am wondering, is there a way to use Vim's autocompletion to show a list of all tags (one or more words & numbers in < > brackets) so when adding those I can just pick them off the list, therefore avoiding the problem of mistyping them or mixing them up?
Here's my approach. It uses a separate tags file, so you can edit multiple report files and be able to use any tag in any file. It scans the files for tags and saves them as keys in a dict, so autocomplete should be quite fast. Save this as autoload/tagHelper.vim in your runtimepath, then call tagHelper#AddAutocmds() in your .vimrc. You'll have to set up global variables for the location of a tags file and the pattern to recognize these report files.
let tags = {}
if exists('g:tagFile')
let tagFile = glob(g:tagFile)
else
let tagFile = glob('~/tags.txt')
endif
if exists('g:reportPattern')
let reportPattern = g:reportPattern
else
let reportPattern = 'reports/*'
endif
function! tagHelper#LoadTags()
try
let taglist = readfile(tagFile)
catch
let taglist = []
endtry
for tag in taglist
let tags[tag] = 1
endfor
endfunction
function! tagHelper#AddMappings()
inoremap <buffer> > ><Esc>:call tagHelper#AddTagsFromLine(getline('.'))<CR>a
inoremap <buffer> < <<C-x><C-o>
setlocal omnifunc=tagHelper#CompleteTag
endfunction
function! tagHelper#AddTagsFromLine(line)
let matchStart = 0
while matchStart != 0
let nextMatchStart = match(a:line, '<[^>]\+>', matchStart)
let tag = matchstr(a:line, '<[^>]\+>', matchStart)
let tags[tag] = 1
let matchStart = nextMatchStart + strlen(tag)
endwhile
endfunction
function! tagHelper#AddAllTagsInBuffer()
for line in getline(1, '$')
call tagHelper#AddTagsFromLine(line)
endfor
endfunction
function! tagHelper#CompleteTag(findstart, base)
if a:findstart
let line = getline('.')
let col = col('.')
let start = strridx(line, '<', col) + 1
return start
endif
let matches = []
for tag in keys(tags)
if stridx(tolower(tag), tolower(a:base)) == 0
call add(matches, tag)
endif
endfor
return matches
endfunction
function! tagHelper#SaveTags()
call writefile(keys(tags), tagFile)
endfunction
function! tagHelper#AddAutocmds()
autocmd VimLeave call tagHelper#SaveTags()
exe 'autocmd BufEnter ' . reportPattern . ' call tagHelper#AddAllTagsInBuffer()'
exe 'autocmd BufEnter ' . reportPattern . ' call tagHelper#AddMappings()'
endfunction
My CompleteHelper library makes it easy to build custom completions (and I've already written quite some); you'd need one to match any tag name inside <...>. But even though the library does the hard work of building the list of matches, you'd still have to write the boilerplate code to define the pattern, and assign this to a completion key.
So, prompted by your use case, I've written the SpecialLocationComplete plugin that utilizes the CompleteHelper library and allows the definition of a custom completion via a simple configuration object. And best (for you), it even ships with a CTRL-X CTRL-X T completion for complete tags, just what you asked for (and what I imagine could be more generally useful for XML tags, too)!

Make VIM function return text without indent

I guess this question could be taken in two ways...
(Generic) - is there a way to specify settings 'local' to a function (setlocal changes seem to persist after the function call)...
(Specific) - I have a function which gets called from an imap mapping (which takes a user input to pass into the function. The function works perfectly if I run set paste or set noai | set nosi either just before running my shortcut, or added into the function itself. The problem is, whichever way I do it, those setting changes persist after my function call.
Essentially, my workflow is:
In insert mode, type //// at which point I get prompted for input text, which I enter and press enter.
The function is called with my input. I need the function to disable indenting, return my string and then re-enable the previous settings. The string would just be a PHP-block comment like this:
/**
* Blah {INPUT TEXT}
*/
Any suggestions appreciated. My script currently looks like this:
function CommentInjector(txt)
return "\/**" ."\<CR>"
\ . " * foo " . a:txt . " bar " . "\<CR>"
\ . " */"
endfunction
imap <silent> //// <C-R>=CommentInjector(input("Enter some text:"))<CR>
UPDATE
Managed to figure it out at least how to dump a comment in... Would appreciate knowing how to get/restore settings though...
function! CommentInjector(txt)
set paste
exe "normal! i/**\<CR>"
\ . " * fooo " . a:txt . " bar\<CR>"
\ . " */\<Esc>"
set nopaste
endfunction
map <C-C><C-C><C-C> :call CommentInjector(input("Enter some text:"))<CR>
Using this you can just pres Ctrl+C 3 time, enter text when prompted and you get a nice comment written in. It assumes you had "set paste" disabled before running though...
Since you've posted an update and are really just looking at how to save/restore settings, I'll give a general solution.
At the start of your function save the initial value of the setting: let save_paste = &paste
Make any changes to paste that you'd like to make
Restore it at the end: let &paste = save_paste
An example of this can be found in the documentation with :help use-cpo-save where they talk about saving and restoring the value of cpoptions.

VIM: How can i know which highlight rule is being used for a keyword?

:colorscheme default
The filetype is php.
Can anyone help me to find out the highlight rule ?
:hi[light]
will list all defined rules with a preview. You can also query single items:
:hi Keyword
To manually look up any syntax group under the cursor, there are choices. Mine is a function bounded to a key like this:
" Show syntax highlighting groups for word under cursor
nmap <F2> :call <SID>SynStack()<CR>
function! <SID>SynStack()
if !exists("*synstack")
return
endif
echo map(synstack(line('.'), col('.')), 'synIDattr(v:val, "name")')
endfunc
It'll list every syntax group the word belongs to.
I've had the following snippet tucked away for a while now, not sure where I got it. This will set your statusline to show the highlight group of the word currently under the cursor:
:set statusline=%{synIDattr(synIDtrans(synID(line('.'),col('.'),1)),'name')}
This will update your statusline as you move around the file.
I have something like this in my _gvimrc:
function! SyntaxBalloon()
let synID = synID(v:beval_lnum, v:beval_col, 0)
let groupID = synIDtrans(synID)
let name = synIDattr(synID, "name")
let group = synIDattr(groupID, "name")
return name . "\n" . group
endfunction
set balloonexpr=SyntaxBalloon()
set ballooneval

Vim script: Buffer/CheatSheet Toggle

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. :)

Resources