├── README.md ├── CODE_OF_CONDUCT.md ├── plugin └── interestingwords.vim └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # vim-interestingwords 2 | 3 | vim-interestingwords highlights the occurrences of the word under the cursor throughout the buffer. Different words can be highlighted at the same time. The plugin also enables one to navigate through the highlighted words in the buffer just like one would through the results of a search. 4 | 5 | ![Screenshot](https://i.imgbox.com/5k3OJWIk.png) 6 | 7 | ## Installation 8 | 9 | The recommended installation is through `vim-plug`: 10 | 11 | ```vimscript 12 | Plug 'lfv89/vim-interestingwords' 13 | ``` 14 | 15 | ## Usage 16 | 17 | - Highlight with ``k`` 18 | - Navigate highlighted words with ``n`` and ``N`` 19 | - Clear every word highlight with ``K`` throughout the buffer 20 | 21 | ### Highlighting Words 22 | 23 | ``k`` will act as a **toggle**, so you can use it to highlight and remove the highlight from a given word. Note that you can highlight different words at the same time. 24 | 25 | ### Navigating Highlights 26 | 27 | With a highlighted word **under your cursor**, you can **navigate** through the occurrences of this word with ``n`` and ``N``, just as you would if you were using a traditional search. 28 | 29 | ### Clearing (every) Highlight 30 | 31 | Finally, if you don't want to toggle every single highlighted word and want to clear all of them, just hit ``K`` 32 | 33 | ## Configuration 34 | 35 | The plugin comes with those default mapping, but you can change it as you like: 36 | 37 | `let g:interestingWordsDefaultMappings = 0` if to disable default mapping 38 | 39 | ```vimscript 40 | nnoremap k :call InterestingWords('n') 41 | vnoremap k :call InterestingWords('v') 42 | nnoremap K :call UncolorAllWords() 43 | 44 | nnoremap n :call WordNavigation(1) 45 | nnoremap N :call WordNavigation(0) 46 | ``` 47 | 48 | Thanks to **@gelguy** it is now possible to randomise and configure your own colors 49 | 50 | To configure the colors for a GUI, add this to your .vimrc: 51 | 52 | ```vimscript 53 | let g:interestingWordsGUIColors = ['#8CCBEA', '#A4E57E', '#FFDB72', '#FF7272', '#FFB3FF', '#9999FF'] 54 | ``` 55 | 56 | And for a terminal: 57 | 58 | ```vimscript 59 | let g:interestingWordsTermColors = ['154', '121', '211', '137', '214', '222'] 60 | ``` 61 | 62 | Also, if you want to randomise the colors (applied to each new buffer), add this to your .vimrc: 63 | 64 | ```vimscript 65 | let g:interestingWordsRandomiseColors = 1 66 | ``` 67 | 68 | ## Credits 69 | 70 | The idea to build this plugin came from the **@stevelosh** video's where he shows some pretty cool configurations from his .vimrc. He named this configuration interesting words, and I choose to keep the name for this plugin. The video is on youtube: https://www.youtube.com/watch?v=xZuy4gBghho 71 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at luis@luisvasconcellos.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /plugin/interestingwords.vim: -------------------------------------------------------------------------------- 1 | " -------------------------------------------------------------------- 2 | " This plugin was inspired and based on Steve Losh's interesting words 3 | " .vimrc config https://www.youtube.com/watch?v=xZuy4gBghho 4 | " -------------------------------------------------------------------- 5 | 6 | let s:interestingWordsGUIColors = ['#aeee00', '#ff0000', '#0000ff', '#b88823', '#ffa724', '#ff2c4b'] 7 | let s:interestingWordsTermColors = ['154', '121', '211', '137', '214', '222'] 8 | 9 | let g:interestingWordsGUIColors = exists('g:interestingWordsGUIColors') ? g:interestingWordsGUIColors : s:interestingWordsGUIColors 10 | let g:interestingWordsTermColors = exists('g:interestingWordsTermColors') ? g:interestingWordsTermColors : s:interestingWordsTermColors 11 | 12 | let s:hasBuiltColors = 0 13 | 14 | let s:interestingWords = [] 15 | let s:interestingModes = [] 16 | let s:mids = {} 17 | let s:recentlyUsed = [] 18 | 19 | function! ColorWord(word, mode) 20 | if !(s:hasBuiltColors) 21 | call s:buildColors() 22 | endif 23 | 24 | " gets the lowest unused index 25 | let n = index(s:interestingWords, 0) 26 | if (n == -1) 27 | if !(exists('g:interestingWordsCycleColors') && g:interestingWordsCycleColors) 28 | echom "InterestingWords: max number of highlight groups reached " . len(s:interestingWords) 29 | return 30 | else 31 | let n = s:recentlyUsed[0] 32 | call UncolorWord(s:interestingWords[n]) 33 | endif 34 | endif 35 | 36 | let mid = 595129 + n 37 | let s:interestingWords[n] = a:word 38 | let s:interestingModes[n] = a:mode 39 | let s:mids[a:word] = mid 40 | 41 | call s:apply_color_to_word(n, a:word, a:mode, mid) 42 | 43 | call s:markRecentlyUsed(n) 44 | 45 | endfunction 46 | 47 | function! s:apply_color_to_word(n, word, mode, mid) 48 | let case = s:checkIgnoreCase(a:word) ? '\c' : '\C' 49 | if a:mode == 'v' 50 | let pat = case . '\V\zs' . escape(a:word, '\') . '\ze' 51 | else 52 | let pat = case . '\V\<' . escape(a:word, '\') . '\>' 53 | endif 54 | 55 | try 56 | call matchadd("InterestingWord" . (a:n + 1), pat, 1, a:mid) 57 | catch /E801/ " match id already taken. 58 | endtry 59 | endfunction 60 | 61 | function! s:nearest_group_at_cursor() abort 62 | let l:matches = {} 63 | for l:match_item in getmatches() 64 | let l:mids = filter(items(s:mids), 'v:val[1] == l:match_item.id') 65 | if len(l:mids) == 0 66 | continue 67 | endif 68 | let l:word = l:mids[0][0] 69 | let l:position = match(getline('.'), l:match_item.pattern) 70 | if l:position > -1 71 | if col('.') > l:position && col('.') <= l:position + len(l:word) 72 | return l:word 73 | endif 74 | endif 75 | endfor 76 | return '' 77 | endfunction 78 | 79 | function! UncolorWord(word) 80 | let index = index(s:interestingWords, a:word) 81 | 82 | if (index > -1) 83 | let mid = s:mids[a:word] 84 | 85 | silent! call matchdelete(mid) 86 | let s:interestingWords[index] = 0 87 | unlet s:mids[a:word] 88 | endif 89 | endfunction 90 | 91 | function! s:getmatch(mid) abort 92 | return filter(getmatches(), 'v:val.id==a:mid')[0] 93 | endfunction 94 | 95 | function! WordNavigation(direction) 96 | let currentWord = s:nearest_group_at_cursor() 97 | 98 | if (s:checkIgnoreCase(currentWord)) 99 | let currentWord = tolower(currentWord) 100 | endif 101 | 102 | if (index(s:interestingWords, currentWord) > -1) 103 | let l:index = index(s:interestingWords, currentWord) 104 | let l:mode = s:interestingModes[index] 105 | let case = s:checkIgnoreCase(currentWord) ? '\c' : '\C' 106 | if l:mode == 'v' 107 | let pat = case . '\V\zs' . escape(currentWord, '\') . '\ze' 108 | else 109 | let pat = case . '\V\<' . escape(currentWord, '\') . '\>' 110 | endif 111 | let searchFlag = '' 112 | if !(a:direction) 113 | let searchFlag = 'b' 114 | endif 115 | call search(pat, searchFlag) 116 | else 117 | try 118 | if (a:direction) 119 | normal! n 120 | else 121 | normal! N 122 | endif 123 | catch /E486/ 124 | echohl WarningMsg | echomsg "E486: Pattern not found: " . @/ 125 | endtry 126 | endif 127 | endfunction 128 | 129 | function! InterestingWords(mode) range 130 | if a:mode == 'v' 131 | let currentWord = s:get_visual_selection() 132 | else 133 | let currentWord = expand('') . '' 134 | endif 135 | if !(len(currentWord)) 136 | return 137 | endif 138 | if (s:checkIgnoreCase(currentWord)) 139 | let currentWord = tolower(currentWord) 140 | endif 141 | if (index(s:interestingWords, currentWord) == -1) 142 | call ColorWord(currentWord, a:mode) 143 | else 144 | call UncolorWord(currentWord) 145 | endif 146 | endfunction 147 | 148 | function! s:get_visual_selection() 149 | " Why is this not a built-in Vim script function?! 150 | let [lnum1, col1] = getpos("'<")[1:2] 151 | let [lnum2, col2] = getpos("'>")[1:2] 152 | let lines = getline(lnum1, lnum2) 153 | let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)] 154 | let lines[0] = lines[0][col1 - 1:] 155 | return join(lines, "\n") 156 | endfunction 157 | 158 | function! UncolorAllWords() 159 | for word in s:interestingWords 160 | " check that word is actually a String since '0' is falsy 161 | if (type(word) == 1) 162 | call UncolorWord(word) 163 | endif 164 | endfor 165 | endfunction 166 | 167 | function! RecolorAllWords() 168 | let i = 0 169 | for word in s:interestingWords 170 | if (type(word) == 1) 171 | let mode = s:interestingModes[i] 172 | let mid = s:mids[word] 173 | call s:apply_color_to_word(i, word, mode, mid) 174 | endif 175 | let i += 1 176 | endfor 177 | endfunction 178 | 179 | " returns true if the ignorecase flag needs to be used 180 | function! s:checkIgnoreCase(word) 181 | " return false if case sensitive is used 182 | if (exists('g:interestingWordsCaseSensitive')) 183 | return !g:interestingWordsCaseSensitive 184 | endif 185 | " checks ignorecase 186 | " and then if smartcase is on, check if the word contains an uppercase char 187 | return &ignorecase && (!&smartcase || (match(a:word, '\u') == -1)) 188 | endfunction 189 | 190 | " moves the index to the back of the s:recentlyUsed list 191 | function! s:markRecentlyUsed(n) 192 | let index = index(s:recentlyUsed, a:n) 193 | call remove(s:recentlyUsed, index) 194 | call add(s:recentlyUsed, a:n) 195 | endfunction 196 | 197 | function! s:uiMode() 198 | " Stolen from airline's airline#init#gui_mode() 199 | return ((has('nvim') && exists('$NVIM_TUI_ENABLE_TRUE_COLOR') && !exists("+termguicolors")) 200 | \ || has('gui_running') || (has("termtruecolor") && &guicolors == 1) || (has("termguicolors") && &termguicolors == 1)) ? 201 | \ 'gui' : 'cterm' 202 | endfunction 203 | 204 | " initialise highlight colors from list of GUIColors 205 | " initialise length of s:interestingWord list 206 | " initialise s:recentlyUsed list 207 | function! s:buildColors() 208 | if (s:hasBuiltColors) 209 | return 210 | endif 211 | let ui = s:uiMode() 212 | let wordColors = (ui == 'gui') ? g:interestingWordsGUIColors : g:interestingWordsTermColors 213 | if (exists('g:interestingWordsRandomiseColors') && g:interestingWordsRandomiseColors) 214 | " fisher-yates shuffle 215 | let i = len(wordColors)-1 216 | while i > 0 217 | let j = s:Random(i) 218 | let temp = wordColors[i] 219 | let wordColors[i] = wordColors[j] 220 | let wordColors[j] = temp 221 | let i -= 1 222 | endwhile 223 | endif 224 | " select ui type 225 | " highlight group indexed from 1 226 | let currentIndex = 1 227 | for wordColor in wordColors 228 | execute 'hi! def InterestingWord' . currentIndex . ' ' . ui . 'bg=' . wordColor . ' ' . ui . 'fg=Black' 229 | call add(s:interestingWords, 0) 230 | call add(s:interestingModes, 'n') 231 | call add(s:recentlyUsed, currentIndex-1) 232 | let currentIndex += 1 233 | endfor 234 | let s:hasBuiltColors = 1 235 | endfunc 236 | 237 | " helper function to get random number between 0 and n-1 inclusive 238 | function! s:Random(n) 239 | let timestamp = reltimestr(reltime())[-2:] 240 | return float2nr(floor(a:n * timestamp/100)) 241 | endfunction 242 | 243 | if !exists('g:interestingWordsDefaultMappings') || g:interestingWordsDefaultMappings != 0 244 | let g:interestingWordsDefaultMappings = 1 245 | endif 246 | 247 | if g:interestingWordsDefaultMappings && !hasmapto('InterestingWords') 248 | nnoremap k :call InterestingWords('n') 249 | vnoremap k :call InterestingWords('v') 250 | nnoremap K :call UncolorAllWords() 251 | 252 | nnoremap n :call WordNavigation(1) 253 | nnoremap N :call WordNavigation(0) 254 | endif 255 | 256 | if g:interestingWordsDefaultMappings 257 | try 258 | nnoremap