├── README ├── autoload └── mark.vim ├── doc └── mark.txt └── plugin └── mark.vim /README: -------------------------------------------------------------------------------- 1 | This is a mirror of http://www.vim.org/scripts/script.php?script_id=2666 2 | 3 | DESCRIPTION 4 | This plugin adds mappings and a :Mark command to highlight several words in 5 | different colors simultaneously, similar to the built-in 'hlsearch' 6 | highlighting of search results and the * command. For example, when you 7 | are browsing a big program file, you could highlight multiple identifiers in 8 | parallel. This will make it easier to trace the source code. 9 | 10 | This is a continuation of vimscript #1238 by Yuheng Xie, who apparently 11 | doesn't maintain his original version anymore and cannot be reached via the 12 | email address in his profile. This plugin offers the following advantages over 13 | the original: 14 | - Much faster, all colored words can now be highlighted, no more clashes with 15 | syntax highlighting (due to use of matchadd()). 16 | - Many bug fixes. 17 | - Jumps behave like the built-in search, including wrap and error messages. 18 | - Like the built-in commands, jumps take an optional [count] to quickly skip 19 | over some marks. 20 | 21 | RELATED WORKS 22 | - MultipleSearch (vimscript #479) can highlight in a single window and in all 23 | buffers, but still relies on the :syntax highlighting method, which is 24 | slower and less reliable. 25 | - http://vim.wikia.com/wiki/Highlight_multiple_words offers control over the 26 | color used by mapping the 1-9 keys on the numeric keypad, persistence, and 27 | highlights only a single window. 28 | - highlight.vim (vimscript #1599) highlights lines or patterns of interest in 29 | different colors, using mappings that start with CTRL-H and work on cword. 30 | - quickhl.vim (vimscript #3692) can also list the matches with colors and in 31 | addition offers on-the-fly highlighting of the current word (like many IDEs 32 | do). 33 | 34 | HIGHLIGHTING 35 | m Mark the word under the cursor, similar to the star 36 | command. The next free highlight group is used. 37 | If already on a mark: Clear the mark, like 38 | n. 39 | {Visual}m Mark or unmark the visual selection. 40 | 41 | r Manually input a regular expression to mark. 42 | {Visual}r Ditto, based on the visual selection. 43 | 44 | n Clear the mark under the cursor. 45 | If not on a mark: Disable all marks, similar to 46 | :nohlsearch. 47 | 48 | :Mark {pattern} Mark or unmark {pattern}. 49 | :Mark Disable all marks, similar to :nohlsearch. Marks 50 | will automatically re-enable when a mark is added or 51 | removed, or a search for marks is performed. 52 | :MarkClear Clear all marks. In contrast to disabling marks, the 53 | actual mark information is cleared, the next mark will 54 | use the first highlight group. This cannot be undone. 55 | 56 | SEARCHING 57 | [count]* [count]# 58 | [count]* [count]# 59 | [count]/ [count]? 60 | Use these six keys to jump to the [count]'th next / 61 | previous occurrence of a mark. 62 | You could also use Vim's / and ? to search, since the 63 | mark patterns are (optionally, see configuration) 64 | added to the search history, too. 65 | 66 | Cursor over mark Cursor not over mark 67 | --------------------------------------------------------------------------- 68 | * Jump to the next occurrence of Jump to the next occurrence of 69 | current mark, and remember it "last mark". 70 | as "last mark". 71 | 72 | / Jump to the next occurrence of Same as left. 73 | ANY mark. 74 | 75 | * If * is the most recently Do Vim's original * command. 76 | used, do a *; otherwise 77 | (/ is the most recently 78 | used), do a /. 79 | 80 | MARK PERSISTENCE 81 | The marks can be kept and restored across Vim sessions, using the viminfo 82 | file. For this to work, the "!" flag must be part of the 'viminfo' setting: 83 | set viminfo+=! " Save and restore global variables. 84 | :MarkLoad Restore the marks from the previous Vim session. All 85 | current marks are discarded. 86 | :MarkSave Save the currently defined marks (or clear the 87 | persisted marks if no marks are currently defined) for 88 | use in a future Vim session. 89 | -------------------------------------------------------------------------------- /autoload/mark.vim: -------------------------------------------------------------------------------- 1 | " Script Name: mark.vim 2 | " Description: Highlight several words in different colors simultaneously. 3 | " 4 | " Copyright: (C) 2005-2008 by Yuheng Xie 5 | " (C) 2008-2011 by Ingo Karkat 6 | " The VIM LICENSE applies to this script; see ':help copyright'. 7 | " 8 | " Maintainer: Ingo Karkat 9 | " 10 | " Dependencies: 11 | " - SearchSpecial.vim autoload script (optional, for improved search messages). 12 | " 13 | " Version: 2.5.2 14 | " Changes: 15 | " 16 | " 09-Nov-2011, Ingo Karkat 17 | " - BUG: With a single match and 'wrapscan' set, a search error was issued 18 | " instead of the wrap message. Add check for l:isStuckAtCurrentMark && 19 | " l:isWrapped in the no-match part of s:Search(). 20 | " - FIX: In backwards search with single match, the :break short-circuits the 21 | " l:isWrapped logic, resets l:line and therefore also confuses the logic and 22 | " leads to wrong error message instead of wrap message. Don't reset l:line, 23 | " set l:isWrapped instead. 24 | " - FIX: Wrong logic for determining l:isWrapped lets wrap-around go undetected 25 | " when v:count >= number of total matches. [l:startLine, l:startCol] must 26 | " be updated on every iteration. 27 | " 28 | " 17-May-2011, Ingo Karkat 29 | " - Make s:GetVisualSelection() public to allow use in suggested 30 | " MarkSpaceIndifferent vmap. 31 | " - FIX: == comparison in s:DoMark() leads to wrong regexp (\A vs. \a) being 32 | " cleared when 'ignorecase' is set. Use case-sensitive comparison ==# instead. 33 | " 34 | " 10-May-2011, Ingo Karkat 35 | " - Refine :MarkLoad messages: Differentiate between nonexistent and empty 36 | " g:MARK_MARKS; add note when marks are disabled. 37 | " 38 | " 06-May-2011, Ingo Karkat 39 | " - Also print status message on :MarkClear to be consistent with :MarkToggle. 40 | " 41 | " 21-Apr-2011, Ingo Karkat 42 | " - Implement toggling of mark display (keeping the mark patterns, unlike the 43 | " clearing of marks), determined by s:enable. s:DoMark() now toggles on empty 44 | " regexp, affecting the \n mapping and :Mark. Introduced 45 | " s:EnableAndMarkScope() wrapper to correctly handle the highlighting updates 46 | " depending on whether marks were previously disabled. 47 | " - Implement persistence of s:enable via g:MARK_ENABLED. 48 | " - Generalize s:Enable() and combine with intermediate s:Disable() into 49 | " s:MarkEnable(), which also performs the persistence of s:enabled. 50 | " - Implement lazy-loading of disabled persistent marks via g:mwDoDeferredLoad 51 | " flag passed from plugin/mark.vim. 52 | " 53 | " 20-Apr-2011, Ingo Karkat 54 | " - Extract setting of s:pattern into s:SetPattern() and implement the automatic 55 | " persistence there. 56 | " 57 | " 19-Apr-2011, Ingo Karkat 58 | " - ENH: Add enabling functions for mark persistence: mark#Load() and 59 | " mark#ToPatternList(). 60 | " - Implement :MarkLoad and :MarkSave commands in mark#LoadCommand() and 61 | " mark#SaveCommand(). 62 | " - Remove superfluous update autocmd on VimEnter: Persistent marks trigger the 63 | " update themselves, same for :Mark commands which could potentially be issued 64 | " e.g. in .vimrc. Otherwise, when no marks are defined after startup, the 65 | " autosource script isn't even loaded yet, so the autocmd on the VimEnter 66 | " event isn't yet defined. 67 | " 68 | " 18-Apr-2011, Ingo Karkat 69 | " - BUG: Include trailing newline character in check for current mark, so that a 70 | " mark that matches the entire line (e.g. created by Vm) can be 71 | " cleared via n. Thanks to ping for reporting this. 72 | " - Minor restructuring of mark#MarkCurrentWord(). 73 | " - FIX: On overlapping marks, mark#CurrentMark() returned the lowest, not the 74 | " highest visible mark. So on overlapping marks, the one that was not visible 75 | " at the cursor position was removed; very confusing! Use reverse iteration 76 | " order. 77 | " - FIX: To avoid an arbitrary ordering of highlightings when the highlighting 78 | " group names roll over, and to avoid order inconsistencies across different 79 | " windows and tabs, we assign a different priority based on the highlighting 80 | " group. 81 | " - Rename s:cycleMax to s:markNum; the previous name was too 82 | " implementation-focused and off-by-one with regards to the actual value. 83 | " 84 | " 16-Apr-2011, Ingo Karkat 85 | " - Move configuration variable g:mwHistAdd to plugin/mark.vim (as is customary) 86 | " and make the remaining g:mw... variables script-local, as these contain 87 | " internal housekeeping information that does not need to be accessible by the 88 | " user. 89 | " - Add :MarkSave warning if 'viminfo' doesn't enable global variable 90 | " persistence. 91 | " 92 | " 15-Apr-2011, Ingo Karkat 93 | " - Robustness: Move initialization of w:mwMatch from mark#UpdateMark() to 94 | " s:MarkMatch(), where the variable is actually used. I had encountered cases 95 | " where it w:mwMatch was undefined when invoked through mark#DoMark() -> 96 | " s:MarkScope() -> s:MarkMatch(). This can be forced by :unlet w:mwMatch 97 | " followed by :Mark foo. 98 | " - Robustness: Checking for s:markNum == 0 in mark#DoMark(), trying to 99 | " re-detect the mark highlightings and finally printing an error instead of 100 | " choking. This can happen when somehow no mark highlightings are defined. 101 | " 102 | " 14-Jan-2011, Ingo Karkat 103 | " - FIX: Capturing the visual selection could still clobber the blockwise yank 104 | " mode of the unnamed register. 105 | " 106 | " 13-Jan-2011, Ingo Karkat 107 | " - FIX: Using a named register for capturing the visual selection on 108 | " {Visual}m and {Visual}r clobbered the unnamed register. Now 109 | " using the unnamed register. 110 | " 111 | " 13-Jul-2010, Ingo Karkat 112 | " - ENH: The MarkSearch mappings ([*#/?]) add the original cursor 113 | " position to the jump list, like the built-in [/?*#nN] commands. This allows 114 | " to use the regular jump commands for mark matches, like with regular search 115 | " matches. 116 | " 117 | " 19-Feb-2010, Andy Wokula 118 | " - BUG: Clearing of an accidental zero-width match (e.g. via :Mark \zs) results 119 | " in endless loop. Thanks to Andy Wokula for the patch. 120 | " 121 | " 17-Nov-2009, Ingo Karkat + Andy Wokula 122 | " - BUG: Creation of literal pattern via '\V' in {Visual}m mapping 123 | " collided with individual escaping done in m mapping so that an 124 | " escaped '\*' would be interpreted as a multi item when both modes are used 125 | " for marking. Replaced \V with s:EscapeText() to be consistent. Replaced the 126 | " (overly) generic mark#GetVisualSelectionEscaped() with 127 | " mark#GetVisualSelectionAsRegexp() and 128 | " mark#GetVisualSelectionAsLiteralPattern(). Thanks to Andy Wokula for the 129 | " patch. 130 | " 131 | " 06-Jul-2009, Ingo Karkat 132 | " - Re-wrote s:AnyMark() in functional programming style. 133 | " - Now resetting 'smartcase' before the search, this setting should not be 134 | " considered for *-command-alike searches and cannot be supported because all 135 | " mark patterns are concatenated into one large regexp, anyway. 136 | " 137 | " 04-Jul-2009, Ingo Karkat 138 | " - Re-wrote s:Search() to handle v:count: 139 | " - Obsoleted s:current_mark_position; mark#CurrentMark() now returns both the 140 | " mark text and start position. 141 | " - s:Search() now checks for a jump to the current mark during a backward 142 | " search; this eliminates a lot of logic at its calling sites. 143 | " - Reverted negative logic at calling sites; using empty() instead of != "". 144 | " - Now passing a:isBackward instead of optional flags into s:Search() and 145 | " around its callers. 146 | " - ':normal! zv' moved from callers into s:Search(). 147 | " - Removed delegation to SearchSpecial#ErrorMessage(), because the fallback 148 | " implementation is perfectly fine and the SearchSpecial routine changed its 149 | " output format into something unsuitable anyway. 150 | " - Using descriptive text instead of "@" (and appropriate highlighting) when 151 | " querying for the pattern to mark. 152 | " 153 | " 02-Jul-2009, Ingo Karkat 154 | " - Split off functions into autoload script. 155 | 156 | "- functions ------------------------------------------------------------------ 157 | function! s:EscapeText( text ) 158 | return substitute( escape(a:text, '\' . '^$.*[~'), "\n", '\\n', 'ge' ) 159 | endfunction 160 | " Mark the current word, like the built-in star command. 161 | " If the cursor is on an existing mark, remove it. 162 | function! mark#MarkCurrentWord() 163 | let l:regexp = mark#CurrentMark()[0] 164 | if empty(l:regexp) 165 | let l:cword = expand('') 166 | if ! empty(l:cword) 167 | let l:regexp = s:EscapeText(l:cword) 168 | " The star command only creates a \ search pattern if the 169 | " actually only consists of keyword characters. 170 | if l:cword =~# '^\k\+$' 171 | let l:regexp = '\<' . l:regexp . '\>' 172 | endif 173 | endif 174 | endif 175 | 176 | if ! empty(l:regexp) 177 | call mark#DoMark(l:regexp) 178 | endif 179 | endfunction 180 | 181 | function! mark#GetVisualSelection() 182 | let save_clipboard = &clipboard 183 | set clipboard= " Avoid clobbering the selection and clipboard registers. 184 | let save_reg = getreg('"') 185 | let save_regmode = getregtype('"') 186 | silent normal! gvy 187 | let res = getreg('"') 188 | call setreg('"', save_reg, save_regmode) 189 | let &clipboard = save_clipboard 190 | return res 191 | endfunction 192 | function! mark#GetVisualSelectionAsLiteralPattern() 193 | return s:EscapeText(mark#GetVisualSelection()) 194 | endfunction 195 | function! mark#GetVisualSelectionAsRegexp() 196 | return substitute(mark#GetVisualSelection(), '\n', '', 'g') 197 | endfunction 198 | 199 | " Manually input a regular expression. 200 | function! mark#MarkRegex( regexpPreset ) 201 | call inputsave() 202 | echohl Question 203 | let l:regexp = input('Input pattern to mark: ', a:regexpPreset) 204 | echohl None 205 | call inputrestore() 206 | if ! empty(l:regexp) 207 | call mark#DoMark(l:regexp) 208 | endif 209 | endfunction 210 | 211 | function! s:Cycle( ... ) 212 | let l:currentCycle = s:cycle 213 | let l:newCycle = (a:0 ? a:1 : s:cycle) + 1 214 | let s:cycle = (l:newCycle < s:markNum ? l:newCycle : 0) 215 | return l:currentCycle 216 | endfunction 217 | 218 | " Set match / clear matches in the current window. 219 | function! s:MarkMatch( indices, expr ) 220 | if ! exists('w:mwMatch') 221 | let w:mwMatch = repeat([0], s:markNum) 222 | endif 223 | 224 | for l:index in a:indices 225 | if w:mwMatch[l:index] > 0 226 | silent! call matchdelete(w:mwMatch[l:index]) 227 | let w:mwMatch[l:index] = 0 228 | endif 229 | endfor 230 | 231 | if ! empty(a:expr) 232 | let l:index = a:indices[0] " Can only set one index for now. 233 | 234 | " Info: matchadd() does not consider the 'magic' (it's always on), 235 | " 'ignorecase' and 'smartcase' settings. 236 | " Make the match according to the 'ignorecase' setting, like the star command. 237 | " (But honor an explicit case-sensitive regexp via the /\C/ atom.) 238 | let l:expr = ((&ignorecase && a:expr !~# '\\\@ 0 ? l:markCnt . ' ' : '') . 'marks' 323 | endif 324 | endfunction 325 | 326 | 327 | " Mark or unmark a regular expression. 328 | function! s:SetPattern( index, pattern ) 329 | let s:pattern[a:index] = a:pattern 330 | 331 | if g:mwAutoSaveMarks 332 | call s:SavePattern() 333 | endif 334 | endfunction 335 | function! mark#ClearAll() 336 | let i = 0 337 | let indices = [] 338 | while i < s:markNum 339 | if ! empty(s:pattern[i]) 340 | call s:SetPattern(i, '') 341 | call add(indices, i) 342 | endif 343 | let i += 1 344 | endwhile 345 | let s:lastSearch = '' 346 | 347 | " Re-enable marks; not strictly necessary, since all marks have just been 348 | " cleared, and marks will be re-enabled, anyway, when the first mark is added. 349 | " It's just more consistent for mark persistence. But save the full refresh, as 350 | " we do the update ourselves. 351 | call s:MarkEnable(0, 0) 352 | 353 | call s:MarkScope(l:indices, '') 354 | 355 | if len(indices) > 0 356 | echo 'Cleared all' len(indices) 'marks' 357 | else 358 | echo 'All marks cleared' 359 | endif 360 | endfunction 361 | function! mark#DoMark(...) " DoMark(regexp) 362 | let regexp = (a:0 ? a:1 : '') 363 | 364 | " Disable marks if regexp is empty. Otherwise, we will be either removing a 365 | " mark or adding one, so marks will be re-enabled. 366 | if empty(regexp) 367 | call mark#Toggle() 368 | return 369 | endif 370 | 371 | " clear the mark if it has been marked 372 | let i = 0 373 | while i < s:markNum 374 | if regexp ==# s:pattern[i] 375 | if s:lastSearch ==# s:pattern[i] 376 | let s:lastSearch = '' 377 | endif 378 | call s:SetPattern(i, '') 379 | call s:EnableAndMarkScope([i], '') 380 | return 381 | endif 382 | let i += 1 383 | endwhile 384 | 385 | if s:markNum <= 0 386 | " Uh, somehow no mark highlightings were defined. Try to detect them again. 387 | call mark#Init() 388 | if s:markNum <= 0 389 | " Still no mark highlightings; complain. 390 | let v:errmsg = 'No mark highlightings defined' 391 | echohl ErrorMsg 392 | echomsg v:errmsg 393 | echohl None 394 | return 395 | endif 396 | endif 397 | 398 | " add to history 399 | if stridx(g:mwHistAdd, '/') >= 0 400 | call histadd('/', regexp) 401 | endif 402 | if stridx(g:mwHistAdd, '@') >= 0 403 | call histadd('@', regexp) 404 | endif 405 | 406 | " choose an unused mark group 407 | let i = 0 408 | while i < s:markNum 409 | if empty(s:pattern[i]) 410 | call s:SetPattern(i, regexp) 411 | call s:Cycle(i) 412 | call s:EnableAndMarkScope([i], regexp) 413 | return 414 | endif 415 | let i += 1 416 | endwhile 417 | 418 | " choose a mark group by cycle 419 | let i = s:Cycle() 420 | if s:lastSearch ==# s:pattern[i] 421 | let s:lastSearch = '' 422 | endif 423 | call s:SetPattern(i, regexp) 424 | call s:EnableAndMarkScope([i], regexp) 425 | endfunction 426 | 427 | " Return [mark text, mark start position] of the mark under the cursor (or 428 | " ['', []] if there is no mark). 429 | " The mark can include the trailing newline character that concludes the line, 430 | " but marks that span multiple lines are not supported. 431 | function! mark#CurrentMark() 432 | let line = getline('.') . "\n" 433 | 434 | " Highlighting groups with higher numbers take precedence over lower numbers, 435 | " and therefore its marks appear "above" other marks. To retrieve the visible 436 | " mark in case of overlapping marks, we need to check from highest to lowest 437 | " highlighting group. 438 | let i = s:markNum - 1 439 | while i >= 0 440 | if ! empty(s:pattern[i]) 441 | " Note: col() is 1-based, all other indexes zero-based! 442 | let start = 0 443 | while start >= 0 && start < strlen(line) && start < col('.') 444 | let b = match(line, s:pattern[i], start) 445 | let e = matchend(line, s:pattern[i], start) 446 | if b < col('.') && col('.') <= e 447 | return [s:pattern[i], [line('.'), (b + 1)]] 448 | endif 449 | if b == e 450 | break 451 | endif 452 | let start = e 453 | endwhile 454 | endif 455 | let i -= 1 456 | endwhile 457 | return ['', []] 458 | endfunction 459 | 460 | " Search current mark. 461 | function! mark#SearchCurrentMark( isBackward ) 462 | let [l:markText, l:markPosition] = mark#CurrentMark() 463 | if empty(l:markText) 464 | if empty(s:lastSearch) 465 | call mark#SearchAnyMark(a:isBackward) 466 | let s:lastSearch = mark#CurrentMark()[0] 467 | else 468 | call s:Search(s:lastSearch, a:isBackward, [], 'same-mark') 469 | endif 470 | else 471 | call s:Search(l:markText, a:isBackward, l:markPosition, (l:markText ==# s:lastSearch ? 'same-mark' : 'new-mark')) 472 | let s:lastSearch = l:markText 473 | endif 474 | endfunction 475 | 476 | silent! call SearchSpecial#DoesNotExist() " Execute a function to force autoload. 477 | if exists('*SearchSpecial#WrapMessage') 478 | function! s:WrapMessage( searchType, searchPattern, isBackward ) 479 | redraw 480 | call SearchSpecial#WrapMessage(a:searchType, a:searchPattern, a:isBackward) 481 | endfunction 482 | function! s:EchoSearchPattern( searchType, searchPattern, isBackward ) 483 | call SearchSpecial#EchoSearchPattern(a:searchType, a:searchPattern, a:isBackward) 484 | endfunction 485 | else 486 | function! s:Trim( message ) 487 | " Limit length to avoid "Hit ENTER" prompt. 488 | return strpart(a:message, 0, (&columns / 2)) . (len(a:message) > (&columns / 2) ? "..." : "") 489 | endfunction 490 | function! s:WrapMessage( searchType, searchPattern, isBackward ) 491 | redraw 492 | let v:warningmsg = printf('%s search hit %s, continuing at %s', a:searchType, (a:isBackward ? 'TOP' : 'BOTTOM'), (a:isBackward ? 'BOTTOM' : 'TOP')) 493 | echohl WarningMsg 494 | echo s:Trim(v:warningmsg) 495 | echohl None 496 | endfunction 497 | function! s:EchoSearchPattern( searchType, searchPattern, isBackward ) 498 | let l:message = (a:isBackward ? '?' : '/') . a:searchPattern 499 | echohl SearchSpecialSearchType 500 | echo a:searchType 501 | echohl None 502 | echon s:Trim(l:message) 503 | endfunction 504 | endif 505 | function! s:ErrorMessage( searchType, searchPattern, isBackward ) 506 | if &wrapscan 507 | let v:errmsg = a:searchType . ' not found: ' . a:searchPattern 508 | else 509 | let v:errmsg = printf('%s search hit %s without match for: %s', a:searchType, (a:isBackward ? 'TOP' : 'BOTTOM'), a:searchPattern) 510 | endif 511 | echohl ErrorMsg 512 | echomsg v:errmsg 513 | echohl None 514 | endfunction 515 | 516 | " Wrapper around search() with additonal search and error messages and "wrapscan" warning. 517 | function! s:Search( pattern, isBackward, currentMarkPosition, searchType ) 518 | let l:save_view = winsaveview() 519 | 520 | " searchpos() obeys the 'smartcase' setting; however, this setting doesn't 521 | " make sense for the mark search, because all patterns for the marks are 522 | " concatenated as branches in one large regexp, and because patterns that 523 | " result from the *-command-alike mappings should not obey 'smartcase' (like 524 | " the * command itself), anyway. If the :Mark command wants to support 525 | " 'smartcase', it'd have to emulate that into the regular expression. 526 | let l:save_smartcase = &smartcase 527 | set nosmartcase 528 | 529 | let l:count = v:count1 530 | let l:isWrapped = 0 531 | let l:isMatch = 0 532 | let l:line = 0 533 | while l:count > 0 534 | let [l:startLine, l:startCol] = [line('.'), col('.')] 535 | 536 | " Search for next match, 'wrapscan' applies. 537 | let [l:line, l:col] = searchpos( a:pattern, (a:isBackward ? 'b' : '') ) 538 | 539 | "****D echomsg '****' a:isBackward string([l:line, l:col]) string(a:currentMarkPosition) l:count 540 | if a:isBackward && l:line > 0 && [l:line, l:col] == a:currentMarkPosition && l:count == v:count1 541 | " On a search in backward direction, the first match is the start of the 542 | " current mark (if the cursor was positioned on the current mark text, and 543 | " not at the start of the mark text). 544 | " In contrast to the normal search, this is not considered the first 545 | " match. The mark text is one entity; if the cursor is positioned anywhere 546 | " inside the mark text, the mark text is considered the current mark. The 547 | " built-in '*' and '#' commands behave in the same way; the entire 548 | " text is considered the current match, and jumps move outside that text. 549 | " In normal search, the cursor can be positioned anywhere (via offsets) 550 | " around the search, and only that single cursor position is considered 551 | " the current match. 552 | " Thus, the search is retried without a decrease of l:count, but only if 553 | " this was the first match; repeat visits during wrapping around count as 554 | " a regular match. The search also must not be retried when this is the 555 | " first match, but we've been here before (i.e. l:isMatch is set): This 556 | " means that there is only the current mark in the buffer, and we must 557 | " break out of the loop and indicate that search wrapped around and no 558 | " other mark was found. 559 | if l:isMatch 560 | let l:isWrapped = 1 561 | break 562 | endif 563 | 564 | " The l:isMatch flag is set so if the final mark cannot be reached, the 565 | " original cursor position is restored. This flag also allows us to detect 566 | " whether we've been here before, which is checked above. 567 | let l:isMatch = 1 568 | elseif l:line > 0 569 | let l:isMatch = 1 570 | let l:count -= 1 571 | 572 | " Note: No need to check 'wrapscan'; the wrapping can only occur if 573 | " 'wrapscan' is actually on. 574 | if ! a:isBackward && (l:startLine > l:line || l:startLine == l:line && l:startCol >= l:col) 575 | let l:isWrapped = 1 576 | elseif a:isBackward && (l:startLine < l:line || l:startLine == l:line && l:startCol <= l:col) 577 | let l:isWrapped = 1 578 | endif 579 | else 580 | break 581 | endif 582 | endwhile 583 | let &smartcase = l:save_smartcase 584 | 585 | " We're not stuck when the search wrapped around and landed on the current 586 | " mark; that's why we exclude a possible wrap-around via v:count1 == 1. 587 | let l:isStuckAtCurrentMark = ([l:line, l:col] == a:currentMarkPosition && v:count1 == 1) 588 | "****D echomsg '****' l:line l:isStuckAtCurrentMark l:isWrapped l:isMatch string([l:line, l:col]) string(a:currentMarkPosition) 589 | if l:line > 0 && ! l:isStuckAtCurrentMark 590 | let l:matchPosition = getpos('.') 591 | 592 | " Open fold at the search result, like the built-in commands. 593 | normal! zv 594 | 595 | " Add the original cursor position to the jump list, like the 596 | " [/?*#nN] commands. 597 | " Implementation: Memorize the match position, restore the view to the state 598 | " before the search, then jump straight back to the match position. This 599 | " also allows us to set a jump only if a match was found. (:call 600 | " setpos("''", ...) doesn't work in Vim 7.2) 601 | call winrestview(l:save_view) 602 | normal! m' 603 | call setpos('.', l:matchPosition) 604 | 605 | " Enable marks (in case they were disabled) after arriving at the mark (to 606 | " avoid unnecessary screen updates) but before the error message (to avoid 607 | " it getting lost due to the screen updates). 608 | call s:MarkEnable(1) 609 | 610 | if l:isWrapped 611 | call s:WrapMessage(a:searchType, a:pattern, a:isBackward) 612 | else 613 | call s:EchoSearchPattern(a:searchType, a:pattern, a:isBackward) 614 | endif 615 | return 1 616 | else 617 | if l:isMatch 618 | " The view has been changed by moving through matches until the end / 619 | " start of file, when 'nowrapscan' forced a stop of searching before the 620 | " l:count'th match was found. 621 | " Restore the view to the state before the search. 622 | call winrestview(l:save_view) 623 | endif 624 | 625 | " Enable marks (in case they were disabled) after arriving at the mark (to 626 | " avoid unnecessary screen updates) but before the error message (to avoid 627 | " it getting lost due to the screen updates). 628 | call s:MarkEnable(1) 629 | 630 | if l:line > 0 && l:isStuckAtCurrentMark && l:isWrapped 631 | call s:WrapMessage(a:searchType, a:pattern, a:isBackward) 632 | return 1 633 | else 634 | call s:ErrorMessage(a:searchType, a:pattern, a:isBackward) 635 | return 0 636 | endif 637 | endif 638 | endfunction 639 | 640 | " Combine all marks into one regexp. 641 | function! s:AnyMark() 642 | return join(filter(copy(s:pattern), '! empty(v:val)'), '\|') 643 | endfunction 644 | 645 | " Search any mark. 646 | function! mark#SearchAnyMark( isBackward ) 647 | let l:markPosition = mark#CurrentMark()[1] 648 | let l:markText = s:AnyMark() 649 | call s:Search(l:markText, a:isBackward, l:markPosition, 'any-mark') 650 | let s:lastSearch = "" 651 | endfunction 652 | 653 | " Search last searched mark. 654 | function! mark#SearchNext( isBackward ) 655 | let l:markText = mark#CurrentMark()[0] 656 | if empty(l:markText) 657 | return 0 658 | else 659 | if empty(s:lastSearch) 660 | call mark#SearchAnyMark(a:isBackward) 661 | else 662 | call mark#SearchCurrentMark(a:isBackward) 663 | endif 664 | return 1 665 | endif 666 | endfunction 667 | 668 | " Load mark patterns from list. 669 | function! mark#Load( pattern, enabled ) 670 | if s:markNum > 0 && len(a:pattern) > 0 671 | " Initialize mark patterns with the passed list. Ensure that, regardless of 672 | " the list length, s:pattern contains exactly s:markNum elements. 673 | let s:pattern = a:pattern[0:(s:markNum - 1)] 674 | let s:pattern += repeat([''], (s:markNum - len(s:pattern))) 675 | 676 | let s:enabled = a:enabled 677 | 678 | call mark#UpdateScope() 679 | 680 | " The list of patterns may be sparse, return only the actual patterns. 681 | return len(filter(copy(a:pattern), '! empty(v:val)')) 682 | endif 683 | return 0 684 | endfunction 685 | 686 | " Access the list of mark patterns. 687 | function! mark#ToPatternList() 688 | " Trim unused patterns from the end of the list, the amount of available marks 689 | " may differ on the next invocation (e.g. due to a different number of 690 | " highlight groups in Vim and GVIM). We want to keep empty patterns in the 691 | " front and middle to maintain the mapping to highlight groups, though. 692 | let l:highestNonEmptyIndex = s:markNum -1 693 | while l:highestNonEmptyIndex >= 0 && empty(s:pattern[l:highestNonEmptyIndex]) 694 | let l:highestNonEmptyIndex -= 1 695 | endwhile 696 | 697 | return (l:highestNonEmptyIndex < 0 ? [] : s:pattern[0:l:highestNonEmptyIndex]) 698 | endfunction 699 | 700 | " :MarkLoad command. 701 | function! mark#LoadCommand( isShowMessages ) 702 | if exists('g:MARK_MARKS') 703 | try 704 | " Persistent global variables cannot be of type List, so we actually store 705 | " the string representation, and eval() it back to a List. 706 | execute 'let l:loadedMarkNum = mark#Load(' . g:MARK_MARKS . ', ' . (exists('g:MARK_ENABLED') ? g:MARK_ENABLED : 1) . ')' 707 | if a:isShowMessages 708 | if l:loadedMarkNum == 0 709 | echomsg 'No persistent marks defined' 710 | else 711 | echomsg printf('Loaded %d mark%s', l:loadedMarkNum, (l:loadedMarkNum == 1 ? '' : 's')) . (s:enabled ? '' : '; marks currently disabled') 712 | endif 713 | endif 714 | catch /^Vim\%((\a\+)\)\=:E/ 715 | let v:errmsg = 'Corrupted persistent mark info in g:MARK_MARKS and g:MARK_ENABLED' 716 | echohl ErrorMsg 717 | echomsg v:errmsg 718 | echohl None 719 | 720 | unlet! g:MARK_MARKS 721 | unlet! g:MARK_ENABLED 722 | endtry 723 | elseif a:isShowMessages 724 | let v:errmsg = 'No persistent marks found' 725 | echohl ErrorMsg 726 | echomsg v:errmsg 727 | echohl None 728 | endif 729 | endfunction 730 | 731 | " :MarkSave command. 732 | function! s:SavePattern() 733 | let l:savedMarks = mark#ToPatternList() 734 | let g:MARK_MARKS = string(l:savedMarks) 735 | let g:MARK_ENABLED = s:enabled 736 | return ! empty(l:savedMarks) 737 | endfunction 738 | function! mark#SaveCommand() 739 | if index(split(&viminfo, ','), '!') == -1 740 | let v:errmsg = "Cannot persist marks, need ! flag in 'viminfo': :set viminfo+=!" 741 | echohl ErrorMsg 742 | echomsg v:errmsg 743 | echohl None 744 | return 745 | endif 746 | 747 | if ! s:SavePattern() 748 | let v:warningmsg = 'No marks defined' 749 | echohl WarningMsg 750 | echomsg v:warningmsg 751 | echohl None 752 | endif 753 | endfunction 754 | 755 | 756 | "- initializations ------------------------------------------------------------ 757 | augroup Mark 758 | autocmd! 759 | autocmd WinEnter * if ! exists('w:mwMatch') | call mark#UpdateMark() | endif 760 | autocmd TabEnter * call mark#UpdateScope() 761 | augroup END 762 | 763 | " Define global variables and initialize current scope. 764 | function! mark#Init() 765 | let s:markNum = 0 766 | while hlexists('MarkWord' . (s:markNum + 1)) 767 | let s:markNum += 1 768 | endwhile 769 | let s:pattern = repeat([''], s:markNum) 770 | let s:cycle = 0 771 | let s:lastSearch = '' 772 | let s:enabled = 1 773 | endfunction 774 | 775 | call mark#Init() 776 | if exists('g:mwDoDeferredLoad') && g:mwDoDeferredLoad 777 | unlet g:mwDoDeferredLoad 778 | call mark#LoadCommand(0) 779 | else 780 | call mark#UpdateScope() 781 | endif 782 | 783 | " vim: ts=2 sw=2 784 | -------------------------------------------------------------------------------- /doc/mark.txt: -------------------------------------------------------------------------------- 1 | *mark.txt* Highlight several words in different colors simultaneously. 2 | 3 | MARK by Ingo Karkat 4 | (original version by Yuheng Xie) 5 | *mark.vim* 6 | description |mark-description| 7 | usage |mark-usage| 8 | installation |mark-installation| 9 | configuration |mark-configuration| 10 | limitations |mark-limitations| 11 | known problems |mark-known-problems| 12 | todo |mark-todo| 13 | history |mark-history| 14 | 15 | ============================================================================== 16 | DESCRIPTION *mark-description* 17 | 18 | This plugin adds mappings and a :Mark command to highlight several words in 19 | different colors simultaneously, similar to the built-in 'hlsearch' 20 | highlighting of search results and the * |star| command. For example, when you 21 | are browsing a big program file, you could highlight multiple identifiers in 22 | parallel. This will make it easier to trace the source code. 23 | 24 | This is a continuation of vimscript #1238 by Yuheng Xie, who apparently 25 | doesn't maintain his original version anymore and cannot be reached via the 26 | email address in his profile. This plugin offers the following advantages over 27 | the original: 28 | - Much faster, all colored words can now be highlighted, no more clashes with 29 | syntax highlighting (due to use of matchadd()). 30 | - Many bug fixes. 31 | - Jumps behave like the built-in search, including wrap and error messages. 32 | - Like the built-in commands, jumps take an optional [count] to quickly skip 33 | over some marks. 34 | 35 | RELATED WORKS * 36 | 37 | - MultipleSearch (vimscript #479) can highlight in a single window and in all 38 | buffers, but still relies on the :syntax highlighting method, which is 39 | slower and less reliable. 40 | - http://vim.wikia.com/wiki/Highlight_multiple_words offers control over the 41 | color used by mapping the 1-9 keys on the numeric keypad, persistence, and 42 | highlights only a single window. 43 | - highlight.vim (vimscript #1599) highlights lines or patterns of interest in 44 | different colors, using mappings that start with CTRL-H and work on cword. 45 | - quickhl.vim (vimscript #3692) can also list the matches with colors and in 46 | addition offers on-the-fly highlighting of the current word (like many IDEs 47 | do). 48 | 49 | ============================================================================== 50 | USAGE *mark-usage* 51 | 52 | HIGHLIGHTING *mark-highlighting* 53 | *m* *v_m* 54 | m Mark the word under the cursor, similar to the |star| 55 | command. The next free highlight group is used. 56 | If already on a mark: Clear the mark, like 57 | |n|. 58 | {Visual}m Mark or unmark the visual selection. 59 | *r* *v_r* 60 | r Manually input a regular expression to mark. 61 | {Visual}r Ditto, based on the visual selection. 62 | 63 | In accordance with the built-in |star| command, 64 | all these mappings use 'ignorecase', but not 65 | 'smartcase'. 66 | *n* 67 | n Clear the mark under the cursor. 68 | If not on a mark: Disable all marks, similar to 69 | |:nohlsearch|. 70 | 71 | Note: Marks that span multiple lines are not detected, 72 | so the use of n on such a mark will 73 | unintentionally remove all marks! Use 74 | {Visual}r or :Mark {pattern} to clear 75 | multi-line marks. 76 | *:Mark* 77 | :Mark {pattern} Mark or unmark {pattern}. 78 | For implementation reasons, {pattern} cannot use the 79 | 'smartcase' setting, only 'ignorecase'. 80 | :Mark Disable all marks, similar to |:nohlsearch|. Marks 81 | will automatically re-enable when a mark is added or 82 | removed, or a search for marks is performed. 83 | *:MarkClear* 84 | :MarkClear Clear all marks. In contrast to disabling marks, the 85 | actual mark information is cleared, the next mark will 86 | use the first highlight group. This cannot be undone. 87 | 88 | 89 | SEARCHING *mark-searching* 90 | *star* *#* */* *?* 91 | [count]* [count]# 92 | [count]* [count]# 93 | [count]/ [count]? 94 | Use these six keys to jump to the [count]'th next / 95 | previous occurrence of a mark. 96 | You could also use Vim's / and ? to search, since the 97 | mark patterns are (optionally, see configuration) 98 | added to the search history, too. 99 | 100 | Cursor over mark Cursor not over mark 101 | --------------------------------------------------------------------------- 102 | * Jump to the next occurrence of Jump to the next occurrence of 103 | current mark, and remember it "last mark". 104 | as "last mark". 105 | 106 | / Jump to the next occurrence of Same as left. 107 | ANY mark. 108 | 109 | * If * is the most recently Do Vim's original * command. 110 | used, do a *; otherwise 111 | (/ is the most recently 112 | used), do a /. 113 | 114 | Note: When the cursor is on a mark, the backwards 115 | search does not jump to the beginning of the current 116 | mark (like the built-in search), but to the previous 117 | mark. The entire mark text is treated as one entity. 118 | 119 | You can use Vim's |jumplist| to go back to previous 120 | mark matches and the position before a mark search. 121 | 122 | MARK PERSISTENCE *mark-persistence* 123 | 124 | The marks can be kept and restored across Vim sessions, using the |viminfo| 125 | file. For this to work, the "!" flag must be part of the 'viminfo' setting: > 126 | set viminfo+=! " Save and restore global variables. 127 | < *:MarkLoad* 128 | :MarkLoad Restore the marks from the previous Vim session. All 129 | current marks are discarded. 130 | *:MarkSave* 131 | :MarkSave Save the currently defined marks (or clear the 132 | persisted marks if no marks are currently defined) for 133 | use in a future Vim session. 134 | 135 | By default, automatic persistence is enabled (so you don't need to explicitly 136 | |:MarkSave|), but you have to explicitly load the persisted marks in a new Vim 137 | session via |:MarkLoad|, to avoid that you accidentally drag along outdated 138 | highlightings from Vim session to session, and be surprised by the arbitrary 139 | highlight groups and occasional appearance of forgotten marks. If you want 140 | just that though and automatically restore any marks, set |g:mwAutoLoadMarks|. 141 | 142 | You can also initialize the marks to static values, e.g. by including this in 143 | |vimrc|: > 144 | runtime plugin/mark.vim 145 | silent MarkClear 146 | Mark foo 147 | Mark bar 148 | Or you can define custom commands that preset certain marks: > 149 | command -bar MyMarks silent MarkClear | execute 'Mark foo' | execute 'Mark bar' 150 | Or a command that adds to the existing marks and then toggles them: > 151 | command -bar ToggleFooBarMarks execute 'Mark foo' | execute 'Mark bar' 152 | 153 | ============================================================================== 154 | INSTALLATION *mark-installation* 155 | 156 | This script is packaged as a|vimball|. If you have the "gunzip" decompressor 157 | in your PATH, simply edit the *.vba.gz package in Vim; otherwise, decompress 158 | the archive first, e.g. using WinZip. Inside Vim, install by sourcing the 159 | vimball or via the |:UseVimball| command. > 160 | vim mark.vba.gz 161 | :so % 162 | To uninstall, use the |:RmVimball| command. 163 | 164 | DEPENDENCIES *mark-dependencies* 165 | 166 | - Requires Vim 7.1 with "matchadd()", or Vim 7.2 or higher. 167 | 168 | ============================================================================== 169 | CONFIGURATION *mark-configuration* 170 | 171 | For a permanent configuration, put the following commands into your |vimrc|. 172 | 173 | *mark-highlight-colors* 174 | You may define your own colors or more than the default 6 highlightings in 175 | your vimrc file (or anywhere before this plugin is sourced), in the following 176 | form (where N = 1..): > 177 | highlight MarkWordN ctermbg=Cyan ctermfg=Black guibg=#8CCBEA guifg=Black 178 | Higher numbers always take precedence and are displayed above lower ones. 179 | 180 | The search type highlighting (in the search message) can be changed via: > 181 | highlight link SearchSpecialSearchType MoreMsg 182 | < 183 | *g:mwHistAdd* 184 | By default, any marked words are also added to the search (/) and input (@) 185 | history; if you don't want that, remove the corresponding symbols from: > 186 | let g:mwHistAdd = '/@' 187 | < 188 | *g:mwAutoLoadMarks* 189 | To enable the automatic restore of marks from a previous Vim session: > 190 | let g:mwAutoLoadMarks = 1 191 | < *g:mwAutoSaveMarks* 192 | To turn off the automatic persistence of marks across Vim sessions: > 193 | let g:mwAutoSaveMarks = 0 194 | You can still explicitly save marks via |:MarkSave|. 195 | 196 | *mark-mappings* 197 | You can use different mappings by mapping to the Mark... mappings (use 198 | ":map Mark" to list them all) before this plugin is sourced. 199 | 200 | There are no default mappings for toggling all marks and for the |:MarkClear| 201 | command, but you can define some yourself: > 202 | nmap M MarkToggle 203 | nmap N MarkAllClear 204 | < 205 | To remove the default overriding of * and #, use: > 206 | nmap IgnoreMarkSearchNext MarkSearchNext 207 | nmap IgnoreMarkSearchPrev MarkSearchPrev 208 | < 209 | *mark-whitespace-indifferent* 210 | Some people like to create a mark based on the visual selection, like 211 | |v_m|, but have whitespace in the selection match any whitespace when 212 | searching (searching for "hello world" will also find "helloworld" as 213 | well as "hello" at the end of a line, with "world" at the start of the next 214 | line). The Vim Tips Wiki describes such a setup for the built-in search at 215 | http://vim.wikia.com/wiki/Search_for_visually_selected_text 216 | You can achieve the same with the Mark plugin through the following scriptlet: > 217 | function! s:GetVisualSelectionAsLiteralWhitespaceIndifferentPattern() 218 | return substitute(escape(mark#GetVisualSelection(), '\' . '^$.*[~'), '\_s\+', '\\_s\\+', 'g') 219 | endfunction 220 | vnoremap MarkWhitespaceIndifferent :call mark#DoMark(GetVisualSelectionAsLiteralWhitespaceIndifferentPattern()) 221 | Using this, you can assign a new visual mode mapping * > 222 | vmap * MarkWhitespaceIndifferent 223 | or override the default |v_m| mapping, in case you always want this 224 | behavior: > 225 | vmap IgnoreMarkSet MarkSet 226 | vmap m MarkWhitespaceIndifferent 227 | < 228 | ============================================================================== 229 | LIMITATIONS *mark-limitations* 230 | 231 | - If the 'ignorecase' setting is changed, there will be discrepancies between 232 | the highlighted marks and subsequent jumps to marks. 233 | - If {pattern} in a :Mark command contains atoms that change the semantics of 234 | the entire (|/\c|, |/\C|) or following (|/\v|,|/\V|, |/\M|) regular 235 | expression, there may be discrepancies between the highlighted marks and 236 | subsequent jumps to marks. 237 | 238 | KNOWN PROBLEMS *mark-known-problems* 239 | 240 | TODO *mark-todo* 241 | 242 | IDEAS *mark-ideas* 243 | 244 | Taken from an alternative implementation at 245 | http://vim.wikia.com/wiki/Highlight_multiple_words: 246 | - Allow to specify the highlight group number via :[N]Mark {regexp} 247 | - Use keys 1-9 on the numeric keypad to toggle a highlight group number. 248 | 249 | ============================================================================== 250 | HISTORY *mark-history* 251 | 252 | 2.5.2 09-Nov-2011 253 | Fixed various problems with wrap-around warnings: 254 | - BUG: With a single match and 'wrapscan' set, a search error was issued. 255 | - FIX: Backwards search with single match leads to wrong error message 256 | instead. 257 | - FIX: Wrong logic for determining l:isWrapped lets wrap-around go undetected. 258 | 259 | 2.5.1 17-May-2011 260 | - FIX: == comparison in s:DoMark() leads to wrong regexp (\A vs. \a) being 261 | cleared when 'ignorecase' is set. Use case-sensitive comparison ==# instead. 262 | - Refine :MarkLoad messages 263 | - Add whitespace-indifferent visual mark configuration example. Thanks to Greg 264 | Klein for the suggestion. 265 | 266 | 2.5.0 07-May-2011 267 | - ENH: Add explicit mark persistence via :MarkLoad and :MarkSave commands and 268 | automatic persistence via the g:mwAutoLoadMarks and g:mwAutoSaveMarks 269 | configuration flags. (Request from Mun Johl, 16-Apr-2010) 270 | - Expose toggling of mark display (keeping the mark patterns) via new 271 | MarkToggle mapping. Offer :MarkClear command as a replacement for the 272 | old argumentless :Mark command, which now just disables, but not clears all 273 | marks. 274 | 275 | 2.4.4 18-Apr-2011 276 | - BUG: Include trailing newline character in check for current mark, so that a 277 | mark that matches the entire line (e.g. created by Vm) can be 278 | cleared via n. Thanks to ping for reporting this. 279 | - FIX: On overlapping marks, mark#CurrentMark() returned the lowest, not the 280 | highest visible mark. So on overlapping marks, the one that was not visible 281 | at the cursor position was removed; very confusing! Use reverse iteration 282 | order. 283 | - FIX: To avoid an arbitrary ordering of highlightings when the highlighting 284 | group names roll over, and to avoid order inconsistencies across different 285 | windows and tabs, we assign a different priority based on the highlighting 286 | group. 287 | 288 | 2.4.3 16-Apr-2011 289 | - Avoid losing the mark highlightings on :syn on or :colorscheme commands. 290 | Thanks to Zhou YiChao for alerting me to this issue and suggesting a fix. 291 | - Made the script more robust when somehow no highlightings have been defined 292 | or when the window-local reckoning of match IDs got lost. I had very 293 | occasionally encountered such script errors in the past. 294 | - Made global housekeeping variables script-local, only g:mwHistAdd is used 295 | for configuration. 296 | 297 | 2.4.2 14-Jan-2011 (unreleased) 298 | - FIX: Capturing the visual selection could still clobber the blockwise yank 299 | mode of the unnamed register. 300 | 301 | 2.4.1 13-Jan-2011 302 | - FIX: Using a named register for capturing the visual selection on 303 | {Visual}m and {Visual}r clobbered the unnamed register. Now 304 | using the unnamed register. 305 | 306 | 2.4.0 13-Jul-2010 307 | - ENH: The MarkSearch mappings ([*#/?]) add the original cursor 308 | position to the jump list, like the built-in [/?*#nN] commands. This allows 309 | to use the regular jump commands for mark matches, like with regular search 310 | matches. 311 | 312 | 2.3.3 19-Feb-2010 313 | - BUG: Clearing of an accidental zero-width match (e.g. via :Mark \zs) results 314 | in endless loop. Thanks to Andy Wokula for the patch. 315 | 316 | 2.3.2 17-Nov-2009 317 | - BUG: Creation of literal pattern via '\V' in {Visual}m mapping 318 | collided with individual escaping done in m mapping so that an 319 | escaped '\*' would be interpreted as a multi item when both modes are used 320 | for marking. Thanks to Andy Wokula for the patch. 321 | 322 | 2.3.1 06-Jul-2009 323 | - Now working correctly when 'smartcase' is set. All mappings and the :Mark 324 | command use 'ignorecase', but not 'smartcase'. 325 | 326 | 2.3.0 04-Jul-2009 327 | - All jump commands now take an optional [count], so you can quickly skip over 328 | some marks, as with the built-in */# and n/N commands. For this, the entire 329 | core search algorithm has been rewritten. The script's logic has been 330 | simplified through the use of Vim 7 features like Lists. 331 | - Now also printing a Vim-alike search error message when 'nowrapscan' is set. 332 | 333 | 2.2.0 02-Jul-2009 334 | - Split off functions into autoload script. 335 | - Initialization of global variables and autocommands is now done lazily on 336 | the first use, not during loading of the plugin. This reduces Vim startup 337 | time and footprint as long as the functionality isn't yet used. 338 | - Split off documentation into separate help file. Now packaging as VimBall. 339 | 340 | 341 | 2.1.0 06-Jun-2009 342 | - Replaced highlighting via :syntax with matchadd() / matchdelete(). This 343 | requires Vim 7.2 / 7.1 with patches. This method is faster, there are no 344 | more clashes with syntax highlighting (:match always has preference), and 345 | the background highlighting does not disappear under 'cursorline'. 346 | - Using winrestcmd() to fix effects of :windo: By entering a window, its 347 | height is potentially increased from 0 to 1. 348 | - Handling multiple tabs by calling s:UpdateScope() on the TabEnter event. 349 | 350 | 2.0.0 01-Jun-2009 351 | - Now using Vim List for g:mwWord and thus requiring Vim 7. g:mwCycle is now 352 | zero-based, but the syntax groups "MarkWordx" are still one-based. 353 | - Factored :syntax operations out of s:DoMark() and s:UpdateMark() so that 354 | they can all be done in a single :windo. 355 | - Normal mode MarkSet now has the same semantics as its visual mode 356 | cousin: If the cursor is on an existing mark, the mark is removed. 357 | Beforehand, one could only remove a visually selected mark via again 358 | selecting it. Now, one simply can invoke the mapping when on such a mark. 359 | 360 | 1.6.1 31-May-2009 361 | Publication of improved version by Ingo Karkat. 362 | - Now prepending search type ("any-mark", "same-mark", "new-mark") for better 363 | identification. 364 | - Retired the algorithm in s:PrevWord in favor of simply using , which 365 | makes mark.vim work like the * command. At the end of a line, non-keyword 366 | characters may now be marked; the previous algorithm preferred any preceding 367 | word. 368 | - BF: If 'iskeyword' contains characters that have a special meaning in a 369 | regexp (e.g. [.*]), these are now escaped properly. 370 | - Highlighting can now actually be overridden in the vimrc (anywhere _before_ 371 | sourcing this script) by using ':hi def'. 372 | - Added missing setter for re-inclusion guard. 373 | 374 | 1.5.0 01-Sep-2008 375 | Bug fixes and enhancements by Ingo Karkat. 376 | - Added MarkAllClear (without a default mapping), which clears all 377 | marks, even when the cursor is on a mark. 378 | - Added ... mappings for hard-coded \*, \#, \/, \?, * and #, to allow 379 | re-mapping and disabling. Beforehand, there were some ... mappings 380 | and hard-coded ones; now, everything can be customized. 381 | - BF: Using :autocmd without to avoid removing _all_ autocmds for the 382 | BufWinEnter event. (Using a custom :augroup would be even better.) 383 | - BF: Explicitly defining s:current_mark_position; some execution paths left 384 | it undefined, causing errors. 385 | - ENH: Make the match according to the 'ignorecase' setting, like the star 386 | command. 387 | - ENH: The jumps to the next/prev occurrence now print 'search hit BOTTOM, 388 | continuing at TOP" and "Pattern not found:..." messages, like the * and n/N 389 | Vim search commands. 390 | - ENH: Jumps now open folds if the occurrence is inside a closed fold, just 391 | like n/N do. 392 | 393 | 1.1.8-g 25-Apr-2008 394 | Last version published by Yuheng Xie on vim.org. 395 | 396 | 1.1.2 22-Mar-2005 397 | Initial version published by Yuheng Xie on vim.org. 398 | 399 | ============================================================================== 400 | Copyright: (C) 2005-2008 by Yuheng Xie 401 | (C) 2008-2011 by Ingo Karkat 402 | The VIM LICENSE applies to this script; see|copyright|. 403 | 404 | Maintainer: Ingo Karkat 405 | ============================================================================== 406 | vim:tw=78:ts=8:ft=help:norl: 407 | -------------------------------------------------------------------------------- /plugin/mark.vim: -------------------------------------------------------------------------------- 1 | " Script Name: mark.vim 2 | " Description: Highlight several words in different colors simultaneously. 3 | " 4 | " Copyright: (C) 2005-2008 by Yuheng Xie 5 | " (C) 2008-2011 by Ingo Karkat 6 | " The VIM LICENSE applies to this script; see ':help copyright'. 7 | " 8 | " Maintainer: Ingo Karkat 9 | " Orig Author: Yuheng Xie 10 | " Contributors:Luc Hermitte, Ingo Karkat 11 | " 12 | " Dependencies: 13 | " - Requires Vim 7.1 with "matchadd()", or Vim 7.2 or higher. 14 | " - mark.vim autoload script. 15 | " 16 | " Version: 2.5.0 17 | " Changes: 18 | " 06-May-2011, Ingo Karkat 19 | " - By default, enable g:mwAutoSaveMarks, so that marks are always persisted, 20 | " but disable g:mwAutoLoadMarks, so that persisted marks have to be explicitly 21 | " loaded, if that is desired. I often wondered why I got unexpected mark 22 | " highlightings in a new Vim session until I realized that I had used marks in 23 | " a previous session and forgot to clear them. 24 | " 25 | " 21-Apr-2011, Ingo Karkat 26 | " - Expose toggling of mark display (keeping the mark patterns) via new 27 | " MarkToggle mapping. Offer :MarkClear command as a replacement for the 28 | " old argumentless :Mark command, which now just disables, but not clears all 29 | " marks. 30 | " - Implement lazy-loading of disabled persistent marks via g:mwDoDeferredLoad 31 | " flag passing to autoload/mark.vim. 32 | " 33 | " 19-Apr-2011, Ingo Karkat 34 | " - ENH: Add explicit mark persistence via :MarkLoad and :MarkSave commands and 35 | " automatic persistence via the g:mwAutoLoadMarks and g:mwAutoSaveMarks 36 | " configuration flags. 37 | " 38 | " 15-Apr-2011, Ingo Karkat 39 | " - Avoid losing the mark highlightings on :syn on or :colorscheme commands. 40 | " Thanks to Zhou YiChao for alerting me to this issue and suggesting a fix. 41 | " 42 | " 17-Nov-2009, Ingo Karkat 43 | " - Replaced the (overly) generic mark#GetVisualSelectionEscaped() with 44 | " mark#GetVisualSelectionAsRegexp() and 45 | " mark#GetVisualSelectionAsLiteralPattern(). 46 | " 47 | " 04-Jul-2009, Ingo Karkat 48 | " - A [count] before any mapping either caused "No range allowed" error or just 49 | " repeated the :call [count] times, resulting in the current search pattern 50 | " echoed [count] times and a hit-enter prompt. Now suppressing [count] via 51 | " and handling it inside the implementation. 52 | " - Now passing isBackward (0/1) instead of optional 'b' flag into functions. 53 | " Also passing empty regexp to mark#MarkRegex() to avoid any optional 54 | " arguments. 55 | " 56 | " 02-Jul-2009, Ingo Karkat 57 | " - Split off functions into autoload script. 58 | " - Removed g:force_reload_mark. 59 | " - Initialization of global variables and autocommands is now done lazily on 60 | " the first use, not during loading of the plugin. This reduces Vim startup 61 | " time and footprint as long as the functionality isn't yet used. 62 | " 63 | " 6-Jun-2009, Ingo Karkat 64 | " 1. Somehow s:WrapMessage() needs a redraw before the :echo to avoid that a 65 | " later Vim redraw clears the wrap message. This happened when there's no 66 | " statusline and thus :echo'ing into the ruler. 67 | " 2. Removed line-continuations and ':set cpo=...'. Upper-cased and . 68 | " 3. Added default highlighting for the special search type. 69 | " 70 | " 2-Jun-2009, Ingo Karkat 71 | " 1. Replaced highlighting via :syntax with matchadd() / matchdelete(). This 72 | " requires Vim 7.2 / 7.1 with patches. This method is faster, there are no 73 | " more clashes with syntax highlighting (:match always has preference), and 74 | " the background highlighting does not disappear under 'cursorline'. 75 | " 2. Factored :windo application out into s:MarkScope(). 76 | " 3. Using winrestcmd() to fix effects of :windo: By entering a window, its 77 | " height is potentially increased from 0 to 1. 78 | " 4. Handling multiple tabs by calling s:UpdateScope() on the TabEnter event. 79 | " 80 | " 1-Jun-2009, Ingo Karkat 81 | " 1. Now using Vim List for g:mwWord and thus requiring Vim 7. g:mwCycle is now 82 | " zero-based, but the syntax groups "MarkWordx" are still one-based. 83 | " 2. Added missing setter for re-inclusion guard. 84 | " 3. Factored :syntax operations out of s:DoMark() and s:UpdateMark() so that 85 | " they can all be done in a single :windo. 86 | " 4. Normal mode MarkSet now has the same semantics as its visual mode 87 | " cousin: If the cursor is on an existing mark, the mark is removed. 88 | " Beforehand, one could only remove a visually selected mark via again 89 | " selecting it. Now, one simply can invoke the mapping when on such a mark. 90 | " 5. Highlighting can now actually be overridden in the vimrc (anywhere 91 | " _before_ sourcing this script) by using ':hi def'. 92 | " 93 | " 31-May-2009, Ingo Karkat 94 | " 1. Refactored s:Search() to optionally take advantage of SearchSpecial.vim 95 | " autoload functionality for echoing of search pattern, wrap and error 96 | " messages. 97 | " 2. Now prepending search type ("any-mark", "same-mark", "new-mark") for 98 | " better identification. 99 | " 3. Retired the algorithm in s:PrevWord in favor of simply using , 100 | " which makes mark.vim work like the * command. At the end of a line, 101 | " non-keyword characters may now be marked; the previous algorithm prefered 102 | " any preceding word. 103 | " 4. BF: If 'iskeyword' contains characters that have a special meaning in a 104 | " regex (e.g. [.*]), these are now escaped properly. 105 | " 106 | " 01-Sep-2008, Ingo Karkat: bugfixes and enhancements 107 | " 1. Added MarkAllClear (without a default mapping), which clears all 108 | " marks, even when the cursor is on a mark. 109 | " 2. Added ... mappings for hard-coded \*, \#, \/, \?, * and #, to allow 110 | " re-mapping and disabling. Beforehand, there were some ... mappings 111 | " and hard-coded ones; now, everything can be customized. 112 | " 3. Bugfix: Using :autocmd without to avoid removing _all_ autocmds for 113 | " the BufWinEnter event. (Using a custom :augroup would be even better.) 114 | " 4. Bugfix: Explicitly defining s:current_mark_position; some execution paths 115 | " left it undefined, causing errors. 116 | " 5. Refactoring: Instead of calling s:InitMarkVariables() at the beginning of 117 | " several functions, just calling it once when sourcing the script. 118 | " 6. Refactoring: Moved multiple 'let lastwinnr = winnr()' to a single one at the 119 | " top of DoMark(). 120 | " 7. ENH: Make the match according to the 'ignorecase' setting, like the star 121 | " command. 122 | " 8. The jumps to the next/prev occurrence now print 'search hit BOTTOM, 123 | " continuing at TOP" and "Pattern not found:..." messages, like the * and 124 | " n/N Vim search commands. 125 | " 9. Jumps now open folds if the occurrence is inside a closed fold, just like n/N 126 | " do. 127 | " 128 | " 10th Mar 2006, Yuheng Xie: jump to ANY mark 129 | " (*) added \* \# \/ \? for the ability of jumping to ANY mark, even when the 130 | " cursor is not currently over any mark 131 | " 132 | " 20th Sep 2005, Yuheng Xie: minor modifications 133 | " (*) merged MarkRegexVisual into MarkRegex 134 | " (*) added GetVisualSelectionEscaped for multi-lines visual selection and 135 | " visual selection contains ^, $, etc. 136 | " (*) changed the name ThisMark to CurrentMark 137 | " (*) added SearchCurrentMark and re-used raw map (instead of Vim function) to 138 | " implement * and # 139 | " 140 | " 14th Sep 2005, Luc Hermitte: modifications done on v1.1.4 141 | " (*) anti-reinclusion guards. They do not guard colors definitions in case 142 | " this script must be reloaded after .gvimrc 143 | " (*) Protection against disabled |line-continuation|s. 144 | " (*) Script-local functions 145 | " (*) Default keybindings 146 | " (*) \r for visual mode 147 | " (*) uses instead of "\" 148 | " (*) do not mess with global variable g:w 149 | " (*) regex simplified -> double quotes changed into simple quotes. 150 | " (*) strpart(str, idx, 1) -> str[idx] 151 | " (*) command :Mark 152 | " -> e.g. :Mark Mark.\{-}\ze( 153 | 154 | " Avoid installing twice or when in unsupported Vim version. 155 | if exists('g:loaded_mark') || (v:version == 701 && ! exists('*matchadd')) || (v:version < 702) 156 | finish 157 | endif 158 | let g:loaded_mark = 1 159 | 160 | "- configuration -------------------------------------------------------------- 161 | if ! exists('g:mwHistAdd') 162 | let g:mwHistAdd = '/@' 163 | endif 164 | 165 | if ! exists('g:mwAutoLoadMarks') 166 | let g:mwAutoLoadMarks = 0 167 | endif 168 | 169 | if ! exists('g:mwAutoSaveMarks') 170 | let g:mwAutoSaveMarks = 1 171 | endif 172 | 173 | 174 | "- default highlightings ------------------------------------------------------ 175 | function! s:DefaultHighlightings() 176 | " You may define your own colors in your vimrc file, in the form as below: 177 | highlight def MarkWord1 ctermbg=Cyan ctermfg=Black guibg=#8CCBEA guifg=Black 178 | highlight def MarkWord2 ctermbg=Green ctermfg=Black guibg=#A4E57E guifg=Black 179 | highlight def MarkWord3 ctermbg=Yellow ctermfg=Black guibg=#FFDB72 guifg=Black 180 | highlight def MarkWord4 ctermbg=Red ctermfg=Black guibg=#FF7272 guifg=Black 181 | highlight def MarkWord5 ctermbg=Magenta ctermfg=Black guibg=#FFB3FF guifg=Black 182 | highlight def MarkWord6 ctermbg=Blue ctermfg=Black guibg=#9999FF guifg=Black 183 | endfunction 184 | call s:DefaultHighlightings() 185 | autocmd ColorScheme * call DefaultHighlightings() 186 | 187 | " Default highlighting for the special search type. 188 | " You can override this by defining / linking the 'SearchSpecialSearchType' 189 | " highlight group before this script is sourced. 190 | highlight def link SearchSpecialSearchType MoreMsg 191 | 192 | 193 | "- mappings ------------------------------------------------------------------- 194 | nnoremap MarkSet :call mark#MarkCurrentWord() 195 | vnoremap MarkSet :call mark#DoMark(mark#GetVisualSelectionAsLiteralPattern()) 196 | nnoremap MarkRegex :call mark#MarkRegex('') 197 | vnoremap MarkRegex :call mark#MarkRegex(mark#GetVisualSelectionAsRegexp()) 198 | nnoremap MarkClear :call mark#DoMark(mark#CurrentMark()[0]) 199 | nnoremap MarkAllClear :call mark#ClearAll() 200 | nnoremap MarkToggle :call mark#Toggle() 201 | 202 | nnoremap MarkSearchCurrentNext :call mark#SearchCurrentMark(0) 203 | nnoremap MarkSearchCurrentPrev :call mark#SearchCurrentMark(1) 204 | nnoremap MarkSearchAnyNext :call mark#SearchAnyMark(0) 205 | nnoremap MarkSearchAnyPrev :call mark#SearchAnyMark(1) 206 | nnoremap MarkSearchNext :if !mark#SearchNext(0)execute 'normal! *zv'endif 207 | nnoremap MarkSearchPrev :if !mark#SearchNext(1)execute 'normal! #zv'endif 208 | " When typed, [*#nN] open the fold at the search result, but inside a mapping or 209 | " :normal this must be done explicitly via 'zv'. 210 | 211 | 212 | if !hasmapto('MarkSet', 'n') 213 | nmap m MarkSet 214 | endif 215 | if !hasmapto('MarkSet', 'v') 216 | vmap m MarkSet 217 | endif 218 | if !hasmapto('MarkRegex', 'n') 219 | nmap r MarkRegex 220 | endif 221 | if !hasmapto('MarkRegex', 'v') 222 | vmap r MarkRegex 223 | endif 224 | if !hasmapto('MarkClear', 'n') 225 | nmap n MarkClear 226 | endif 227 | " No default mapping for MarkAllClear. 228 | " No default mapping for MarkToggle. 229 | 230 | if !hasmapto('MarkSearchCurrentNext', 'n') 231 | nmap * MarkSearchCurrentNext 232 | endif 233 | if !hasmapto('MarkSearchCurrentPrev', 'n') 234 | nmap # MarkSearchCurrentPrev 235 | endif 236 | if !hasmapto('MarkSearchAnyNext', 'n') 237 | nmap / MarkSearchAnyNext 238 | endif 239 | if !hasmapto('MarkSearchAnyPrev', 'n') 240 | nmap ? MarkSearchAnyPrev 241 | endif 242 | if !hasmapto('MarkSearchNext', 'n') 243 | nmap * MarkSearchNext 244 | endif 245 | if !hasmapto('MarkSearchPrev', 'n') 246 | nmap # MarkSearchPrev 247 | endif 248 | 249 | 250 | "- commands ------------------------------------------------------------------- 251 | command! -nargs=? Mark call mark#DoMark() 252 | command! -bar MarkClear call mark#ClearAll() 253 | 254 | command! -bar MarkLoad call mark#LoadCommand(1) 255 | command! -bar MarkSave call mark#SaveCommand() 256 | 257 | 258 | "- marks persistence ---------------------------------------------------------- 259 | if g:mwAutoLoadMarks 260 | " As the viminfo is only processed after sourcing of the runtime files, the 261 | " persistent global variables are not yet available here. Defer this until Vim 262 | " startup has completed. 263 | function! s:AutoLoadMarks() 264 | if g:mwAutoLoadMarks && exists('g:MARK_MARKS') && g:MARK_MARKS !=# '[]' 265 | if ! exists('g:MARK_ENABLED') || g:MARK_ENABLED 266 | " There are persistent marks and they haven't been disabled; we need to 267 | " show them right now. 268 | call mark#LoadCommand(0) 269 | else 270 | " Though there are persistent marks, they have been disabled. We avoid 271 | " sourcing the autoload script and its invasive autocmds right now; 272 | " maybe the marks are never turned on. We just inform the autoload 273 | " script that it should do this once it is sourced on-demand by a 274 | " mark mapping or command. 275 | let g:mwDoDeferredLoad = 1 276 | endif 277 | endif 278 | endfunction 279 | 280 | augroup MarkInitialization 281 | autocmd! 282 | " Note: Avoid triggering the autoload unless there actually are persistent 283 | " marks. For that, we need to check that g:MARK_MARKS doesn't contain the 284 | " empty list representation, and also :execute the :call. 285 | autocmd VimEnter * call AutoLoadMarks() 286 | augroup END 287 | endif 288 | 289 | " vim: ts=2 sw=2 290 | --------------------------------------------------------------------------------