├── README.md ├── autoload ├── searchx.vim └── searchx │ ├── async.vim │ ├── cursor.vim │ ├── highlight.vim │ └── searchundo.vim ├── doc └── searchx.txt └── plugin └── searchx.vim /README.md: -------------------------------------------------------------------------------- 1 | # vim-searchx 2 | 3 | The extended search motion. 4 | 5 | - Jump to the specific match via marker (like easymotion) 6 | - Move cursor during searching 7 | - Input converter 8 | - Improved jumplist management 9 | 10 | 11 | ### Settings 12 | 13 | ```vim 14 | " Overwrite / and ?. 15 | nnoremap ? call searchx#start({ 'dir': 0 }) 16 | nnoremap / call searchx#start({ 'dir': 1 }) 17 | xnoremap ? call searchx#start({ 'dir': 0 }) 18 | xnoremap / call searchx#start({ 'dir': 1 }) 19 | cnoremap ; call searchx#select() 20 | 21 | " Move to next/prev match. 22 | nnoremap N call searchx#prev_dir() 23 | nnoremap n call searchx#next_dir() 24 | xnoremap N call searchx#prev_dir() 25 | xnoremap n call searchx#next_dir() 26 | nnoremap call searchx#prev() 27 | nnoremap call searchx#next() 28 | xnoremap call searchx#prev() 29 | xnoremap call searchx#next() 30 | cnoremap call searchx#prev() 31 | cnoremap call searchx#next() 32 | 33 | " Clear highlights 34 | nnoremap call searchx#clear() 35 | 36 | let g:searchx = {} 37 | 38 | " Auto jump if the recent input matches to any marker. 39 | let g:searchx.auto_accept = v:true 40 | 41 | " The scrolloff value for moving to next/prev. 42 | let g:searchx.scrolloff = &scrolloff 43 | 44 | " To enable scrolling animation. 45 | let g:searchx.scrolltime = 500 46 | 47 | " To enable auto nohlsearch after cursor is moved 48 | let g:searchx.nohlsearch = {} 49 | let g:searchx.nohlsearch.jump = v:true 50 | 51 | " Marker characters. 52 | let g:searchx.markers = split('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '.\zs') 53 | 54 | " Convert search pattern. 55 | function g:searchx.convert(input) abort 56 | if a:input !~# '\k' 57 | return '\V' .. a:input 58 | endif 59 | return a:input[0] .. substitute(a:input[1:], '\\\@ User SearchxEnter 40 | 41 | " Update statusline if enter cmdline mode via `input(...)`. 42 | call feedkeys("\redrawstatus\", 'ni') 43 | 44 | let s:state.prompt = v:true 45 | let l:return = input(s:state.direction == 1 ? '/' : '?') 46 | let s:state.prompt = v:false 47 | let s:state.prompt_emptily = v:true 48 | doautocmd User SearchxLeave 49 | augroup searchx-run 50 | autocmd! 51 | augroup END 52 | 53 | " finalize. 54 | call s:clear() 55 | if l:return ==# '' 56 | call winrestview(s:state.firstview) 57 | doautocmd User SearchxCancel 58 | else 59 | call histadd('/', @/) 60 | call searchx#cursor#mark() 61 | doautocmd User SearchxAccept 62 | if index([s:AcceptReason.Marker], s:state.accept_reason) >= 0 63 | doautocmd User SearchxAcceptMarker 64 | call searchx#searchundo#hlsearch(v:false) 65 | else 66 | doautocmd User SearchxAcceptReturn 67 | call searchx#searchundo#hlsearch(v:true) 68 | endif 69 | endif 70 | endfunction 71 | 72 | " 73 | " searchx#select 74 | " 75 | function! searchx#select() abort 76 | let s:state.matches = s:find_matches(@/, getcurpos()[1:2]) 77 | call s:refresh({ 'marker': v:true, 'incsearch': v:false }) 78 | 79 | function! s:callback(timer) abort closure 80 | if getchar(1) != 0 81 | return 82 | endif 83 | call timer_stop(a:timer) 84 | 85 | let l:char = nr2char(getchar()) 86 | call s:clear() 87 | for l:match in s:state.matches.matches 88 | if l:match.marker ==# l:char 89 | return s:accept_marker(l:match) 90 | endif 91 | endfor 92 | endfunction 93 | call timer_start(0, function('s:callback'), { 'repeat': -1 }) 94 | endfunction 95 | 96 | " 97 | " searchx#clear 98 | " 99 | function! searchx#clear() abort 100 | call s:clear() 101 | endfunction 102 | 103 | " 104 | " searchx#next_dir 105 | " 106 | function searchx#next_dir() abort 107 | if v:searchforward == 0 108 | return searchx#prev() 109 | endif 110 | call searchx#next() 111 | endfunction 112 | 113 | " 114 | " searchx#prev_dir 115 | " 116 | function searchx#prev_dir() abort 117 | if v:searchforward == 0 118 | return searchx#next() 119 | endif 120 | call searchx#prev() 121 | endfunction 122 | 123 | " 124 | " searchx#prev 125 | " 126 | function! searchx#prev() abort 127 | if @/ !=# '' 128 | for l:i in range(1, s:state.prompt ? 1 : v:count1) 129 | let l:pos = searchpos(@/, 'wbn') 130 | if l:pos[0] != 0 131 | call s:goto(l:pos) 132 | endif 133 | endfor 134 | redraw 135 | endif 136 | endfunction 137 | 138 | " 139 | " searchx#next 140 | " 141 | function! searchx#next() abort 142 | if @/ !=# '' 143 | for l:i in range(1, s:state.prompt ? 1 : v:count1) 144 | let l:pos = searchpos(@/, 'wzn') 145 | if l:pos[0] != 0 146 | call s:goto(l:pos) 147 | endif 148 | endfor 149 | redraw 150 | endif 151 | endfunction 152 | 153 | " 154 | " searchx#redraw 155 | " 156 | function! searchx#redraw() abort 157 | call s:on_input({ 'is_userinput': v:false }) 158 | endfunction 159 | 160 | " 161 | " s:goto 162 | " 163 | function! s:goto(pos) abort 164 | call searchx#cursor#goto(a:pos) 165 | if s:state.prompt 166 | let s:state.matches = s:find_matches(@/, a:pos) 167 | call s:refresh({ 'marker': g:searchx.auto_accept, 'incsearch': v:true }) 168 | elseif reg_executing() ==# '' 169 | let s:state.matches = s:find_matches(@/, a:pos) 170 | call searchx#async#step([ 171 | \ { next -> [s:refresh({ 'marker': v:false, 'incsearch': v:true }), searchx#async#timeout('goto', 500, next)] }, 172 | \ { next -> [s:refresh({ 'marker': v:false, 'incsearch': v:false }), next()] }, 173 | \ { next -> [searchx#searchundo#hlsearch(v:true), next()] }, 174 | \ ] + (g:searchx.nohlsearch.jump ? [ 175 | \ { next -> [s:auto_nohlsearch(), next()] }, 176 | \ ] : [])) 177 | endif 178 | endfunction 179 | 180 | " 181 | " s:auto_nohlsearch 182 | " 183 | function! s:auto_nohlsearch() abort 184 | augroup searchx-auto-nohlsearch 185 | autocmd! 186 | autocmd CursorMoved * 187 | \ autocmd searchx-auto-nohlsearch CursorMoved * ++once 188 | \ call searchx#searchundo#hlsearch(v:false) 189 | \ | autocmd! searchx-auto-nohlsearch 190 | augroup END 191 | endfunction 192 | 193 | " 194 | " s:accept_marker 195 | " 196 | function! s:accept_marker(match) abort 197 | let s:state.accept_reason = s:AcceptReason.Marker 198 | call searchx#cursor#goto([a:match.lnum, a:match.col]) 199 | if s:state.prompt 200 | " Remove marker input. 201 | call feedkeys("\\", 'n') 202 | else 203 | call s:clear() 204 | endif 205 | endfunction 206 | 207 | " 208 | " s:detect_direction 209 | " 210 | function! s:detect_direction() abort 211 | let l:curpos = getcurpos()[1:2] 212 | let l:above = l:curpos[0] - line('w0') 213 | let l:below = line('w$') - l:curpos[0] 214 | return l:above > l:below ? s:Direction.Prev : s:Direction.Next 215 | endfunction 216 | 217 | " 218 | " on_input 219 | " 220 | function! s:on_input(...) abort 221 | let l:option = get(a:000, 0, {}) 222 | let l:option.is_userinput = get(l:option, 'is_userinput', v:true) 223 | 224 | try 225 | " Check marker. 226 | let l:input = getcmdline() 227 | if g:searchx.auto_accept && strlen(l:input) > 0 228 | let l:index = index(g:searchx.markers, l:input[strlen(l:input) - 1]) 229 | if l:index >= 0 230 | for l:match in s:state.matches.matches 231 | if l:match.marker ==# g:searchx.markers[l:index] 232 | return s:accept_marker(l:match) 233 | endif 234 | endfor 235 | endif 236 | endif 237 | 238 | " Check prompt emptiness 239 | if strlen(l:input) == 0 240 | if s:state.prompt_emptily && l:option.is_userinput 241 | call feedkeys("\", 'n') 242 | else 243 | let s:state.prompt_emptily = v:true 244 | endif 245 | else 246 | let s:state.prompt_emptily = v:false 247 | endif 248 | 249 | let l:input = s:state.convert(l:input) 250 | if @/ ==# l:input 251 | return 252 | endif 253 | 254 | " Check backspace. 255 | if stridx(l:input, @/) != 0 256 | silent noautocmd call winrestview(s:state.firstview) 257 | endif 258 | 259 | " Update search pattern. 260 | let @/ = l:input 261 | call searchx#searchundo#searchforward(s:state.direction) 262 | 263 | " Update view state. 264 | let s:state.matches = s:find_matches(@/, getcurpos()[1:2]) 265 | call s:refresh({ 'marker': g:searchx.auto_accept }) 266 | 267 | " Search off-screen match. 268 | if empty(s:state.matches.matches) 269 | silent noautocmd call winrestview(s:state.firstview) 270 | let l:next_pos = searchpos(@/, s:state.direction == s:Direction.Next ? 'zn' : 'bn') 271 | if l:next_pos[0] == 0 272 | let s:state.direction = s:state.direction == s:Direction.Next ? s:Direction.Prev : s:Direction.Next 273 | endif 274 | call searchx#next_dir() 275 | " Move to current match. 276 | else 277 | if s:state.matches.current isnot v:null 278 | call searchx#cursor#goto([s:state.matches.current.lnum, s:state.matches.current.col]) 279 | endif 280 | redraw 281 | endif 282 | 283 | doautocmd User SearchxInputChanged 284 | catch /^Vim\%((\a\+)\)\=:E54/ 285 | " ignore 286 | catch /.*/ 287 | echomsg string({ 'exception': v:exception, 'throwpoint': v:throwpoint }) 288 | endtry 289 | endfunction 290 | 291 | " 292 | " refresh 293 | " 294 | function s:refresh(...) abort 295 | call s:clear() 296 | 297 | let l:option = get(a:000, 0, {}) 298 | for l:match in s:state.matches.matches 299 | if get(l:option, 'incsearch', v:true) 300 | if l:match.current 301 | call searchx#highlight#set_incsearch(l:match) 302 | endif 303 | endif 304 | 305 | if get(l:option, 'marker', v:true) 306 | call searchx#highlight#add_marker(l:match) 307 | endif 308 | endfor 309 | call searchx#searchundo#hlsearch(len(s:state.matches.matches) > 0) 310 | endfunction 311 | 312 | " 313 | " clear 314 | " 315 | function s:clear() abort 316 | call searchx#async#clear() 317 | call searchx#highlight#clear() 318 | call searchx#searchundo#hlsearch(v:false) 319 | endfunction 320 | 321 | " 322 | " find_matches 323 | " 324 | function! s:find_matches(input, curpos) abort 325 | let l:lnum_s = line('w0') 326 | let l:lnum_e = line('w$') 327 | let l:texts = getbufline('%', l:lnum_s, l:lnum_e) 328 | let l:next = v:null 329 | let l:prev = v:null 330 | let l:matches = [] 331 | for l:i in range(0, len(l:texts) - 1) 332 | let l:text = l:texts[l:i] 333 | 334 | let l:off = 0 335 | while l:off < strlen(l:text) 336 | let l:m = matchstrpos(l:text, a:input, l:off, 1) 337 | if l:m[0] ==# '' 338 | break 339 | endif 340 | 341 | let l:match = { 342 | \ 'id': len(l:matches) + 1, 343 | \ 'lnum': l:lnum_s + l:i, 344 | \ 'col': l:m[1] + 1, 345 | \ 'end_col': l:m[2] + 1, 346 | \ 'marker': get(g:searchx.markers, len(l:matches), v:null), 347 | \ 'current': v:false, 348 | \ } 349 | 350 | " nearest next. 351 | if empty(l:next) && (a:curpos[0] < l:match.lnum || a:curpos[0] == l:match.lnum && a:curpos[1] <= l:match.col) 352 | let l:next = l:match 353 | endif 354 | 355 | " nearest prev. 356 | if a:curpos[0] > l:match.lnum || a:curpos[0] == l:match.lnum && a:curpos[1] >= l:match.col 357 | let l:prev = l:match 358 | endif 359 | 360 | " add match. 361 | call add(l:matches, l:match) 362 | 363 | let l:off = l:match.end_col 364 | endwhile 365 | endfor 366 | 367 | " Select current match. 368 | let l:next = empty(l:next) ? l:prev : l:next 369 | let l:prev = empty(l:prev) ? l:next : l:prev 370 | let l:current = s:state.direction == s:Direction.Next ? l:next : l:prev 371 | if !empty(l:current) 372 | let l:current.current = v:true 373 | endif 374 | 375 | return { 'matches': l:matches, 'current': l:current } 376 | endfunction 377 | 378 | -------------------------------------------------------------------------------- /autoload/searchx/async.vim: -------------------------------------------------------------------------------- 1 | let s:timers = {} 2 | 3 | " 4 | " searchx#async#timeout 5 | " 6 | function! searchx#async#timeout(name, timeout, func) abort 7 | let l:timer = get(s:timers, a:name, 0) 8 | call timer_stop(l:timer) 9 | let s:timers[a:name] = timer_start(a:timeout, a:func) 10 | endfunction 11 | 12 | " 13 | " searchx#async#step 14 | " 15 | function! searchx#async#step(steps) abort 16 | let l:steps = copy(a:steps) 17 | function! s:next() abort closure 18 | if len(l:steps) == 0 19 | return 20 | endif 21 | call remove(l:steps, 0)({ -> s:next() }) 22 | endfunction 23 | call s:next() 24 | endfunction 25 | 26 | " 27 | " searchx#async#clear 28 | " 29 | function! searchx#async#clear() abort 30 | for [l:k, l:v] in items(s:timers) 31 | call timer_stop(l:v) 32 | endfor 33 | endfunction 34 | 35 | -------------------------------------------------------------------------------- /autoload/searchx/cursor.vim: -------------------------------------------------------------------------------- 1 | let s:state = {} 2 | let s:state.firstview = v:null 3 | let s:state.finalview = v:null 4 | 5 | " 6 | " searchx#cursor#save 7 | " 8 | function! searchx#cursor#save(view) abort 9 | let s:state.firstview = a:view 10 | endfunction 11 | 12 | " 13 | " searchx#cursor#goto 14 | " 15 | function! searchx#cursor#goto(pos) abort 16 | if empty(s:state.firstview) 17 | let s:state.firstview = winsaveview() 18 | endif 19 | call s:cursor(a:pos[0], a:pos[1]) 20 | let s:state.finalview = winsaveview() 21 | 22 | augroup searchx-cursor-goto 23 | autocmd! 24 | autocmd CursorMoved * call s:on_cursor_moved() 25 | augroup END 26 | endfunction 27 | 28 | " 29 | " searchx#cursor#mark 30 | " 31 | function! searchx#cursor#mark() abort 32 | if empty(s:state.firstview) 33 | return 34 | endif 35 | 36 | augroup searchx-cursor-goto 37 | autocmd! 38 | augroup END 39 | 40 | let l:view = winsaveview() 41 | call winrestview(s:state.firstview) 42 | normal! m` 43 | call winrestview(l:view) 44 | 45 | let s:state = {} 46 | let s:state.firstview = v:null 47 | let s:state.finalview = v:null 48 | endfunction 49 | 50 | " 51 | " s:on_cursor_moved 52 | " 53 | function! s:on_cursor_moved() abort 54 | let l:view = winsaveview() 55 | let l:same = v:true 56 | let l:same = l:same && l:view.lnum == s:state.finalview.lnum 57 | let l:same = l:same && l:view.col == s:state.finalview.col 58 | if l:same 59 | return 60 | endif 61 | call searchx#cursor#mark() 62 | endfunction 63 | 64 | " 65 | " cursor 66 | " 67 | function! s:cursor(lnum, col) abort 68 | let l:scrolloff = g:searchx.scrolloff isnot v:null ? g:searchx.scrolloff : &scrolloff 69 | let l:above = line('w0') + l:scrolloff 70 | let l:below = line('w$') - l:scrolloff 71 | let l:dir = 0 72 | let l:steps = [] 73 | if a:lnum < l:above 74 | let l:dir = 0 75 | let l:steps = repeat(["\"], l:above - a:lnum) 76 | elseif l:below < a:lnum 77 | let l:dir = 1 78 | let l:steps = repeat(["\"], a:lnum - l:below) 79 | endif 80 | 81 | if reg_executing() ==# '' 82 | let l:total_value = len(l:steps) - 1 83 | if l:scrolloff < l:total_value 84 | let l:saved_cursorline = &cursorline 85 | let l:saved_scrolloff = &scrolloff 86 | let l:saved_virtualedit = &virtualedit 87 | 88 | let &cursorline = v:false 89 | let &scrolloff = 0 90 | let &virtualedit = 'all' 91 | let l:prev_value = 0 92 | let l:start_time = reltimefloat(reltime()) * 1000 93 | let l:curr_time = l:start_time 94 | let l:total_time = l:start_time + g:searchx.scrolltime 95 | while l:curr_time < l:total_time 96 | let l:curr_time = reltimefloat(reltime()) * 1000 97 | let l:next_value = s:easing(l:start_time, l:curr_time, l:total_time, l:total_value) 98 | for l:i in range(l:prev_value, l:next_value) 99 | execute printf('normal! %s', l:steps[l:i]) 100 | endfor 101 | let l:prev_value = l:next_value + 1 102 | 103 | if l:dir == 0 104 | call cursor(line('w0') + l:scrolloff - 1, a:col) 105 | else 106 | call cursor(line('w$') - l:scrolloff + 1, a:col) 107 | endif 108 | redraw 109 | sleep 16ms 110 | endwhile 111 | let &cursorline = l:saved_cursorline 112 | let &scrolloff = l:saved_scrolloff 113 | let &virtualedit = l:saved_virtualedit 114 | endif 115 | endif 116 | 117 | call cursor(a:lnum, a:col) 118 | endfunction 119 | 120 | function! s:easing(start_time, curr_time, total_time, total_value) abort 121 | return min([a:total_value, float2nr(a:total_value * (-pow(2, -10 * ((a:curr_time - a:start_time) / (a:total_time - a:start_time)) ) + 1))]) 122 | endfunction 123 | 124 | -------------------------------------------------------------------------------- /autoload/searchx/highlight.vim: -------------------------------------------------------------------------------- 1 | let s:incsearch_id = 0 2 | 3 | " 4 | " set_incsearch 5 | " 6 | function! searchx#highlight#set_incsearch(match) abort 7 | let s:incsearch_id = matchaddpos('SearchxIncSearch', [[a:match.lnum, a:match.col, a:match.end_col - a:match.col]]) 8 | endfunction 9 | 10 | " 11 | " add_marker 12 | " 13 | function! searchx#highlight#add_marker(match) abort 14 | call s:add_marker(a:match) 15 | endfunction 16 | 17 | " 18 | " clear 19 | " 20 | function! searchx#highlight#clear() abort 21 | try 22 | silent! call matchdelete(s:incsearch_id) 23 | catch /.*/ 24 | endtry 25 | call s:clear_marker() 26 | endfunction 27 | 28 | " 29 | " add_marker/clear_marker 30 | " 31 | if has('nvim') 32 | let s:marker_ns = nvim_create_namespace('searchx:marker') 33 | 34 | " 35 | " add_marker 36 | " 37 | function! s:add_marker(match) abort 38 | if !empty(a:match.marker) 39 | call nvim_buf_set_extmark(0, s:marker_ns, a:match.lnum - 1, max([0, a:match.col - 2]), { 40 | \ 'id': a:match.id, 41 | \ 'end_line': a:match.lnum - 1, 42 | \ 'end_col': max([0, a:match.col - 2]), 43 | \ 'virt_text': [[a:match.marker, (a:match.current ? 'SearchxMarkerCurrent' : 'SearchxMarker')]], 44 | \ 'virt_text_pos': 'overlay', 45 | \ 'priority': 1000, 46 | \ }) 47 | endif 48 | endfunction 49 | 50 | " 51 | " clear_marker 52 | " 53 | function! s:clear_marker() abort 54 | call nvim_buf_clear_namespace(0, s:marker_ns, 0, -1) 55 | endfunction 56 | else 57 | call prop_type_delete('searchx_marker', {}) 58 | call prop_type_add('searchx_marker', {}) 59 | 60 | " 61 | " add_marker 62 | " 63 | function! s:add_marker(match) abort 64 | if !empty(a:match.marker) 65 | call prop_add(a:match.lnum, a:match.col, { 66 | \ 'type': 'searchx_marker', 67 | \ 'id': a:match.id, 68 | \ }) 69 | call popup_create(a:match.marker, { 70 | \ 'line': -1, 71 | \ 'col': -1, 72 | \ 'textprop': 'searchx_marker', 73 | \ 'textpropid': a:match.id, 74 | \ 'width': 1, 75 | \ 'height': 1, 76 | \ 'highlight': 'SearchxMarker' 77 | \ }) 78 | endif 79 | endfunction 80 | 81 | " 82 | " clear_marker 83 | " 84 | function! s:clear_marker() abort 85 | call prop_clear(1, line('$'), { 'type': 'searchx_marker' }) 86 | endfunction 87 | endif 88 | 89 | -------------------------------------------------------------------------------- /autoload/searchx/searchundo.vim: -------------------------------------------------------------------------------- 1 | let s:state = {} 2 | let s:state.hlsearch = v:false 3 | let s:state.searchforward = 1 4 | let s:state.running = v:false 5 | 6 | " 7 | " searchx#searchundo#hlsearch 8 | " 9 | function! searchx#searchundo#hlsearch(v) abort 10 | let v:hlsearch = a:v 11 | let s:state.hlsearch = a:v 12 | call s:update() 13 | endfunction 14 | 15 | " 16 | " searchx#searchundo#searchforward 17 | " 18 | function! searchx#searchundo#searchforward(v) abort 19 | let v:searchforward = a:v 20 | let s:state.searchforward = a:v 21 | call s:update() 22 | endfunction 23 | 24 | " 25 | " _state 26 | " 27 | function! searchx#searchundo#_state(...) abort 28 | let l:merge = get(a:000, 0, {}) 29 | let s:state = extend(l:merge, s:state, 'keep') 30 | return s:state 31 | endfunction 32 | 33 | " 34 | " update 35 | " 36 | function! s:update() abort 37 | if !s:state.running 38 | let s:state.running = v:true 39 | call feedkeys("\call searchx#searchundo#_state({ 'running': v:false })\", 'ni') 40 | call feedkeys("\let v:searchforward = searchx#searchundo#_state().searchforward\", 'ni') 41 | call feedkeys("\let v:hlsearch = searchx#searchundo#_state().hlsearch\", 'ni') 42 | endif 43 | endfunction 44 | 45 | -------------------------------------------------------------------------------- /doc/searchx.txt: -------------------------------------------------------------------------------- 1 | *vim-searchx* *searchx* 2 | 3 | A plugin that provides extended search motion. 4 | 5 | ============================================================================== 6 | CONTENTS *searchx-contents* 7 | 8 | Usage |searchx-usage| 9 | Function |searchx-function| 10 | Variable |searchx-variable| 11 | Highlight |searchx-highlight| 12 | Autocmd |searchx-autocmd| 13 | 14 | 15 | 16 | ============================================================================== 17 | Usage *searchx-usage* 18 | 19 | > 20 | " Overwrite / and ?. 21 | nnoremap ? call searchx#start({ 'dir': 0 }) 22 | nnoremap / call searchx#start({ 'dir': 1 }) 23 | xnoremap ? call searchx#start({ 'dir': 0 }) 24 | xnoremap / call searchx#start({ 'dir': 1 }) 25 | cnoremap ; call searchx#select() 26 | 27 | " Move to next/prev match. 28 | nnoremap N call searchx#prev_dir() 29 | nnoremap n call searchx#next_dir() 30 | xnoremap N call searchx#prev_dir() 31 | xnoremap n call searchx#next_dir() 32 | nnoremap call searchx#prev() 33 | nnoremap call searchx#next() 34 | xnoremap call searchx#prev() 35 | xnoremap call searchx#next() 36 | cnoremap call searchx#prev() 37 | cnoremap call searchx#next() 38 | 39 | " Clear highlights 40 | nnoremap call searchx#clear() 41 | < 42 | 43 | 44 | ============================================================================== 45 | Function *searchx-function* 46 | 47 | *searchx#start()* 48 | searchx#start([opts])~ 49 | 50 | Start / or ? like command. 51 | 52 | The `opts` argument can have the following properties. 53 | - `dir` 54 | - The search direction. 0 is prev. 1 is next. 55 | - `convert` 56 | - A function which converts input to search pattern. 57 | Default value is |g:searchx.convert|. 58 | 59 | *searchx#next_dir()* 60 | searchx#next_dir()~ 61 | 62 | Move to next match considering |v:searchforward|, see |n|. 63 | 64 | *searchx#prev_dir()* 65 | searchx#prev_dir()~ 66 | 67 | Move to prev match considering |v:searchforward|, see |N|. 68 | 69 | *searchx#next()* 70 | searchx#next()~ 71 | 72 | Move to next match. 73 | 74 | *searchx#prev()* 75 | searchx#prev()~ 76 | 77 | Move to prev match. 78 | 79 | *searchx#redraw()* 80 | searchx#redraw()~ 81 | 82 | Redraw immediately. 83 | 84 | *searchx#clear()* 85 | searchx#clear()~ 86 | 87 | Clear all highlights. 88 | 89 | 90 | 91 | ============================================================================== 92 | Variable *searchx-variable* 93 | 94 | *g:searchx.auto_accept* 95 | g:searchx.auto_accept~ 96 | 97 | Specify check the marker jump on every input or not. 98 | 99 | *g:searchx.markers* 100 | g:searchx.markers~ 101 | 102 | The array of characters for markers. 103 | 104 | *g:searchx.convert* 105 | g:searchx.convert~ 106 | 107 | The function to convert input to search pattern. 108 | The recommended settings are the below. 109 | 110 | > 111 | let g:searchx = {} 112 | function g:searchx.convert(input) abort 113 | if a:input !~# '\k' 114 | return '\V' .. a:input 115 | endif 116 | return a:input[0] .. substitute(a:input[1:], '\\\@. 184 | 185 | *SearchxAcceptMarker* 186 | SearchxAcceptMarker~ 187 | 188 | Accept some jump point via marker. 189 | 190 | *SearchxCancel* 191 | SearchxCancel~ 192 | 193 | Cancel searchx prompt. 194 | 195 | 196 | ============================================================================== 197 | vim:tw=78:ts=4:et:ft=help:norl: 198 | 199 | -------------------------------------------------------------------------------- /plugin/searchx.vim: -------------------------------------------------------------------------------- 1 | if exists('g:loaded_searchx') 2 | finish 3 | endif 4 | let g:loaded_searchx = v:true 5 | 6 | let g:searchx = get(g:, 'searchx', {}) 7 | let g:searchx.auto_accept = get(g:searchx, 'auto_accept', v:false) 8 | let g:searchx.markers = get(g:searchx, 'markers', split('ABCDEFGHIJKLMNOPQRSTUVWXYZ', '.\zs')) 9 | let g:searchx.convert = get(g:searchx, 'convert', { input -> input }) 10 | let g:searchx.scrolloff = get(g:searchx, 'scrolloff', v:null) 11 | let g:searchx.scrolltime = get(g:searchx, 'scrolltime', 0) 12 | let g:searchx.nohlsearch = get(g:searchx, 'nohlsearch', {'jump': v:false}) 13 | 14 | augroup searchx-silent 15 | autocmd User SearchxEnter silent 16 | autocmd User SearchxLeave silent 17 | autocmd User SearchxInputChanged silent 18 | autocmd User SearchxCancel silent 19 | autocmd User SearchxAccept silent 20 | autocmd User SearchxAcceptReturn silent 21 | autocmd User SearchxAcceptMarker silent 22 | autocmd User SearchxCancel silent 23 | augroup END 24 | 25 | if !hlexists('SearchxIncSearch') 26 | highlight default link SearchxIncSearch IncSearch 27 | endif 28 | 29 | if !hlexists('SearchxMarker') 30 | highlight default link SearchxMarker DiffAdd 31 | endif 32 | 33 | if !hlexists('SearchxMarkerCurrent') 34 | highlight default link SearchxMarkerCurrent DiffText 35 | endif 36 | 37 | --------------------------------------------------------------------------------