JohnBeckett (talk | contribs) (Suggestions (some amazing code!)) |
Tag: Visual edit |
||
(19 intermediate revisions by 10 users not shown) | |||
Line 1: | Line 1: | ||
+ | {{TipNew |
||
− | {{TipProposed |
||
− | |id= |
+ | |id=1572 |
− | |previous= |
+ | |previous=1571 |
− | |next= |
+ | |next=1573 |
− | |created= |
+ | |created=2008 |
|complexity=basic |
|complexity=basic |
||
− | |author= |
+ | |author= |
− | |version=7. |
+ | |version=7.2 |
|subpage=/200806 |
|subpage=/200806 |
||
− | |category1= |
+ | |category1=Searching |
|category2= |
|category2= |
||
}} |
}} |
||
− | Vim provides a simple way to highlight all |
+ | Vim provides a simple way to [[VimTip14|highlight all occurrences matching your last search]], but it doesn't provide an easy way to highlight more than one search. This tip provides a script to make it easy to highlight multiple words in different colors. In addition, you can search for the next highlighted word. |
− | == |
+ | ==Requirements== |
+ | The script uses <code>matchadd()</code> which requires [[Where to download Vim|Vim 7.2]] (actually version 7.1.040 or later). You will probably need gvim to display the colors specified in the script. In addition, the script assumes you have a numeric keypad and a system that allows you to map keys on that keypad (for example, the command <code>:map <k0> :echo 'Hello'<CR></code> would display ''Hello'' when you press <code>0</code> on the numeric keypad). |
||
− | To highlight all occurrences of the word under the cursor type <tt>\ma</tt>. To highlight another word without removing the highlighting of the first word move the cursor to the second word and type <tt>2\ma</tt>. As many words as desired can be highlighted by giving each a different count. To remove the highlighting from a word, type <tt><count>\md</tt> where <tt><count></tt> is the count given to the <tt>\ma</tt> command when highlighting the word. |
||
+ | You need the script (<code>highlights.vim</code>) and the file that defines the highlight groups (<code>highlights.csv</code>); these are given below. |
||
− | Adding highlighting to a word will remove any previous highlighting with that count. For example, if the cursor is over the word 'Bob', <tt>\ma</tt> will highlight all Bobs. Moving the cursor to 'Fred' and typing <tt>\ma</tt> will remove the highlighting from Bob and apply it to Fred. Then moving the cursor to 'Bill' and typing <tt>2\ma</tt> will add highlighting to Bill without removing it from Fred. |
||
+ | ==Usage== |
||
− | The script below is setup with colours defined for counts 1, 2 and 3. To add colours for more counts, or to change the existing colours, type <tt><count>\mc</tt> and follow the prompt. See {{help|gui-colors}} for a list of likely supported colours. |
||
+ | [[File:HighlightMultipleWords.png|right|Sample highlighting of words]] |
||
+ | The script defines highlight groups named <code>hl1</code>, <code>hl2</code>, ... <code>hl9</code> (and more). Once enabled, pressing one of the keys <code>1</code>, <code>2</code>, ... <code>9</code> on the numeric keypad will highlight the word under the cursor with the colors defined in the corresponding highlight group (for example, press <code>4</code> on the keypad to highlight the current word with the <code>hl4</code> group). |
||
+ | Type <code>\m</code> (assuming the default backslash leader key) to toggle mapping of the keypad on/off. At startup, the numeric keypad operates normally because only <code>\m</code> is mapped. |
||
− | ==Script== |
||
− | Add the following to your [[vimrc]] or a plugin file: |
||
− | <pre> |
||
− | " matchadd() priority -1 means 'hlsearch' will override the match. |
||
− | function! DoHighlight(hlnum, search_term) |
||
− | if has_key(g:matchadd_ids, a:hlnum) |
||
− | call UndoHighlight(a:hlnum) |
||
− | endif |
||
− | let id = matchadd("hl".a:hlnum, a:search_term, -1) |
||
− | let g:matchadd_ids[a:hlnum] = id |
||
− | endfunction |
||
+ | After typing <code>\m</code> to enable the mappings, you could use the keypad to: |
||
− | function! UndoHighlight(hlnum) |
||
+ | *Press <code>1</code> to highlight the current visually selected text, or the current word (if nothing is selected). Highlight group <code>hl1</code> is used. |
||
− | silent! call matchdelete(g:matchadd_ids[a:hlnum]) |
||
+ | *Press <code>2</code> for highlight <code>hl2</code>, <code>3</code> for highlight <code>hl3</code>, etc. |
||
− | endfunction |
||
+ | *Press <code>0</code> to remove all highlights from the current visually selected text, or the current word. |
||
+ | For example, if you place the cursor on the word "rain" and press <code>4</code> on the keypad, all occurrences of "rain" will be highlighted (use <code>:hi hl4</code> to show the colors). No highlighting will occur in words like "rainbow". |
||
− | function! SetHighlight(hlnum, colour) |
||
− | exe "highlight hl".a:hlnum." term=bold ctermfg=".a:colour." guifg=".a:colour |
||
− | endfunction |
||
+ | Alternatively, place the cursor on "rain" and press <code>viw</code> to select the word, then <code>4</code>. All occurrences of the visually selected text will be highlighted, including the "rain" in "rainbow". |
||
− | let g:matchadd_ids = {} |
||
− | call SetHighlight(1, 'blue') |
||
− | call SetHighlight(2, 'green') |
||
− | call SetHighlight(3, 'red') |
||
− | nnoremap <Leader>ma :<C-u>call DoHighlight(v:count1, expand("<cword>"))<CR> |
||
− | nnoremap <Leader>md :<C-u>call UndoHighlight(v:count1)<CR> |
||
− | nnoremap <Leader>mc :<C-u>call SetHighlight(v:count1, input("Enter colour: "))<CR> |
||
− | </pre> |
||
+ | Each window has its own set of highlights: if you use <code>:split</code> to show two windows, you can have one set of highlights in the top window, and a different set in the other window. |
||
− | ==Explanation== |
||
− | {{todo}} |
||
− | Explain the script covering: |
||
− | *matchadd() |
||
− | *priorities for highlight |
||
− | *matchdelete |
||
− | *highlight (briefly) |
||
− | *v:count1 |
||
− | *expand("<cword>") |
||
− | *global variable could be replaced with script variable |
||
+ | With the mappings enabled, you could use the keypad to: |
||
− | ==See also== |
||
+ | *Press <code>-</code> to remove all highlights in the current window. |
||
− | *{{script|id=1238|text=Mark}} plugin does this, and more |
||
+ | *Press <code>+</code> to restore the highlighting when <code>-</code> was last used in the current window. |
||
+ | *Press <code>*</code> to restore the highlighting when <code>-</code> was last used in any window. |
||
+ | For example, suppose you are displaying two windows and you want the same highlighting in each window. In the first window, apply the wanted highlighting. Then (using the numeric keypad keys): |
||
− | {{todo}} |
||
+ | *Press <code>-</code> to remove all highlights in the first window. |
||
− | Mention limitations in script which aren't in the plugin. |
||
+ | *Press <code>+</code> to restore them. |
||
+ | *Switch to the other window and press <code>*</code> to apply the highlights from the first window. |
||
− | == |
+ | ===Searching=== |
+ | After using <code>\m</code> to enable mappings, you can search for the next or previous occurrence of highlighted text. Patterns highlighted with the <code>:match</code> or <code>:2match</code> commands are also found. |
||
− | *{{help|matchadd()}} |
||
− | *{{help|matchdelete()}} |
||
− | *{{help|clearmatches()}} |
||
− | *{{help|v:count1}} |
||
− | *{{help|expand()}} |
||
− | *{{help|gui-colors}} |
||
+ | Type <code>\f</code> to find the next match, or <code>\F</code> to find backwards. This has no effect on search highlighting or history. |
||
− | ==Comments== |
||
− | '''Very nice!''' Congratulations BenA! I didn't think this was possible in such a short space, but it's a really nice result. I did some work with matchadd in the [[VimTip667|CSV tip]]. I was going to make some suggestions, but I thought I may as well join the fun, and I've ended up with quite a few changes. Let me know what you think. --[[User:JohnBeckett|JohnBeckett]] 04:59, 2 September 2008 (UTC) |
||
− | *Thanks for the compliment and for tidying up the script too. --[[User:BenArmston|BenArmston]] 22:51, 5 September 2008 (UTC) |
||
+ | Alternatively, type <code>\n</code> or <code>\N</code> to search forwards or backwards. Now you can press the normal search keys <code>n</code> or <code>N</code> to find the next or previous occurrence. |
||
− | '''Some notes''' |
||
− | *I think the "silent!" will overcome inevitable errors when the code tries to delete a match that the user has removed some other way (e.g. <tt>:call clearmatches()</tt>). |
||
− | *The keys used for the mappings need some work. It's nice that they are just two characters, but of course 'ma' stops 'mark a'. I guess it would have to be <tt><Leader>ma</tt>. |
||
− | *Bug/feature: You can highlight multiple words with, for example, 'ma' (the different words are all in blue). Then you have no way to clear the highlighting. A little work will fix this. |
||
− | **Well spotted. This has been fixed in the simplest way possible. |
||
− | *If you maintained a dictionary of highlighted words, the 'ma' mapping could ''toggle'' highlighting on/off! (I'm not recommending this; it's probably best to keep the script simple.) |
||
− | **Implementing this made a two line function into a twenty line function, so I'm in agreement here that the tip should be kept simple and point to the plugin for a more feature complete implementation. |
||
− | *In some recent edits I did on [[Commenting with opfunc]], I put the sections in order: Usage, Script, Explanation. I'm not sure what order is best, but have a look and see what you think. |
||
− | **That order reads quite nicely, so I think I'll copy it here. |
||
− | *I see you spell "colour" correctly (I'm also from an enlightened part of the world!). However, I actually gritted my teeth and started saying "color" (based on the Vim docs). I'll leave that ugly choice to you... |
||
− | *For interest, I note that the code is now so compact that you could make each mapping a "one liner" and have no functions! However, I like the functions (particularly on the wiki they are helpful for understanding). |
||
− | *BenArmston: As always, when ready please delete these comments. |
||
+ | ===Highlight command=== |
||
− | '''Title''' |
||
+ | The script defines a <code>:Highlight ''n'' ''pattern''</code> command where ''n'' is a number (1..99) and ''pattern'' is the text you want highlighted. Depending on your system, you may be able to use the abbreviation <code>:Hi</code> rather than <code>:Highlight</code>. |
||
− | *Let's decide on a title for this tip (consistency is good). |
||
− | *Since the tip doesn't actually search, perhaps "search" should be omitted from title? |
||
− | *However, it might be reasonable to put the tip in the Searching category? |
||
+ | The following examples use different colors to highlight various patterns: |
||
− | No similar tips have "multiple" or "match" in title. Vaguely-related tips on highlighting are: |
||
+ | <pre> |
||
− | *Highlight all search pattern matches |
||
− | + | :Highlight 4 hello |
|
− | + | :Highlight 5 \<hello\> |
|
− | + | :Highlight 6 Hello World |
|
− | + | :Highlight 7 \c\<th |
|
+ | </pre> |
||
− | *Highlight text inside matching parentheses |
||
− | *Highlight unwanted spaces |
||
+ | The second example is the same as placing the cursor on "hello" then pressing <code>5</code> on the numeric keypad. The last example highlights <code>th</code> occurring at the beginning of a word, not case sensitive. |
||
− | Suggestions for a new title for this tip (please add any thoughts): |
||
− | #Multiple highlighted search |
||
− | #Highlight multiple words |
||
+ | The following command will list all active highlights (group names and patterns): |
||
− | I'd go with 'Highlight multiple words' as it's closest to what the tip now does. Is there any disadvantage to putting it in the Searching category? Other than the obvious 'don't have tips in inappropriate categories'? If not, it's close enough to go in I think. --[[User:BenArmston|BenArmston]] 22:51, 5 September 2008 (UTC) |
||
+ | <pre> |
||
+ | :Highlight |
||
+ | </pre> |
||
+ | ===Other commands=== |
||
− | __NOTOC__ |
||
+ | After using <code>\m</code> to enable the keypad mappings, you can press 1..9 on the keypad to highlight the visual selection (if any), or the current word. For example, pressing <code>4</code> on the keypad will highlight using the <code>hl4</code> group. |
||
− | ==Suggestions== |
||
− | '''To reply, please add a new heading (e.g. <tt><nowiki>==Reply==</nowiki></tt>) after everything else, so we can see it.''' |
||
+ | In addition, you can press a digit 1..9 on the main keyboard before pressing 1..9 on the keypad. For example, if you press <code>2</code> on the main keyboard before <code>4</code> on the keypad, the <code>hl24</code> highlight group will be used. |
||
− | BenA: I've got interested in the code and gone crazy. Sorry, but I've put three new versions below. If you haven't got time to look at it, let me know, and I'll clean up the tip (maybe put this on the talk page for consideration another time). Otherwise, please consider the points raised and we can discuss it -- the main question is, which version should we use? |
||
+ | The <code>:Hclear</code> command can clear highlights. After typing <code>:Hclear</code>, you can enter an argument, or you can press Space then Tab for command completion (highlighted patterns). Examples: |
||
− | I started with version JB#1 below: just a couple of tweaks. With this version, you can press '\ma' to highlight a word. Later, you can move to a blank line, and press '\ma' again to ''remove'' the highlight. |
||
+ | <pre> |
||
+ | " Remove highlight for visual selection (if any), or current word. |
||
+ | :Hclear |
||
+ | " Remove highlight 24. |
||
+ | :Hclear 24 |
||
+ | " Remove all highlights for pattern '\c\<th'. |
||
+ | :Hclear \c\<th |
||
+ | " Remove all highlights. |
||
+ | :Hclear * |
||
+ | </pre> |
||
+ | The following command will create a scratch buffer showing all the defined highlights: |
||
− | While testing, I noticed a problem. Say you split the screen and show two windows. It turns out that each window has its own set of matches, while the tip uses a single global dictionary. |
||
+ | <pre> |
||
+ | :Hsample |
||
+ | </pre> |
||
+ | The following commands can be used to save or restore the current highlights: |
||
− | Apart from any inelegancy, this gives a bug: If you use '\ma' to match word "one" in the first window, and then use '\ma' to match "two" in the second window, then you cannot remove the match of "one" (the global dictionary holds only the last id, whereas there is actually an id for the match in the first window, and an unrelated id for the match in the second. |
||
+ | <pre> |
||
+ | :Hsave anyname |
||
+ | :Hrestore anyname |
||
+ | </pre> |
||
+ | Replace <code>anyname</code> with any name you would like to use. These commands use uppercase global variables (<code>anyname</code> is converted to uppercase), which Vim will save providing you have the <code>!</code> flag in the <code>'viminfo'</code> option (<code>:set viminfo^=!</code>). |
||
− | While wondering whether there was any simple fix, I realised that we don't ''need'' a dictionary at all, because Vim maintains that info. That led to version JB#2 below. |
||
+ | After typing <code>:Hsave</code> or <code>:Hrestore</code>, you can press Space then Tab for command completion. |
||
− | Then I started wondering if I could make the mappings easier to use. I decided to just use the numeric keypad. It is ''amazing'' -- you have to try it! On the keypad (may require gvim): press 1 to highlight the current word in colour hl1, or 2 for colour hl2, etc. Press 0 on the keypad to remove the highlight from the current word. See the comments -- there's more. |
||
+ | ===Example=== |
||
− | The tip should document: Can <tt>:call DoHighlight(1, pattern)</tt> where pattern is: |
||
+ | Copy this text into Vim to test word highlighting: |
||
− | *Any wanted search pattern. ''or'' |
||
− | *<tt><C-r>"</tt> (Ctrl-r then double-quote, to insert the unnamed register). ''or'' |
||
− | *<tt><C-r>/</tt> (Ctrl-r then slash, to insert the last search). |
||
− | For example, the following uses hl4 to match 'th' occurring at the beginning of a word, not case sensitive: |
||
<pre> |
<pre> |
||
+ | 777 888 999 111 222 333 444 555 666 777 888 999 |
||
− | :call DoHighlight(4,'\c\<th') |
||
+ | 444 555 666 999 111 222 333 444 555 666 777 888 |
||
+ | 111 222 333 888 999 111 222 333 444 555 666 777 |
||
+ | 111222 333444 555666 777888999 111222 333444 555666 |
||
</pre> |
</pre> |
||
+ | Use <code>:sp</code> to split the window and view the above text in both windows. Use <code>\m</code> to enable mappings, then apply some matches in the top window. Switch to the bottom window and apply some different matches: each window maintains a separate list of highlight matches. |
||
− | I only got involved in this project because of the impressive result BenArmston achieved (I've used dictionaries in Python, but never in Vim script). My script is getting a bit long, but it's incredibly useful. --[[User:JohnBeckett|JohnBeckett]] 10:04, 6 September 2008 (UTC) |
||
+ | In the next step, you must use the <code>-+*</code> keys on the numeric keypad. While in the bottom window, press <code>-</code> to clear highlighting in that window. If wanted, press <code>+</code> to restore highlighting. In the top window, press <code>*</code> to apply the highlighting from the bottom window. |
||
− | <pre> |
||
− | " JB#1: |
||
− | " No need for has_key() since 'silent!' hides error. |
||
− | " Don't try to highlight if no search_term |
||
− | " (can use '\ma' on a blank line to clear highlight). |
||
− | " Don't try to SetHighlight if no colour |
||
− | " (can press Esc or Enter at input prompt to cancel). |
||
− | " matchadd() priority -1 means 'hlsearch' will override the match. |
||
− | function! DoHighlight(hlnum, search_term) |
||
− | call UndoHighlight(a:hlnum) |
||
− | if len(a:search_term) > 0 |
||
− | let id = matchadd("hl".a:hlnum, a:search_term, -1) |
||
− | let g:matchadd_ids[a:hlnum] = id |
||
− | endif |
||
− | endfunction |
||
+ | ==Script== |
||
− | function! UndoHighlight(hlnum) |
||
+ | Create file <code>~/.vim/plugin/highlights.vim</code> (Unix) or <code>$HOME/vimfiles/plugin/highlights.vim</code> (Windows) containing the script below, then restart Vim. |
||
− | silent! call matchdelete(g:matchadd_ids[a:hlnum]) |
||
+ | <source lang="vim"> |
||
− | endfunction |
||
+ | " Plugin to highlight multiple words in different colors. |
||
+ | " Version 2008-11-19 from http://vim.wikia.com/wiki/VimTip1572 |
||
+ | " File highlights.csv (in same directory as script) defines the highlights. |
||
+ | " |
||
+ | " Type '\m' to toggle mapping of keypad on/off (assuming \ leader). |
||
+ | " Type '\f' to find the next match; '\F' to find backwards. |
||
+ | " Can also type '\n' or '\N' for search; then n or N will find next. |
||
+ | " On the numeric keypad, press: |
||
+ | " 1 to highlight visually selected text or current word |
||
+ | " using highlight group hl1 (defined below) |
||
+ | " 2 for highlight hl2, 3 for highlight hl3, etc |
||
+ | " (can press 1 to 9 on keypad for highlights hl1 to hl9) |
||
+ | " 0 to remove highlight from current word |
||
+ | " - to remove all highlights in current window |
||
+ | " + to restore highlights cleared with '-' in current window |
||
+ | " * to restore highlights (possibly from another window) |
||
+ | " Can press 1 or 2 on main keyboard before keypad 1..9 for more highlights. |
||
+ | " Commands: |
||
+ | " ':Highlight' list all highlights. |
||
+ | " ':Highlight [n [pattern]]' set highlight. |
||
+ | " ':Hsample' display all highlights in a scratch buffer. |
||
+ | " ':Hclear [hlnum|pattern|*]' clear highlights. |
||
+ | " ':Hsave x', ':Hrestore x' save/restore highlights (x any name). |
||
+ | " Saving current highlights requires '!' in 'viminfo' option. |
||
+ | if v:version < 702 || exists('loaded_highlightmultiple') || &cp |
||
+ | finish |
||
+ | endif |
||
+ | let loaded_highlightmultiple = 1 |
||
+ | " On first call, read file highlights.csv in same directory as script. |
||
− | function! SetHighlight(hlnum, colour) |
||
+ | " For example, line "5,white,blue,black,green" executes: |
||
− | if len(a:colour) > 0 |
||
− | + | " highlight hl5 ctermfg=white ctermbg=blue guifg=black guibg=green |
|
+ | let s:data_file = expand('<sfile>:p:r').'.csv' |
||
+ | let s:loaded_data = 0 |
||
+ | function! LoadHighlights() |
||
+ | if !s:loaded_data |
||
+ | if filereadable(s:data_file) |
||
+ | let names = ['hl', 'ctermfg=', 'ctermbg=', 'guifg=', 'guibg='] |
||
+ | for line in readfile(s:data_file) |
||
+ | let fields = split(line, ',', 1) |
||
+ | if len(fields) == 5 && fields[0] =~ '^\d\+$' |
||
+ | let cmd = range(5) |
||
+ | call map(cmd, 'names[v:val].fields[v:val]') |
||
+ | call filter(cmd, 'v:val!~''=$''') |
||
+ | execute 'silent highlight '.join(cmd) |
||
+ | endif |
||
+ | endfor |
||
+ | let s:loaded_data = 1 |
||
+ | endif |
||
+ | if !s:loaded_data |
||
+ | echo 'Error: Could not read highlight data from '.s:data_file |
||
+ | endif |
||
endif |
endif |
||
endfunction |
endfunction |
||
+ | " Return last visually selected text or '\<cword\>'. |
||
− | let g:matchadd_ids = {} |
||
+ | " what = 1 (selection), or 2 (cword), or 0 (guess if 1 or 2 is wanted). |
||
− | call SetHighlight(1, 'blue') |
||
+ | function! s:Pattern(what) |
||
− | call SetHighlight(2, 'green') |
||
+ | if a:what == 2 || (a:what == 0 && histget(':', -1) =~# '^H') |
||
− | call SetHighlight(3, 'red') |
||
− | + | let result = expand("<cword>") |
|
+ | if !empty(result) |
||
− | nnoremap <Leader>md :<C-u>call UndoHighlight(v:count1)<CR> |
||
+ | let result = '\<'.result.'\>' |
||
− | nnoremap <Leader>mc :<C-u>call SetHighlight(v:count1, input("Enter colour: "))<CR> |
||
+ | endif |
||
− | </pre> |
||
+ | else |
||
− | |||
+ | let old_reg = getreg('"') |
||
− | <pre> |
||
+ | let old_regtype = getregtype('"') |
||
− | " JB#2: |
||
+ | normal! gvy |
||
− | " Use getmatches() rather than dictionary (works in multiple windows). |
||
+ | let result = substitute(escape(@@, '\.*$^~['), '\_s\+', '\\_s\\+', 'g') |
||
− | function! DoHighlight(hlnum, search_term) |
||
+ | normal! gV |
||
− | call UndoHighlight(a:hlnum) |
||
+ | call setreg('"', old_reg, old_regtype) |
||
− | if len(a:search_term) > 0 |
||
− | let id = matchadd('hl'.a:hlnum, a:search_term, -1) |
||
endif |
endif |
||
+ | return result |
||
endfunction |
endfunction |
||
+ | " Remove any highlighting for hlnum then highlight pattern (if not empty). |
||
− | function! UndoHighlight(hlnum) |
||
+ | " If pat is numeric, use current word or visual selection and |
||
− | silent! call matchdelete(GetId(a:hlnum)) |
||
+ | " increase hlnum by count*10 (if count [1..9] is given). |
||
+ | function! s:DoHighlight(hlnum, pat, decade) |
||
+ | call LoadHighlights() |
||
+ | let hltotal = a:hlnum |
||
+ | if 0 < a:decade && a:decade < 10 |
||
+ | let hltotal += a:decade * 10 |
||
+ | endif |
||
+ | if type(a:pat) == type(0) |
||
+ | let pattern = s:Pattern(a:pat) |
||
+ | else |
||
+ | let pattern = a:pat |
||
+ | endif |
||
+ | let id = hltotal + 100 |
||
+ | silent! call matchdelete(id) |
||
+ | if !empty(pattern) |
||
+ | try |
||
+ | call matchadd('hl'.hltotal, pattern, -1, id) |
||
+ | catch /E28:/ |
||
+ | echo 'Highlight hl'.hltotal.' is not defined' |
||
+ | endtry |
||
+ | endif |
||
endfunction |
endfunction |
||
+ | " Remove all matches for pattern. |
||
− | function! GetId(hlnum) |
||
+ | function! s:UndoHighlight(pat) |
||
+ | if type(a:pat) == type(0) |
||
+ | let pattern = s:Pattern(a:pat) |
||
+ | else |
||
+ | let pattern = a:pat |
||
+ | endif |
||
for m in getmatches() |
for m in getmatches() |
||
− | if m |
+ | if m.pattern ==# pattern |
− | + | call matchdelete(m.id) |
|
endif |
endif |
||
endfor |
endfor |
||
− | return 0 |
||
endfunction |
endfunction |
||
+ | " Return pattern to search for next match, and do search. |
||
− | function! SetHighlight(hlnum, colour) |
||
+ | function! s:Search(backward) |
||
− | if len(a:colour) > 0 |
||
+ | let patterns = [] |
||
− | exe "highlight hl".a:hlnum." term=bold ctermfg=".a:colour." guifg=".a:colour |
||
− | endif |
||
− | endfunction |
||
− | |||
− | call SetHighlight(1, 'blue') |
||
− | call SetHighlight(2, 'green') |
||
− | call SetHighlight(3, 'red') |
||
− | nnoremap <Leader>ma :<C-u>call DoHighlight(v:count1, expand("<cword>"))<CR> |
||
− | nnoremap <Leader>md :<C-u>call UndoHighlight(v:count1)<CR> |
||
− | nnoremap <Leader>mc :<C-u>call SetHighlight(v:count1, input("Enter colour: "))<CR> |
||
− | </pre> |
||
− | |||
− | <pre> |
||
− | " JB#3: |
||
− | " Use fixed highlight id (hlnum+10). |
||
− | " Fixed colours (edit script to change). |
||
− | " On the numeric keypad, press: |
||
− | " 1 to highlight current word with highlight hl1 |
||
− | " 2 for highlight hl2, 3 for highlight hl3, etc |
||
− | " (the script defines highlights hl1, hl2, ... hl6) |
||
− | " 0 to remove highlight from current word |
||
− | " - to remove all highlights in current window |
||
− | " + to restore highlights cleared with '-' in current window |
||
− | " * to restore highlights (possibly from another window) |
||
− | " Type '\m' to toggle mapping of keypad on/off. |
||
− | function! DoHighlight(hlnum, pattern) |
||
− | let id = a:hlnum + 10 |
||
− | silent! call matchdelete(id) |
||
− | if len(a:pattern) > 0 |
||
− | let id = matchadd('hl'.a:hlnum, a:pattern, -1, id) |
||
− | endif |
||
− | endfunction |
||
− | |||
− | function! UndoHighlight(pattern) |
||
for m in getmatches() |
for m in getmatches() |
||
+ | call add(patterns, m.pattern) |
||
− | if m['pattern'] ==# a:pattern && m['priority'] == -1 |
||
− | call matchdelete(m['id']) |
||
− | endif |
||
endfor |
endfor |
||
+ | if empty(patterns) |
||
+ | let pat = '' |
||
+ | else |
||
+ | let pat = join(patterns, '\|') |
||
+ | call search(pat, a:backward ? 'b' : '') |
||
+ | endif |
||
+ | return pat |
||
endfunction |
endfunction |
||
+ | " Enable or disable mappings and any current matches. |
||
− | function! ToggleMatchMap() |
||
+ | function! s:MatchToggle() |
||
if exists('g:match_maps') && g:match_maps |
if exists('g:match_maps') && g:match_maps |
||
let g:match_maps = 0 |
let g:match_maps = 0 |
||
− | for i in range( |
+ | for i in range(0, 9) |
− | + | execute 'unmap <k'.i.'>' |
|
endfor |
endfor |
||
− | nunmap <k0> |
||
nunmap <kMinus> |
nunmap <kMinus> |
||
nunmap <kPlus> |
nunmap <kPlus> |
||
+ | nunmap <kMultiply> |
||
+ | nunmap <Leader>f |
||
+ | nunmap <Leader>F |
||
+ | nunmap <Leader>n |
||
+ | nunmap <Leader>N |
||
else |
else |
||
let g:match_maps = 1 |
let g:match_maps = 1 |
||
for i in range(1, 9) |
for i in range(1, 9) |
||
− | + | execute 'vnoremap <silent> <k'.i.'> :<C-U>call <SID>DoHighlight('.i.', 1, v:count)<CR>' |
|
+ | execute 'nnoremap <silent> <k'.i.'> :<C-U>call <SID>DoHighlight('.i.', 2, v:count)<CR>' |
||
endfor |
endfor |
||
− | + | vnoremap <silent> <k0> :<C-U>call <SID>UndoHighlight(1)<CR> |
|
− | nnoremap < |
+ | nnoremap <silent> <k0> :<C-U>call <SID>UndoHighlight(2)<CR> |
− | nnoremap < |
+ | nnoremap <silent> <kMinus> :call <SID>WindowMatches(0)<CR> |
− | nnoremap < |
+ | nnoremap <silent> <kPlus> :call <SID>WindowMatches(1)<CR> |
+ | nnoremap <silent> <kMultiply> :call <SID>WindowMatches(2)<CR> |
||
+ | nnoremap <silent> <Leader>f :call <SID>Search(0)<CR> |
||
+ | nnoremap <silent> <Leader>F :call <SID>Search(1)<CR> |
||
+ | nnoremap <silent> <Leader>n :let @/=<SID>Search(0)<CR> |
||
+ | nnoremap <silent> <Leader>N :let @/=<SID>Search(1)<CR> |
||
endif |
endif |
||
+ | call s:WindowMatches(g:match_maps) |
||
echo 'Mappings for matching:' g:match_maps ? 'ON' : 'off' |
echo 'Mappings for matching:' g:match_maps ? 'ON' : 'off' |
||
endfunction |
endfunction |
||
+ | nnoremap <silent> <Leader>m :call <SID>MatchToggle()<CR> |
||
+ | " Remove and save current matches, or restore them. |
||
− | function! WindowMatches(action) |
||
+ | function! s:WindowMatches(action) |
||
+ | call LoadHighlights() |
||
if a:action == 1 |
if a:action == 1 |
||
if exists('w:last_matches') |
if exists('w:last_matches') |
||
Line 269: | Line 291: | ||
if exists('g:last_matches') |
if exists('g:last_matches') |
||
call setmatches(g:last_matches) |
call setmatches(g:last_matches) |
||
+ | else |
||
+ | call s:Hrestore('') |
||
endif |
endif |
||
else |
else |
||
let m = getmatches() |
let m = getmatches() |
||
− | if |
+ | if !empty(m) |
let w:last_matches = m |
let w:last_matches = m |
||
let g:last_matches = m |
let g:last_matches = m |
||
+ | call s:Hsave('') |
||
call clearmatches() |
call clearmatches() |
||
endif |
endif |
||
Line 280: | Line 305: | ||
endfunction |
endfunction |
||
+ | " Return name of global variable to save value ('' if invalid). |
||
− | highlight hl1 term=bold ctermfg=Blue guifg=Blue |
||
+ | function! s:NameForSave(name) |
||
− | highlight hl2 term=bold ctermfg=Green guifg=Green |
||
+ | if a:name =~# '^\w*$' |
||
− | highlight hl3 term=bold ctermfg=Red guifg=Red |
||
+ | return 'HI_SAVE_'.toupper(a:name) |
||
− | highlight hl4 ctermfg=Black ctermbg=Cyan guifg=Black guibg=DarkYellow |
||
+ | endif |
||
− | highlight hl5 ctermfg=Black ctermbg=Green guifg=Black guibg=DarkGreen |
||
+ | echo 'Error: Invalid name "'.a:name.'"' |
||
− | highlight hl6 ctermfg=Black ctermbg=Magenta guifg=Black guibg=Violet |
||
+ | return '' |
||
− | nnoremap <Leader>m :call ToggleMatchMap()<CR> |
||
+ | endfunction |
||
+ | |||
+ | " Return custom completion string (match patterns). |
||
+ | function! s:MatchPatterns(A, L, P) |
||
+ | return join(sort(map(getmatches(), 'v:val.pattern')), "\n") |
||
+ | endfunction |
||
+ | |||
+ | " Return custom completion string (saved highlight names). |
||
+ | function! s:SavedNames(A, L, P) |
||
+ | let l = filter(keys(g:), 'v:val =~# ''^HI_SAVE_\w''') |
||
+ | return tolower(join(sort(map(l, 'strpart(v:val, 8)')), "\n")) |
||
+ | endfunction |
||
+ | |||
+ | " Save current highlighting in a global variable. |
||
+ | function! s:Hsave(name) |
||
+ | let sname = s:NameForSave(a:name) |
||
+ | if !empty(sname) |
||
+ | let l = getmatches() |
||
+ | call map(l, 'join([v:val.group, v:val.pattern, v:val.priority, v:val.id], "\t")') |
||
+ | let g:{sname} = join(l, "\n") |
||
+ | endif |
||
+ | endfunction |
||
+ | command! -nargs=? -complete=custom,s:SavedNames Hsave call s:Hsave('<args>') |
||
+ | |||
+ | " Restore current highlighting from a global variable. |
||
+ | function! s:Hrestore(name) |
||
+ | call LoadHighlights() |
||
+ | let sname = s:NameForSave(a:name) |
||
+ | if !empty(sname) |
||
+ | if exists('g:{sname}') |
||
+ | let matches = [] |
||
+ | for l in split(g:{sname}, "\n") |
||
+ | let f = split(l, "\t", 1) |
||
+ | call add(matches, {'group':f[0], 'pattern':f[1], 'priority':f[2], 'id':f[3]}) |
||
+ | endfor |
||
+ | call setmatches(matches) |
||
+ | else |
||
+ | echo 'No such global variable: '.sname |
||
+ | endif |
||
+ | endif |
||
+ | endfunction |
||
+ | command! -nargs=? -complete=custom,s:SavedNames Hrestore call s:Hrestore('<args>') |
||
+ | |||
+ | " Clear a match, or clear all current matches. Example args: |
||
+ | " '14' = hl14, '*' = all, '' = visual selection or cword, |
||
+ | " 'pattern' = all matches for pattern |
||
+ | function! s:Hclear(pattern) range |
||
+ | if empty(a:pattern) |
||
+ | call s:UndoHighlight(0) |
||
+ | elseif a:pattern == '*' |
||
+ | call s:WindowMatches(0) |
||
+ | elseif a:pattern =~ '^[1-9][0-9]\?$' |
||
+ | call s:DoHighlight(str2nr(a:pattern), '', 0) |
||
+ | else |
||
+ | call s:UndoHighlight(a:pattern) |
||
+ | endif |
||
+ | endfunction |
||
+ | command! -nargs=* -complete=custom,s:MatchPatterns -range Hclear call s:Hclear('<args>') |
||
+ | |||
+ | " Create a scratch buffer with sample text, and apply all highlighting. |
||
+ | function! s:Hsample() |
||
+ | call LoadHighlights() |
||
+ | new |
||
+ | setlocal buftype=nofile bufhidden=hide noswapfile |
||
+ | let lines = [] |
||
+ | let items = [] |
||
+ | for hl in filter(range(1, 99), 'v:val % 10 > 0') |
||
+ | if hlexists('hl'.hl) |
||
+ | let sample = printf('Sample%2d', hl) |
||
+ | call s:DoHighlight(hl, sample, 0) |
||
+ | else |
||
+ | let sample = ' ' |
||
+ | endif |
||
+ | call add(items, sample) |
||
+ | if len(items) >= 3 |
||
+ | call insert(lines, substitute(join(items), '\s\+$', '', '')) |
||
+ | let items = [] |
||
+ | endif |
||
+ | endfor |
||
+ | call append(0, filter(lines, 'len(v:val) > 0')) |
||
+ | $d |
||
+ | %s/\d3$/&\r/e |
||
+ | endfunction |
||
+ | command! Hsample call s:Hsample() |
||
+ | |||
+ | " Set a match, or display all current matches. Example args: |
||
+ | " '14' = set hl14 for visual selection or cword, |
||
+ | " '14 pattern' = set hl14 for pattern, '' = display all |
||
+ | function! s:Highlight(args) range |
||
+ | if empty(a:args) |
||
+ | echo 'Highlight groups and patterns:' |
||
+ | for m in getmatches() |
||
+ | echo m.group m.pattern |
||
+ | endfor |
||
+ | return |
||
+ | endif |
||
+ | let l = matchlist(a:args, '^\s*\([1-9][0-9]\?\)\%($\|\s\+\(.*\)\)') |
||
+ | if len(l) >= 3 |
||
+ | let hlnum = str2nr(l[1]) |
||
+ | let pattern = l[2] |
||
+ | if empty(pattern) |
||
+ | let pattern = s:Pattern(0) |
||
+ | endif |
||
+ | call s:DoHighlight(hlnum, pattern, 0) |
||
+ | return |
||
+ | endif |
||
+ | echo 'Error: First argument must be highlight number 1..99' |
||
+ | endfunction |
||
+ | command! -nargs=* -range Highlight call s:Highlight('<args>') |
||
+ | </source> |
||
+ | |||
+ | ==Defining highlight groups== |
||
+ | The script reads a [[CSV]] file with the same directory/name as the script, but with <code>.csv</code> extension. Each line should consist of five comma-separated fields, where the first field is a number from 1 to 99 inclusive (any other lines are ignored). |
||
+ | |||
+ | For example, the line "<code>5,white,blue,black,green</code>" would define a highlight group named <code>hl5</code> which color terminals would display as white on blue, and gvim would display as black on green. |
||
+ | |||
+ | Create file <code>~/.vim/plugin/highlights.csv</code> (Unix) or <code>$HOME/vimfiles/plugin/highlights.csv</code> (Windows) containing the sample below, then restart Vim. |
||
+ | <pre> |
||
+ | hlnum,ctermfg,ctermbg,guifg,guibg |
||
+ | 1,red,,red, |
||
+ | 2,green,,green, |
||
+ | 3,blue,,blue, |
||
+ | 4,black,magenta,black,IndianRed |
||
+ | 5,black,green,black,chartreuse |
||
+ | 6,black,cyan,black,DeepSkyBlue |
||
+ | 7,white,DarkRed,white,firebrick |
||
+ | 8,white,DarkGreen,white,DarkGreen |
||
+ | 9,white,DarkBlue,white,DarkSlateBlue |
||
+ | 11,,,black,thistle |
||
+ | 12,,,black,burlywood |
||
+ | 13,,,black,PowderBlue |
||
+ | 14,,,black,peru |
||
+ | 15,,,black,DarkSeaGreen |
||
+ | 16,,,black,SteelBlue |
||
+ | 17,,,black,DarkOrange |
||
+ | 18,,,black,DarkOliveGreen |
||
+ | 19,,,black,SlateGray |
||
+ | 21,,,red,grey20 |
||
+ | 22,,,green,grey20 |
||
+ | 23,,,CornflowerBlue,grey20 |
||
+ | 24,,,red,grey40 |
||
+ | 25,,,green,grey40 |
||
+ | 26,,,blue,grey40 |
||
+ | 27,,,red,grey70 |
||
+ | 28,,,DarkGreen,grey70 |
||
+ | 29,,,blue,grey70 |
||
</pre> |
</pre> |
||
+ | |||
+ | ==See also== |
||
+ | *[[Talk:Highlight_multiple_words|Discussion page]] for alternative methods |
||
+ | *[[VimTip634|View all named colors]] to show the colors for the standard color names |
||
+ | *[[VimTip24|Highlighting information]] want to fix this to list relevant tips |
||
+ | *{{script|id=479|text=MultipleSearch}} highlight multiple searches, each with a different color |
||
+ | *{{script|id=2666|text=Mark}} highlight several words in different colors simultaneously (a continuation of the original {{script|id=1238|text=Mark}} plugin) |
||
+ | |||
+ | ==References== |
||
+ | *{{help|matchadd()}} |
||
+ | *{{help|matchdelete()}} |
||
+ | *{{help|clearmatches()}} |
||
+ | *{{help|gui-colors}} |
||
+ | |||
+ | ==Comments== |
||
+ | A [http://vim.wikia.com/index.php?title=Highlight_multiple_words&diff=29652&oldid=23776 recent edit] added a note regarding the script that "Selected text can not be highlighted in unmodifiable buffers; "normal! gy" does not work". I guess "gvy" was intended, rather that "gy". However, I can select text in a Help buffer (which is ":set nomodifiable"), then highlight all occurrences of that text using the script in this tip. I have removed the note pending some further information (what is it exactly that does not work?). [[User:JohnBeckett|JohnBeckett]] 04:09, March 5, 2011 (UTC) |
||
+ | |||
+ | Hi John, |
||
+ | |||
+ | I assume you created this awesome VIM script. If that is true, can I ask for your permission to use some of the codes for my own VIM script? I plan to do almost the same thing (multiple highlights), but the search text is taken from a configuration file (highlight.csv) and supports case sensitivity. I plan to share it out to the community through my blog, so is there any caveat? Thanks. |
||
+ | |||
+ | Daniel --January 27, 2015 |
||
+ | :Certainly, everything here is available for re-use! I did not start this tip but I did do a lot of work to extend it to its current form. [[User:JohnBeckett|JohnBeckett]] ([[User talk:JohnBeckett|talk]]) 20:31, January 30, 2015 (UTC) |
||
+ | : |
||
+ | : |
||
+ | : Thanks, John. I have completed the script and let me know if you would like me to share it here too. |
||
+ | |||
+ | The basic features are: |
||
+ | |||
+ | (1) Highlight multiple regex based on configuration file (to cater for user that needs to read files of a certain format often) |
||
+ | |||
+ | Eg. |
||
+ | : hlnum,ctermfg,ctermbg,guifg,guibg,regex,ignorecase |
||
+ | : 1,black,blue,black,springgreen,localhost,0 |
||
+ | : 2,black,yellow,black,violet,vmnet-dhcpd,1 |
||
+ | : 3,black,cyan,black,dodgerblue1,dhclient,0 |
||
+ | : 4,black,magenta,black,deeppink4,NetworkManager,1 |
||
+ | : 5,black,lightmagenta,black,mediumpurple4,process-[0-9],0 |
||
+ | |||
+ | (2) Configuration can be changed dynamically and take effect immediately by pressing <leader>m without the need to reopen the input file |
||
+ | |||
+ | (3) The user can opt to switch to another configuration file using (:UsrCfg <configfile>) on the fly without needing to reopen the input file. |
||
+ | |||
+ | ~ Daniel ~ |
Revision as of 02:28, 31 January 2015
Vim provides a simple way to highlight all occurrences matching your last search, but it doesn't provide an easy way to highlight more than one search. This tip provides a script to make it easy to highlight multiple words in different colors. In addition, you can search for the next highlighted word.
Requirements
The script uses matchadd()
which requires Vim 7.2 (actually version 7.1.040 or later). You will probably need gvim to display the colors specified in the script. In addition, the script assumes you have a numeric keypad and a system that allows you to map keys on that keypad (for example, the command :map <k0> :echo 'Hello'<CR>
would display Hello when you press 0
on the numeric keypad).
You need the script (highlights.vim
) and the file that defines the highlight groups (highlights.csv
); these are given below.
Usage
The script defines highlight groups named hl1
, hl2
, ... hl9
(and more). Once enabled, pressing one of the keys 1
, 2
, ... 9
on the numeric keypad will highlight the word under the cursor with the colors defined in the corresponding highlight group (for example, press 4
on the keypad to highlight the current word with the hl4
group).
Type \m
(assuming the default backslash leader key) to toggle mapping of the keypad on/off. At startup, the numeric keypad operates normally because only \m
is mapped.
After typing \m
to enable the mappings, you could use the keypad to:
- Press
1
to highlight the current visually selected text, or the current word (if nothing is selected). Highlight grouphl1
is used. - Press
2
for highlighthl2
,3
for highlighthl3
, etc. - Press
0
to remove all highlights from the current visually selected text, or the current word.
For example, if you place the cursor on the word "rain" and press 4
on the keypad, all occurrences of "rain" will be highlighted (use :hi hl4
to show the colors). No highlighting will occur in words like "rainbow".
Alternatively, place the cursor on "rain" and press viw
to select the word, then 4
. All occurrences of the visually selected text will be highlighted, including the "rain" in "rainbow".
Each window has its own set of highlights: if you use :split
to show two windows, you can have one set of highlights in the top window, and a different set in the other window.
With the mappings enabled, you could use the keypad to:
- Press
-
to remove all highlights in the current window. - Press
+
to restore the highlighting when-
was last used in the current window. - Press
*
to restore the highlighting when-
was last used in any window.
For example, suppose you are displaying two windows and you want the same highlighting in each window. In the first window, apply the wanted highlighting. Then (using the numeric keypad keys):
- Press
-
to remove all highlights in the first window. - Press
+
to restore them. - Switch to the other window and press
*
to apply the highlights from the first window.
Searching
After using \m
to enable mappings, you can search for the next or previous occurrence of highlighted text. Patterns highlighted with the :match
or :2match
commands are also found.
Type \f
to find the next match, or \F
to find backwards. This has no effect on search highlighting or history.
Alternatively, type \n
or \N
to search forwards or backwards. Now you can press the normal search keys n
or N
to find the next or previous occurrence.
Highlight command
The script defines a :Highlight n pattern
command where n is a number (1..99) and pattern is the text you want highlighted. Depending on your system, you may be able to use the abbreviation :Hi
rather than :Highlight
.
The following examples use different colors to highlight various patterns:
:Highlight 4 hello :Highlight 5 \<hello\> :Highlight 6 Hello World :Highlight 7 \c\<th
The second example is the same as placing the cursor on "hello" then pressing 5
on the numeric keypad. The last example highlights th
occurring at the beginning of a word, not case sensitive.
The following command will list all active highlights (group names and patterns):
:Highlight
Other commands
After using \m
to enable the keypad mappings, you can press 1..9 on the keypad to highlight the visual selection (if any), or the current word. For example, pressing 4
on the keypad will highlight using the hl4
group.
In addition, you can press a digit 1..9 on the main keyboard before pressing 1..9 on the keypad. For example, if you press 2
on the main keyboard before 4
on the keypad, the hl24
highlight group will be used.
The :Hclear
command can clear highlights. After typing :Hclear
, you can enter an argument, or you can press Space then Tab for command completion (highlighted patterns). Examples:
" Remove highlight for visual selection (if any), or current word. :Hclear " Remove highlight 24. :Hclear 24 " Remove all highlights for pattern '\c\<th'. :Hclear \c\<th " Remove all highlights. :Hclear *
The following command will create a scratch buffer showing all the defined highlights:
:Hsample
The following commands can be used to save or restore the current highlights:
:Hsave anyname :Hrestore anyname
Replace anyname
with any name you would like to use. These commands use uppercase global variables (anyname
is converted to uppercase), which Vim will save providing you have the !
flag in the 'viminfo'
option (:set viminfo^=!
).
After typing :Hsave
or :Hrestore
, you can press Space then Tab for command completion.
Example
Copy this text into Vim to test word highlighting:
777 888 999 111 222 333 444 555 666 777 888 999 444 555 666 999 111 222 333 444 555 666 777 888 111 222 333 888 999 111 222 333 444 555 666 777 111222 333444 555666 777888999 111222 333444 555666
Use :sp
to split the window and view the above text in both windows. Use \m
to enable mappings, then apply some matches in the top window. Switch to the bottom window and apply some different matches: each window maintains a separate list of highlight matches.
In the next step, you must use the -+*
keys on the numeric keypad. While in the bottom window, press -
to clear highlighting in that window. If wanted, press +
to restore highlighting. In the top window, press *
to apply the highlighting from the bottom window.
Script
Create file ~/.vim/plugin/highlights.vim
(Unix) or $HOME/vimfiles/plugin/highlights.vim
(Windows) containing the script below, then restart Vim.
" Plugin to highlight multiple words in different colors.
" Version 2008-11-19 from http://vim.wikia.com/wiki/VimTip1572
" File highlights.csv (in same directory as script) defines the highlights.
"
" Type '\m' to toggle mapping of keypad on/off (assuming \ leader).
" Type '\f' to find the next match; '\F' to find backwards.
" Can also type '\n' or '\N' for search; then n or N will find next.
" On the numeric keypad, press:
" 1 to highlight visually selected text or current word
" using highlight group hl1 (defined below)
" 2 for highlight hl2, 3 for highlight hl3, etc
" (can press 1 to 9 on keypad for highlights hl1 to hl9)
" 0 to remove highlight from current word
" - to remove all highlights in current window
" + to restore highlights cleared with '-' in current window
" * to restore highlights (possibly from another window)
" Can press 1 or 2 on main keyboard before keypad 1..9 for more highlights.
" Commands:
" ':Highlight' list all highlights.
" ':Highlight [n [pattern]]' set highlight.
" ':Hsample' display all highlights in a scratch buffer.
" ':Hclear [hlnum|pattern|*]' clear highlights.
" ':Hsave x', ':Hrestore x' save/restore highlights (x any name).
" Saving current highlights requires '!' in 'viminfo' option.
if v:version < 702 || exists('loaded_highlightmultiple') || &cp
finish
endif
let loaded_highlightmultiple = 1
" On first call, read file highlights.csv in same directory as script.
" For example, line "5,white,blue,black,green" executes:
" highlight hl5 ctermfg=white ctermbg=blue guifg=black guibg=green
let s:data_file = expand('<sfile>:p:r').'.csv'
let s:loaded_data = 0
function! LoadHighlights()
if !s:loaded_data
if filereadable(s:data_file)
let names = ['hl', 'ctermfg=', 'ctermbg=', 'guifg=', 'guibg=']
for line in readfile(s:data_file)
let fields = split(line, ',', 1)
if len(fields) == 5 && fields[0] =~ '^\d\+$'
let cmd = range(5)
call map(cmd, 'names[v:val].fields[v:val]')
call filter(cmd, 'v:val!~''=$''')
execute 'silent highlight '.join(cmd)
endif
endfor
let s:loaded_data = 1
endif
if !s:loaded_data
echo 'Error: Could not read highlight data from '.s:data_file
endif
endif
endfunction
" Return last visually selected text or '\<cword\>'.
" what = 1 (selection), or 2 (cword), or 0 (guess if 1 or 2 is wanted).
function! s:Pattern(what)
if a:what == 2 || (a:what == 0 && histget(':', -1) =~# '^H')
let result = expand("<cword>")
if !empty(result)
let result = '\<'.result.'\>'
endif
else
let old_reg = getreg('"')
let old_regtype = getregtype('"')
normal! gvy
let result = substitute(escape(@@, '\.*$^~['), '\_s\+', '\\_s\\+', 'g')
normal! gV
call setreg('"', old_reg, old_regtype)
endif
return result
endfunction
" Remove any highlighting for hlnum then highlight pattern (if not empty).
" If pat is numeric, use current word or visual selection and
" increase hlnum by count*10 (if count [1..9] is given).
function! s:DoHighlight(hlnum, pat, decade)
call LoadHighlights()
let hltotal = a:hlnum
if 0 < a:decade && a:decade < 10
let hltotal += a:decade * 10
endif
if type(a:pat) == type(0)
let pattern = s:Pattern(a:pat)
else
let pattern = a:pat
endif
let id = hltotal + 100
silent! call matchdelete(id)
if !empty(pattern)
try
call matchadd('hl'.hltotal, pattern, -1, id)
catch /E28:/
echo 'Highlight hl'.hltotal.' is not defined'
endtry
endif
endfunction
" Remove all matches for pattern.
function! s:UndoHighlight(pat)
if type(a:pat) == type(0)
let pattern = s:Pattern(a:pat)
else
let pattern = a:pat
endif
for m in getmatches()
if m.pattern ==# pattern
call matchdelete(m.id)
endif
endfor
endfunction
" Return pattern to search for next match, and do search.
function! s:Search(backward)
let patterns = []
for m in getmatches()
call add(patterns, m.pattern)
endfor
if empty(patterns)
let pat = ''
else
let pat = join(patterns, '\|')
call search(pat, a:backward ? 'b' : '')
endif
return pat
endfunction
" Enable or disable mappings and any current matches.
function! s:MatchToggle()
if exists('g:match_maps') && g:match_maps
let g:match_maps = 0
for i in range(0, 9)
execute 'unmap <k'.i.'>'
endfor
nunmap <kMinus>
nunmap <kPlus>
nunmap <kMultiply>
nunmap <Leader>f
nunmap <Leader>F
nunmap <Leader>n
nunmap <Leader>N
else
let g:match_maps = 1
for i in range(1, 9)
execute 'vnoremap <silent> <k'.i.'> :<C-U>call <SID>DoHighlight('.i.', 1, v:count)<CR>'
execute 'nnoremap <silent> <k'.i.'> :<C-U>call <SID>DoHighlight('.i.', 2, v:count)<CR>'
endfor
vnoremap <silent> <k0> :<C-U>call <SID>UndoHighlight(1)<CR>
nnoremap <silent> <k0> :<C-U>call <SID>UndoHighlight(2)<CR>
nnoremap <silent> <kMinus> :call <SID>WindowMatches(0)<CR>
nnoremap <silent> <kPlus> :call <SID>WindowMatches(1)<CR>
nnoremap <silent> <kMultiply> :call <SID>WindowMatches(2)<CR>
nnoremap <silent> <Leader>f :call <SID>Search(0)<CR>
nnoremap <silent> <Leader>F :call <SID>Search(1)<CR>
nnoremap <silent> <Leader>n :let @/=<SID>Search(0)<CR>
nnoremap <silent> <Leader>N :let @/=<SID>Search(1)<CR>
endif
call s:WindowMatches(g:match_maps)
echo 'Mappings for matching:' g:match_maps ? 'ON' : 'off'
endfunction
nnoremap <silent> <Leader>m :call <SID>MatchToggle()<CR>
" Remove and save current matches, or restore them.
function! s:WindowMatches(action)
call LoadHighlights()
if a:action == 1
if exists('w:last_matches')
call setmatches(w:last_matches)
endif
elseif a:action == 2
if exists('g:last_matches')
call setmatches(g:last_matches)
else
call s:Hrestore('')
endif
else
let m = getmatches()
if !empty(m)
let w:last_matches = m
let g:last_matches = m
call s:Hsave('')
call clearmatches()
endif
endif
endfunction
" Return name of global variable to save value ('' if invalid).
function! s:NameForSave(name)
if a:name =~# '^\w*$'
return 'HI_SAVE_'.toupper(a:name)
endif
echo 'Error: Invalid name "'.a:name.'"'
return ''
endfunction
" Return custom completion string (match patterns).
function! s:MatchPatterns(A, L, P)
return join(sort(map(getmatches(), 'v:val.pattern')), "\n")
endfunction
" Return custom completion string (saved highlight names).
function! s:SavedNames(A, L, P)
let l = filter(keys(g:), 'v:val =~# ''^HI_SAVE_\w''')
return tolower(join(sort(map(l, 'strpart(v:val, 8)')), "\n"))
endfunction
" Save current highlighting in a global variable.
function! s:Hsave(name)
let sname = s:NameForSave(a:name)
if !empty(sname)
let l = getmatches()
call map(l, 'join([v:val.group, v:val.pattern, v:val.priority, v:val.id], "\t")')
let g:{sname} = join(l, "\n")
endif
endfunction
command! -nargs=? -complete=custom,s:SavedNames Hsave call s:Hsave('<args>')
" Restore current highlighting from a global variable.
function! s:Hrestore(name)
call LoadHighlights()
let sname = s:NameForSave(a:name)
if !empty(sname)
if exists('g:{sname}')
let matches = []
for l in split(g:{sname}, "\n")
let f = split(l, "\t", 1)
call add(matches, {'group':f[0], 'pattern':f[1], 'priority':f[2], 'id':f[3]})
endfor
call setmatches(matches)
else
echo 'No such global variable: '.sname
endif
endif
endfunction
command! -nargs=? -complete=custom,s:SavedNames Hrestore call s:Hrestore('<args>')
" Clear a match, or clear all current matches. Example args:
" '14' = hl14, '*' = all, '' = visual selection or cword,
" 'pattern' = all matches for pattern
function! s:Hclear(pattern) range
if empty(a:pattern)
call s:UndoHighlight(0)
elseif a:pattern == '*'
call s:WindowMatches(0)
elseif a:pattern =~ '^[1-9][0-9]\?$'
call s:DoHighlight(str2nr(a:pattern), '', 0)
else
call s:UndoHighlight(a:pattern)
endif
endfunction
command! -nargs=* -complete=custom,s:MatchPatterns -range Hclear call s:Hclear('<args>')
" Create a scratch buffer with sample text, and apply all highlighting.
function! s:Hsample()
call LoadHighlights()
new
setlocal buftype=nofile bufhidden=hide noswapfile
let lines = []
let items = []
for hl in filter(range(1, 99), 'v:val % 10 > 0')
if hlexists('hl'.hl)
let sample = printf('Sample%2d', hl)
call s:DoHighlight(hl, sample, 0)
else
let sample = ' '
endif
call add(items, sample)
if len(items) >= 3
call insert(lines, substitute(join(items), '\s\+$', '', ''))
let items = []
endif
endfor
call append(0, filter(lines, 'len(v:val) > 0'))
$d
%s/\d3$/&\r/e
endfunction
command! Hsample call s:Hsample()
" Set a match, or display all current matches. Example args:
" '14' = set hl14 for visual selection or cword,
" '14 pattern' = set hl14 for pattern, '' = display all
function! s:Highlight(args) range
if empty(a:args)
echo 'Highlight groups and patterns:'
for m in getmatches()
echo m.group m.pattern
endfor
return
endif
let l = matchlist(a:args, '^\s*\([1-9][0-9]\?\)\%($\|\s\+\(.*\)\)')
if len(l) >= 3
let hlnum = str2nr(l[1])
let pattern = l[2]
if empty(pattern)
let pattern = s:Pattern(0)
endif
call s:DoHighlight(hlnum, pattern, 0)
return
endif
echo 'Error: First argument must be highlight number 1..99'
endfunction
command! -nargs=* -range Highlight call s:Highlight('<args>')
Defining highlight groups
The script reads a CSV file with the same directory/name as the script, but with .csv
extension. Each line should consist of five comma-separated fields, where the first field is a number from 1 to 99 inclusive (any other lines are ignored).
For example, the line "5,white,blue,black,green
" would define a highlight group named hl5
which color terminals would display as white on blue, and gvim would display as black on green.
Create file ~/.vim/plugin/highlights.csv
(Unix) or $HOME/vimfiles/plugin/highlights.csv
(Windows) containing the sample below, then restart Vim.
hlnum,ctermfg,ctermbg,guifg,guibg 1,red,,red, 2,green,,green, 3,blue,,blue, 4,black,magenta,black,IndianRed 5,black,green,black,chartreuse 6,black,cyan,black,DeepSkyBlue 7,white,DarkRed,white,firebrick 8,white,DarkGreen,white,DarkGreen 9,white,DarkBlue,white,DarkSlateBlue 11,,,black,thistle 12,,,black,burlywood 13,,,black,PowderBlue 14,,,black,peru 15,,,black,DarkSeaGreen 16,,,black,SteelBlue 17,,,black,DarkOrange 18,,,black,DarkOliveGreen 19,,,black,SlateGray 21,,,red,grey20 22,,,green,grey20 23,,,CornflowerBlue,grey20 24,,,red,grey40 25,,,green,grey40 26,,,blue,grey40 27,,,red,grey70 28,,,DarkGreen,grey70 29,,,blue,grey70
See also
- Discussion page for alternative methods
- View all named colors to show the colors for the standard color names
- Highlighting information want to fix this to list relevant tips
- MultipleSearch highlight multiple searches, each with a different color
- Mark highlight several words in different colors simultaneously (a continuation of the original Mark plugin)
References
Comments
A recent edit added a note regarding the script that "Selected text can not be highlighted in unmodifiable buffers; "normal! gy" does not work". I guess "gvy" was intended, rather that "gy". However, I can select text in a Help buffer (which is ":set nomodifiable"), then highlight all occurrences of that text using the script in this tip. I have removed the note pending some further information (what is it exactly that does not work?). JohnBeckett 04:09, March 5, 2011 (UTC)
Hi John,
I assume you created this awesome VIM script. If that is true, can I ask for your permission to use some of the codes for my own VIM script? I plan to do almost the same thing (multiple highlights), but the search text is taken from a configuration file (highlight.csv) and supports case sensitivity. I plan to share it out to the community through my blog, so is there any caveat? Thanks.
Daniel --January 27, 2015
- Certainly, everything here is available for re-use! I did not start this tip but I did do a lot of work to extend it to its current form. JohnBeckett (talk) 20:31, January 30, 2015 (UTC)
- Thanks, John. I have completed the script and let me know if you would like me to share it here too.
The basic features are:
(1) Highlight multiple regex based on configuration file (to cater for user that needs to read files of a certain format often)
Eg.
- hlnum,ctermfg,ctermbg,guifg,guibg,regex,ignorecase
- 1,black,blue,black,springgreen,localhost,0
- 2,black,yellow,black,violet,vmnet-dhcpd,1
- 3,black,cyan,black,dodgerblue1,dhclient,0
- 4,black,magenta,black,deeppink4,NetworkManager,1
- 5,black,lightmagenta,black,mediumpurple4,process-[0-9],0
(2) Configuration can be changed dynamically and take effect immediately by pressing <leader>m without the need to reopen the input file
(3) The user can opt to switch to another configuration file using (:UsrCfg <configfile>) on the fly without needing to reopen the input file.
~ Daniel ~