1308 lines
38 KiB
VimL
1308 lines
38 KiB
VimL
let s:cpo_save = &cpo
|
||
set cpo&vim
|
||
|
||
let s:timeout = get(g:, 'traces_timeout', 1000)
|
||
let s:timeout = s:timeout > 200 ? s:timeout : 200
|
||
let s:search_timeout = get(g:, 'traces_search_timeout', 500)
|
||
let s:search_timeout = s:search_timeout > s:timeout - 100 ? s:timeout - 100 : s:search_timeout
|
||
|
||
let s:has_matchdelete_win = has('patch-8.1.1741')
|
||
let s:cmd_pattern = '\v\C^%('
|
||
\ . 'g%[lobal][[:alnum:]]@!\!=|'
|
||
\ . 's%[ubstitute][[:alnum:]]@!|'
|
||
\ . '(Subvert|S)[[:alnum:]]@!|'
|
||
\ . 'sm%[agic][[:alnum:]]@!|'
|
||
\ . 'sno%[magic][[:alnum:]]@!|'
|
||
\ . 'sor%[t][[:alnum:]]@!\!=|'
|
||
\ . 'norm%[al][[:alnum:]]@!\!=|'
|
||
\ . 'v%[global][[:alnum:]]@!'
|
||
\ . ')'
|
||
|
||
let s:buf = {}
|
||
|
||
function! s:trim(...) abort
|
||
if a:0 == 2
|
||
let a:1[0] = strpart(a:1[0], a:2)
|
||
else
|
||
let a:1[0] = substitute(a:1[0], '^\s\+', '', '')
|
||
endif
|
||
endfunction
|
||
|
||
function! s:parse_range(range, cmdl) abort
|
||
let specifier = {}
|
||
let specifier.addresses = []
|
||
|
||
let while_limit = 0
|
||
while 1
|
||
" address part
|
||
call s:trim(a:cmdl)
|
||
let entry = {}
|
||
" regexp for pattern specifier
|
||
let pattern = '/%(\\.|/@!&.)*/=|\?%(\\.|\?@!&.)*\?='
|
||
if !len(specifier.addresses)
|
||
let address = matchstrpos(a:cmdl[0], '\v^%(\d+|\.|\$|\%|\*|''.|'. pattern . '|\\\/|\\\?|\\\&)')
|
||
else
|
||
let address = matchstrpos(a:cmdl[0], '\v^%(' . pattern . ')' )
|
||
endif
|
||
if address[2] != -1
|
||
call s:trim(a:cmdl, address[2])
|
||
let entry.str = address[0]
|
||
endif
|
||
|
||
" offset
|
||
call s:trim(a:cmdl)
|
||
let offset = matchstrpos(a:cmdl[0], '\m^\%(\d\|\s\|+\|-\)\+')
|
||
if offset[2] != -1
|
||
call s:trim(a:cmdl, offset[2])
|
||
let entry.offset = offset[0]
|
||
endif
|
||
|
||
" add first address
|
||
if address[2] != -1 || offset[2] != -1
|
||
" if offset is present but specifier is missing add '.' specifier
|
||
if !has_key(entry, 'str')
|
||
let entry.str = '.'
|
||
endif
|
||
call add(specifier.addresses, entry)
|
||
else
|
||
" stop trying if previous attempt was unsuccessful
|
||
break
|
||
endif
|
||
let while_limit += 1 | if while_limit == 1000
|
||
\ | echoerr 'infinite loop' | break | endif
|
||
endwhile
|
||
|
||
" delimiter
|
||
call s:trim(a:cmdl)
|
||
let delimiter = matchstrpos(a:cmdl[0], '\m^\%(,\|;\)')
|
||
if delimiter[2] != -1
|
||
call s:trim(a:cmdl, delimiter[2])
|
||
let specifier.delimiter = delimiter[0]
|
||
endif
|
||
|
||
" add when addresses or delimiter are found or when one specifier is
|
||
" already known
|
||
if !empty(specifier.addresses) || delimiter[2] != -1 || !empty(a:range)
|
||
" specifiers are not given but delimiter is present
|
||
if empty(specifier.addresses)
|
||
call add(specifier.addresses, { 'str': '.' })
|
||
endif
|
||
call add(a:range, specifier)
|
||
endif
|
||
|
||
if delimiter[2] != -1
|
||
try
|
||
return s:parse_range(a:range, a:cmdl)
|
||
catch /^Vim\%((\a\+)\)\=:E132/
|
||
return []
|
||
endtry
|
||
else
|
||
return a:range
|
||
endif
|
||
endfunction
|
||
|
||
" five cases (+11 -11 11 -- ++)
|
||
function! s:offset_to_num(string) abort
|
||
let offset = 0
|
||
let copy = a:string
|
||
let input = [copy]
|
||
let pattern = '\m^\%(+\d\+\|-\d\+\|\d\+\|-\+\ze-\d\|+\+\ze+\d\|-\+\|+\+\)'
|
||
|
||
let while_limit = 0
|
||
while !empty(input[0])
|
||
call s:trim(input)
|
||
let part = matchstrpos(input[0], pattern)
|
||
call s:trim(input, part[2])
|
||
|
||
if part[0] =~# '+\d\+'
|
||
let offset += str2nr(matchstr(part[0], '\d\+'))
|
||
elseif part[0] =~# '-\d\+'
|
||
let offset -= str2nr(matchstr(part[0], '\d\+'))
|
||
elseif part[0] =~# '\d\+'
|
||
let offset += str2nr(part[0])
|
||
elseif part[0] =~# '+\+'
|
||
let offset += strchars(part[0])
|
||
elseif part[0] =~# '-\+'
|
||
let offset -= strchars(part[0])
|
||
endif
|
||
|
||
let while_limit += 1 | if while_limit == 1000
|
||
\ | echoerr 'infinite loop' | break | endif
|
||
endwhile
|
||
|
||
return offset
|
||
endfunction
|
||
|
||
function! s:search(...) abort
|
||
let cache = s:buf[s:nr].search_cache
|
||
let key = string(a:000[0:2]) . string(getcurpos())
|
||
if has_key(cache, key)
|
||
if a:0 is 1 || a:0 >= 2 && a:2 !~# 'n'
|
||
call setpos('.', cache[key].curpos)
|
||
endif
|
||
return cache[key].lnum
|
||
endif
|
||
if s:search_timeout_remaining <= 0
|
||
return 0
|
||
endif
|
||
let start = reltime()
|
||
silent! let lnum = call('search', a:000)
|
||
let time = reltimefloat(reltime(start)) * 1000
|
||
let s:search_timeout_remaining -= float2nr(ceil(time))
|
||
let cache[key] = {'lnum': lnum, 'curpos': getcurpos()}
|
||
return lnum
|
||
endfunction
|
||
|
||
function! s:address_to_num(address, last_pos) abort
|
||
let result = {}
|
||
let result.range = []
|
||
let result.valid = 1
|
||
let result.regex = ''
|
||
let s:entire_file = 0
|
||
|
||
if a:address.str =~# '^\d\+'
|
||
let lnum = str2nr(a:address.str)
|
||
call add(result.range, lnum)
|
||
|
||
elseif a:address.str ==# '.'
|
||
call add(result.range, a:last_pos)
|
||
|
||
elseif a:address.str ==# '$'
|
||
call add(result.range, getpos('$')[1])
|
||
|
||
elseif a:address.str ==# '%'
|
||
call add(result.range, 1)
|
||
call add(result.range, getpos('$')[1])
|
||
let s:entire_file = 1
|
||
|
||
elseif a:address.str ==# '*'
|
||
call add(result.range, getpos('''<')[1])
|
||
call add(result.range, getpos('''>')[1])
|
||
if match(&cpoptions, '\*') != -1
|
||
let result.valid = 0
|
||
endif
|
||
let s:buf[s:nr].show_range = 1
|
||
|
||
elseif a:address.str =~# '^''.'
|
||
call cursor(a:last_pos, 1)
|
||
let mark_position = getpos(a:address.str)
|
||
if mark_position[1]
|
||
call add(result.range, mark_position[1])
|
||
else
|
||
let result.valid = 0
|
||
endif
|
||
let s:buf[s:nr].show_range = 1
|
||
|
||
elseif a:address.str[0] is '/'
|
||
let closed = a:address.str =~# '\v%([^\\]\\)@<!\/$'
|
||
let pattern = closed ? a:address.str[1:-2] : a:address.str[1:]
|
||
if closed
|
||
if empty(pattern)
|
||
let pattern = s:last_pattern
|
||
else
|
||
let s:last_pattern = pattern
|
||
endif
|
||
endif
|
||
call cursor(a:last_pos + 1, 1)
|
||
if a:last_pos is line('$')
|
||
if &wrapscan
|
||
call cursor(1, 1)
|
||
else
|
||
let result.valid = 0
|
||
endif
|
||
endif
|
||
let s:buf[s:nr].show_range = 1
|
||
let query = s:search(pattern, 'nc', 0, s:search_timeout_remaining)
|
||
if !query | let result.valid = 0 | endif
|
||
if !closed | let result.regex = pattern | endif
|
||
call add(result.range, query)
|
||
|
||
elseif a:address.str[0] is '?'
|
||
let closed = a:address.str =~# '\v%([^\\]\\)@<!\?$'
|
||
let pattern = closed ? a:address.str[1:-2] : a:address.str[1:]
|
||
let pattern = substitute(pattern, '\\?', '?', '')
|
||
if closed
|
||
if empty(pattern)
|
||
let pattern = s:last_pattern
|
||
else
|
||
let s:last_pattern = pattern
|
||
endif
|
||
endif
|
||
call cursor(a:last_pos, 1)
|
||
let s:buf[s:nr].show_range = 1
|
||
let query = s:search(pattern, 'nb', 0, s:search_timeout_remaining)
|
||
if !query | let result.valid = 0 | endif
|
||
if !closed | let result.regex = pattern | endif
|
||
call add(result.range, query)
|
||
|
||
elseif a:address.str ==# '\/'
|
||
call cursor(a:last_pos + 1, 1)
|
||
if a:last_pos is line('$')
|
||
if &wrapscan
|
||
call cursor(1, 1)
|
||
else
|
||
let result.valid = 0
|
||
endif
|
||
endif
|
||
let query = s:search(s:last_pattern, 'nc', 0, s:search_timeout_remaining)
|
||
if !query | let result.valid = 0 | endif
|
||
call add(result.range, query)
|
||
let s:buf[s:nr].show_range = 1
|
||
|
||
elseif a:address.str ==# '\?'
|
||
call cursor(a:last_pos, 1)
|
||
let query = s:search(s:last_pattern, 'nb', 0, s:search_timeout_remaining)
|
||
if !query | let result.valid = 0 | endif
|
||
call add(result.range, query)
|
||
let s:buf[s:nr].show_range = 1
|
||
|
||
elseif a:address.str ==# '\&'
|
||
call cursor(a:last_pos, 1)
|
||
try
|
||
noautocmd keeppatterns keepjumps silent \&
|
||
catch
|
||
let result.valid = 0
|
||
endtry
|
||
call add(result.range, getpos('.')[1])
|
||
let s:buf[s:nr].show_range = 1
|
||
endif
|
||
|
||
if !empty(result.range)
|
||
" add offset
|
||
if has_key(a:address, 'offset') && !s:entire_file
|
||
let result.range[0] += s:offset_to_num(a:address.offset)
|
||
endif
|
||
|
||
" treat specifier 0 as 1
|
||
if result.range[0] == 0
|
||
let result.range[0] = 1
|
||
endif
|
||
|
||
" check if range exceeds file limits
|
||
if result.range[0] > line('$') || result.range[0] < 0
|
||
let result.valid = 0
|
||
endif
|
||
endif
|
||
|
||
return result
|
||
endfunction
|
||
|
||
function! s:evaluate_range(range_structure) abort
|
||
let result = { 'range': [], 'pattern': '', 'end': ''}
|
||
let s:range_valid = 1
|
||
let pos = s:buf[s:nr].cur_init_pos[0]
|
||
|
||
for specifier in a:range_structure
|
||
let tmp_pos = pos
|
||
let specifier_result = []
|
||
|
||
for address in specifier.addresses
|
||
" skip empty unclosed pattern specifier when range is empty otherwise
|
||
" substitute it with current position
|
||
if address.str =~# '^[?/]$'
|
||
let s:buf[s:nr].show_range = 1
|
||
if empty(result.range)
|
||
break
|
||
endif
|
||
let address.str = '.'
|
||
endif
|
||
let query = s:address_to_num(address, tmp_pos)
|
||
" % specifier doesn't accept additional addresses
|
||
if !query.valid || len(query.range) == 2 && len(specifier.addresses) > 1
|
||
let s:range_valid = 0
|
||
break
|
||
endif
|
||
let tmp_pos = query.range[-1]
|
||
let specifier_result = deepcopy(query.range)
|
||
let result.pattern = query.regex
|
||
endfor
|
||
if !s:range_valid
|
||
break
|
||
endif
|
||
|
||
call extend(result.range, specifier_result)
|
||
if exists('specifier.delimiter')
|
||
let s:specifier_delimiter = 1
|
||
endif
|
||
if get(specifier, 'delimiter') is# ';'
|
||
let pos = result.range[-1]
|
||
endif
|
||
endfor
|
||
|
||
if !empty(result.range)
|
||
let result.end = result.range[-1]
|
||
if len(result.range) == 1
|
||
call extend(result.range, result.range)
|
||
else
|
||
let result.range = result.range[-2:-1]
|
||
if result.range[-1] < result.range[-2]
|
||
let temp = result.range[-2]
|
||
let result.range[-2] = result.range[-1]
|
||
let result.range[-1] = temp
|
||
endif
|
||
endif
|
||
endif
|
||
|
||
return s:range_valid ? result : { 'range': [], 'pattern': '', 'end': '' }
|
||
endfunction
|
||
|
||
function! s:get_selection_regexp(range) abort
|
||
if empty(a:range)
|
||
return ''
|
||
endif
|
||
let pattern = '\%>' . (a:range[-2] - 1) . 'l\%<' . (a:range[-1] + 1) . 'l'
|
||
if &listchars =~# 'eol:.'
|
||
let pattern .= '\_.'
|
||
else
|
||
let pattern .= '\(.\|^\)'
|
||
endif
|
||
return pattern
|
||
endfunction
|
||
|
||
function! s:get_command(cmdl) abort
|
||
call s:trim(a:cmdl)
|
||
if !s:range_valid
|
||
return ''
|
||
endif
|
||
let result = matchstrpos(a:cmdl[0], s:cmd_pattern)
|
||
if result[2] != -1
|
||
call s:trim(a:cmdl, result[2])
|
||
return result[0]
|
||
endif
|
||
return ''
|
||
endfunction
|
||
|
||
function! s:add_opt(pattern, cmdl) abort
|
||
if empty(a:pattern) || !s:range_valid
|
||
\ || empty(substitute(a:pattern, '\\[cCvVmM]', '', 'g'))
|
||
return ''
|
||
endif
|
||
|
||
let option = ''
|
||
|
||
" magic
|
||
if has_key(a:cmdl, 'cmd') && a:cmdl.cmd.name =~# '\v^sm%[agic]$'
|
||
let option = '\m'
|
||
elseif has_key(a:cmdl, 'cmd') && a:cmdl.cmd.name =~# '\v^sno%[magic]$'
|
||
let option = '\M'
|
||
elseif &magic
|
||
let option = '\m'
|
||
else
|
||
let option = '\M'
|
||
endif
|
||
|
||
" case
|
||
if &ignorecase
|
||
if &smartcase
|
||
if match(a:pattern, '\u') ==# -1
|
||
let option .= '\c'
|
||
else
|
||
let option .= '\C'
|
||
endif
|
||
else
|
||
let option .= '\c'
|
||
endif
|
||
endif
|
||
|
||
return option . a:pattern
|
||
endfunction
|
||
|
||
function! s:add_hl_guard(pattern, range, type) abort
|
||
if empty(a:pattern)
|
||
return ''
|
||
endif
|
||
if a:type is 's'
|
||
if empty(a:range)
|
||
let start = s:buf[s:nr].cur_init_pos[0] - 1
|
||
let end = s:buf[s:nr].cur_init_pos[0] + 1
|
||
else
|
||
let start = a:range[-2] - 1
|
||
let end = a:range[-1] + 1
|
||
endif
|
||
elseif a:type is 'g'
|
||
if empty(a:range)
|
||
return a:pattern
|
||
else
|
||
let start = a:range[-2] - 1
|
||
let end = a:range[-1] + 1
|
||
endif
|
||
elseif a:type is 'r'
|
||
let start = a:range - 1
|
||
let end = a:range + 1
|
||
endif
|
||
|
||
let range = '\m\%>'. start .'l' . '\%<' . end . 'l'
|
||
" group is necessary to contain pattern inside range when using branches (\|)
|
||
let group_start = '\%('
|
||
let group_end = '\m\)'
|
||
" add backslash to the end of pattern if it ends with odd number of
|
||
" backslashes, this is required to properly close group
|
||
if len(matchstr(a:pattern, '\\\+$')) % 2
|
||
let group_end = '\' . group_end
|
||
endif
|
||
|
||
return range . group_start . a:pattern . group_end
|
||
endfunction
|
||
|
||
function! s:parse_global(cmdl) abort
|
||
call s:trim(a:cmdl.string)
|
||
let pattern = '\v^([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|.){-})%((\1)|$)'
|
||
let args = {}
|
||
let r = matchlist(a:cmdl.string[0], pattern)
|
||
if len(r)
|
||
let args.delimiter = r[1]
|
||
let args.pattern = s:add_opt((empty(r[2]) && !empty(r[3])) ? s:last_pattern : r[2], a:cmdl)
|
||
endif
|
||
return args
|
||
endfunction
|
||
|
||
function! s:parse_substitute(cmdl) abort
|
||
call s:trim(a:cmdl.string)
|
||
let pattern = '\v^([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|\1@!&.)*)%((\1)%((%(\\.|\1@!&.)*)%((\1)([&cegiInp#lr]+)=)=)=)=$'
|
||
let args = {}
|
||
let r = matchlist(a:cmdl.string[0], pattern)
|
||
if len(r)
|
||
let args.delimiter = r[1]
|
||
let args.pattern_org = (empty(r[2]) && !empty(r[3])) ? s:last_pattern : r[2]
|
||
let args.pattern = s:add_opt((empty(r[2]) && !empty(r[3])) ? s:last_pattern : r[2], a:cmdl)
|
||
let args.string = r[4]
|
||
let args.last_delimiter = r[5]
|
||
let args.flags = r[6]
|
||
endif
|
||
return args
|
||
endfunction
|
||
|
||
function! s:parse_subvert(cmdl) abort
|
||
if !exists("g:loaded_abolish") || !g:traces_abolish_integration
|
||
return {}
|
||
endif
|
||
if !exists('s:abolishID')
|
||
" https://stackoverflow.com/a/39216373
|
||
" 'dirty trick' to access script-local functions
|
||
let s:abolishID = '<SNR>' . matchstr(matchstr(split(execute('scriptnames'), "\n"), 'abolish.vim'), '^\s*\zs\d\+') . '_'
|
||
endif
|
||
if !exists('*' . s:abolishID . 'substitute_command')
|
||
return {}
|
||
endif
|
||
call s:trim(a:cmdl.string)
|
||
let a:cmdl.cmd.name = 'substitute'
|
||
let pattern = '\v^([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|\1@!&.)*)%((\1)%((%(\\.|\1@!&.)*)%((\1)([aviw&cegiInp#lr]+)=)=)=)=$'
|
||
let args = {}
|
||
let r = matchlist(a:cmdl.string[0], pattern)
|
||
if len(r) && !empty(r[2])
|
||
" String is always '\=Abolished()', unlet to update preview window
|
||
if exists('s:buf[s:nr].preview_window.args')
|
||
unlet s:buf[s:nr].preview_window.args
|
||
endif
|
||
let args.delimiter = r[1]
|
||
let args.pattern_org = substitute({s:abolishID}substitute_command('', r[2], r[4], r[6])[1:], '\/\\=Abolished.*', '', '')
|
||
let args.pattern = args.pattern_org
|
||
let args.string = !empty(r[4]) ? '\=Abolished()' : ''
|
||
let args.last_delimiter = r[5]
|
||
let args.flags = substitute(r[6], '\C[avIiw]', '', 'g')
|
||
endif
|
||
return args
|
||
endfunction
|
||
|
||
function! s:parse_sort(cmdl) abort
|
||
call s:trim(a:cmdl.string)
|
||
let pattern = '\v^.{-}([[:graph:]]&[^[:alnum:]\\"|])(%(\\.|.){-})%((\1)|$)'
|
||
let args = {}
|
||
let r = matchlist(a:cmdl.string[0], pattern)
|
||
if len(r)
|
||
let args.delimiter = r[1]
|
||
let args.pattern = s:add_opt((empty(r[2]) && !empty(r[3])) ? s:last_pattern : r[2], a:cmdl)
|
||
endif
|
||
return args
|
||
endfunction
|
||
|
||
function! s:parse_normal(cmdl) abort
|
||
let args = {}
|
||
call s:trim(a:cmdl.string)
|
||
let args.string = a:cmdl.string
|
||
return args
|
||
endfunction
|
||
|
||
function! s:parse_command(cmdl) abort
|
||
let a:cmdl.cmd.name = s:get_command(a:cmdl.string)
|
||
if a:cmdl.cmd.name =~# '\v^%(g%[lobal]\!=|v%[global])$'
|
||
let a:cmdl.cmd.args = s:parse_global(a:cmdl)
|
||
elseif a:cmdl.cmd.name =~# '\v^%(s%[ubstitute]|sm%[agic]|sno%[magic])$'
|
||
let a:cmdl.cmd.args = s:parse_substitute(a:cmdl)
|
||
elseif a:cmdl.cmd.name =~# '\v^%(sor%[t]\!=)$'
|
||
let a:cmdl.cmd.args = s:parse_sort(a:cmdl)
|
||
elseif a:cmdl.cmd.name =~# '\v^norm%[al]\!=$'
|
||
let a:cmdl.cmd.args = s:parse_normal(a:cmdl)
|
||
elseif a:cmdl.cmd.name =~# '\v^(Subvert|S)$'
|
||
let a:cmdl.cmd.args = s:parse_subvert(a:cmdl)
|
||
endif
|
||
endfunction
|
||
|
||
function! s:pos_pattern(pattern, range, delimiter, type) abort
|
||
if g:traces_preserve_view_state || empty(a:pattern)
|
||
return
|
||
endif
|
||
|
||
let stopline = 0
|
||
if len(a:range) > 1 && !get(s:, 'entire_file')
|
||
if a:delimiter ==# '?'
|
||
call cursor([a:range[-1], 1])
|
||
call cursor([a:range[-1], col('$')])
|
||
let stopline = a:range[-2]
|
||
else
|
||
call cursor([a:range[-2], 1])
|
||
let stopline = a:range[-1]
|
||
endif
|
||
else
|
||
call cursor(s:buf[s:nr].cur_init_pos)
|
||
if a:type && empty(a:range)
|
||
let stopline = s:buf[s:nr].cur_init_pos[0]
|
||
endif
|
||
endif
|
||
if a:delimiter ==# '?'
|
||
let position = s:search(a:pattern, 'cb', stopline, s:search_timeout_remaining)
|
||
else
|
||
let position = s:search(a:pattern, 'c', stopline, s:search_timeout_remaining)
|
||
endif
|
||
if position !=# 0
|
||
let s:moved = 1
|
||
endif
|
||
endfunction
|
||
|
||
function! s:pos_range(end, pattern) abort
|
||
if g:traces_preserve_view_state || empty(a:end)
|
||
return
|
||
endif
|
||
|
||
if exists('s:buf[s:nr].pre_cmdl_view')
|
||
if get(s:buf[s:nr].pre_cmdl_view, 'mode', '') =~# "^[vV\<C-V>]"
|
||
\ && a:end > line('w$')
|
||
unlet s:buf[s:nr].pre_cmdl_view.mode
|
||
call winrestview(s:buf[s:nr].pre_cmdl_view)
|
||
endif
|
||
unlet s:buf[s:nr].pre_cmdl_view
|
||
endif
|
||
call cursor([a:end, 1])
|
||
if !empty(a:pattern)
|
||
call s:search(a:pattern, 'c', a:end, s:search_timeout_remaining)
|
||
endif
|
||
let s:moved = 1
|
||
endfunction
|
||
|
||
function! s:highlight(group, pattern, priority) abort
|
||
let cur_win = s:buf[s:nr].cur_win
|
||
if exists('s:buf[s:nr].win[cur_win].matches[a:group].pattern')
|
||
if s:buf[s:nr].win[cur_win].matches[a:group].pattern ==# a:pattern
|
||
return
|
||
endif
|
||
elseif empty(a:pattern)
|
||
return
|
||
endif
|
||
if a:group ==# 'TracesSearch' || a:group ==# 'TracesReplace'
|
||
noautocmd let &hlsearch = 0
|
||
endif
|
||
noautocmd let &winwidth = max([1, &winminwidth])
|
||
noautocmd let &winheight = max([1, &winminheight])
|
||
|
||
let windows = filter(win_findbuf(s:nr), {_, val -> win_id2win(val)})
|
||
|
||
if empty(s:buf[s:nr].win)
|
||
" save local options
|
||
for id in windows
|
||
let s:buf[s:nr].win[id] = {}
|
||
let win = s:buf[s:nr].win[id]
|
||
let win.options = {}
|
||
let win.options.cursorcolumn = getwinvar(id, '&cursorcolumn')
|
||
let win.options.cursorline = getwinvar(id, '&cursorline')
|
||
let win.options.scrolloff = getwinvar(id, '&scrolloff')
|
||
let win.options.conceallevel = getwinvar(id, '&conceallevel')
|
||
let win.options.concealcursor = getwinvar(id, '&concealcursor')
|
||
endfor
|
||
" set local options
|
||
for id in windows
|
||
call setwinvar(id, '&' . 'cursorcolumn', 0)
|
||
call setwinvar(id, '&' . 'cursorline', 0)
|
||
if id isnot s:buf[s:nr].cur_win
|
||
call setwinvar(id, '&' . 'scrolloff', 0)
|
||
endif
|
||
endfor
|
||
endif
|
||
|
||
" add matches
|
||
for id in windows
|
||
if getwininfo(id)[0].height is 0 || getwininfo(id)[0].width is 0
|
||
" skip minimized windows
|
||
continue
|
||
endif
|
||
if empty(getcmdwintype()) && !s:has_matchdelete_win
|
||
noautocmd call win_gotoid(id)
|
||
endif
|
||
let win = s:buf[s:nr].win[id]
|
||
let win.matches = get(win, 'matches', {})
|
||
if !exists('win.matches[a:group]')
|
||
if s:has_matchdelete_win
|
||
silent! let match_id = matchadd(a:group, a:pattern, a:priority, -1, {'window': id})
|
||
else
|
||
silent! let match_id = matchadd(a:group, a:pattern, a:priority)
|
||
endif
|
||
let win.matches[a:group] = {'match_id': match_id, 'pattern': a:pattern}
|
||
let s:redraw_later = 1
|
||
if a:group ==# 'Conceal'
|
||
call setwinvar(id, '&conceallevel', 2)
|
||
call setwinvar(id, '&concealcursor', 'c')
|
||
endif
|
||
else
|
||
if s:has_matchdelete_win
|
||
silent! call matchdelete(win.matches[a:group].match_id, id)
|
||
silent! let match_id = matchadd(a:group, a:pattern, a:priority, -1, {'window': id})
|
||
else
|
||
silent! call matchdelete(win.matches[a:group].match_id)
|
||
silent! let match_id = matchadd(a:group, a:pattern, a:priority)
|
||
endif
|
||
let win.matches[a:group] = {'match_id': match_id, 'pattern': a:pattern}
|
||
let s:redraw_later = 1
|
||
endif
|
||
endfor
|
||
|
||
if empty(getcmdwintype()) && !s:has_matchdelete_win
|
||
noautocmd call win_gotoid(s:buf[s:nr].cur_win)
|
||
endif
|
||
endfunction
|
||
|
||
function! s:format_range(cmdl) abort
|
||
let range_str = ''
|
||
if len(a:cmdl.range.abs) == 0
|
||
let range_str .= s:buf[s:nr].cur_init_pos[0]
|
||
elseif len(a:cmdl.range.abs) == 1
|
||
let range_str .= a:cmdl.range.abs[0]
|
||
else
|
||
let range_str .= max([a:cmdl.range.abs[-2], line("w0")])
|
||
let range_str .= ';'
|
||
let range_str .= min([a:cmdl.range.abs[-1], line("w$")])
|
||
endif
|
||
return range_str
|
||
endfunction
|
||
|
||
function! s:format_command(cmdl) abort
|
||
let cmd_str = ''
|
||
let cmd_str .= a:cmdl.cmd.name
|
||
let cmd_str .= a:cmdl.cmd.args.delimiter
|
||
let cmd_str .= a:cmdl.cmd.args.pattern_org
|
||
let cmd_str .= a:cmdl.cmd.args.delimiter
|
||
let s_mark = s:buf[s:nr].s_mark
|
||
if a:cmdl.cmd.args.string =~# '^\\='
|
||
let cmd_str .= printf("\\='%s' . printf('%%s', %s) . '%s'",
|
||
\ s_mark, empty(a:cmdl.cmd.args.string[2:]) ?
|
||
\ '''''' : a:cmdl.cmd.args.string[2:], s_mark)
|
||
else
|
||
" make ending single backslash literal or else it will escape s_mark
|
||
if substitute(a:cmdl.cmd.args.string, '\\\\', '', 'g') =~# '\\$'
|
||
let cmd_str .= s_mark . a:cmdl.cmd.args.string . '\' . s_mark
|
||
else
|
||
let cmd_str .= s_mark . a:cmdl.cmd.args.string . s_mark
|
||
endif
|
||
endif
|
||
let cmd_str .= a:cmdl.cmd.args.delimiter
|
||
let cmd_str .= substitute(a:cmdl.cmd.args.flags, '[^giI]', '', 'g')
|
||
return cmd_str
|
||
endfunction
|
||
|
||
function! s:preview_window(range, pattern, type, preview_cmd) abort
|
||
if !empty(getcmdwintype())
|
||
return
|
||
endif
|
||
" skip when arguments are unchanged
|
||
if exists('s:buf[s:nr].preview_window.args')
|
||
\ && s:buf[s:nr].preview_window.args ==# string(a:)
|
||
return
|
||
endif
|
||
|
||
let winopen_pattern = '\v^\s*((('
|
||
\ . 'vert%[ical]|'
|
||
\ . 'lefta%[bove]|'
|
||
\ . 'abo%[veleft]|'
|
||
\ . 'rightb%[elow]|'
|
||
\ . 'bel%[owright]|'
|
||
\ . 'to%[pleft]|'
|
||
\ . 'bo%[tright]'
|
||
\ . ')\s+)+)=(\d+\s*)=v=new\s*$'
|
||
let winopen = ''
|
||
try
|
||
if g:traces_preview_window =~# winopen_pattern
|
||
let winopen = g:traces_preview_window
|
||
elseif eval(g:traces_preview_window) =~# winopen_pattern
|
||
let winopen = eval(g:traces_preview_window)
|
||
else
|
||
return
|
||
endif
|
||
catch
|
||
return
|
||
endtry
|
||
|
||
let range = a:range
|
||
if a:type is 'g' && empty(range)
|
||
let range = [0, line('$')]
|
||
endif
|
||
if empty(range) || empty(a:pattern)
|
||
\ || range[0] >= line('w0') && range[1] <= line('w$')
|
||
if exists('s:buf[s:nr].preview_window')
|
||
let s:buf[s:nr].preview_window.args = string(a:)
|
||
noautocmd call win_gotoid(s:buf[s:nr].preview_window.winid)
|
||
noautocmd %delete
|
||
noautocmd call win_gotoid(s:buf[s:nr].cur_win)
|
||
endif
|
||
return
|
||
endif
|
||
|
||
" prepare preview window
|
||
if !exists('s:buf[s:nr].preview_window')
|
||
execute 'noautocmd noswapfile' winopen
|
||
noautocmd setlocal undolevels=-1 nobuflisted buftype=nofile bufhidden=wipe
|
||
let s:buf[s:nr].preview_window = {}
|
||
let s:buf[s:nr].preview_window.bufnr = bufnr('%')
|
||
let s:buf[s:nr].preview_window.winid = win_getid()
|
||
call matchadd('TracesReplace', s:buf[s:nr].s_mark . '\_.\{-}' . s:buf[s:nr].s_mark, 101, -1,)
|
||
call matchadd('Conceal', s:buf[s:nr].s_mark . '\|' . s:buf[s:nr].s_mark, 102, -1)
|
||
noautocmd setlocal conceallevel=2
|
||
noautocmd setlocal concealcursor=c
|
||
noautocmd setlocal nocursorline nocursorcolumn nonumber norelativenumber
|
||
noautocmd call win_gotoid(s:buf[s:nr].cur_win)
|
||
endif
|
||
let s:buf[s:nr].preview_window.args = string(a:)
|
||
|
||
" gather lines for preview window
|
||
let view = winsaveview()
|
||
let wintop = line('w0')
|
||
let winbot = line('w$')
|
||
let filtered = []
|
||
let currentline = max([range[0], 1])
|
||
let stopline = range[1]
|
||
let max = getwininfo(s:buf[s:nr].preview_window.winid)[0].height
|
||
while currentline <= stopline && len(filtered) < max
|
||
call cursor(currentline, 1)
|
||
let matchstart = s:search(a:pattern, 'cn', stopline, s:search_timeout_remaining)
|
||
if matchstart
|
||
call cursor(matchstart, 1)
|
||
let matchend = s:search(a:pattern, 'cen', stopline, s:search_timeout_remaining)
|
||
let currentline = matchend + 1
|
||
if matchstart < wintop || matchstart > winbot
|
||
call extend(filtered, getbufline('%', matchstart, matchend))
|
||
endif
|
||
else
|
||
break
|
||
endif
|
||
endwhile
|
||
call winrestview(view)
|
||
|
||
" fill and highlight preview window
|
||
noautocmd call win_gotoid(s:buf[s:nr].preview_window.winid)
|
||
noautocmd %delete
|
||
noautocmd call append(0, filtered)
|
||
silent! call matchdelete(s:buf[s:nr].preview_window.match_id)
|
||
if a:type is 's' && !empty(a:preview_cmd)
|
||
execute 'silent! %' a:preview_cmd
|
||
else
|
||
silent! let s:buf[s:nr].preview_window.match_id =
|
||
\ matchadd('TracesSearch', a:pattern, 101, -1)
|
||
endif
|
||
normal! Gddgg
|
||
noautocmd call win_gotoid(s:buf[s:nr].cur_win)
|
||
endfunction
|
||
|
||
function! s:preview_window_close()
|
||
if !exists('s:buf[s:nr].preview_window')
|
||
return
|
||
endif
|
||
execute 'noautocmd bwipe! ' s:buf[s:nr].preview_window.bufnr
|
||
unlet s:buf[s:nr].preview_window
|
||
endfunction
|
||
|
||
function! s:preview_substitute(cmdl) abort
|
||
if empty(a:cmdl.cmd.args)
|
||
return
|
||
endif
|
||
|
||
let ptrn = a:cmdl.cmd.args.pattern
|
||
let str = a:cmdl.cmd.args.string
|
||
let range = a:cmdl.range.abs
|
||
let dlm = a:cmdl.cmd.args.delimiter
|
||
|
||
call s:pos_pattern(ptrn, range, dlm, 1)
|
||
|
||
if !g:traces_substitute_preview || &readonly || !&modifiable || empty(str)
|
||
call s:highlight('TracesSearch', s:add_hl_guard(ptrn, range, 's'), 101)
|
||
call s:preview_window(range, ptrn, 's', '')
|
||
return
|
||
endif
|
||
|
||
call s:save_undo_history()
|
||
|
||
if s:buf[s:nr].undo_file is 0
|
||
call s:highlight('TracesSearch', s:add_hl_guard(ptrn, range, 's'), 101)
|
||
call s:preview_window(range, ptrn, 's', '')
|
||
return
|
||
endif
|
||
|
||
let range_str = s:format_range(a:cmdl)
|
||
let cmd_str = s:format_command(a:cmdl)
|
||
call s:preview_window(range, ptrn, 's', cmd_str)
|
||
let cmd = 'noautocmd keepjumps keeppatterns ' . range_str . cmd_str
|
||
let tick = b:changedtick
|
||
|
||
let lines = line('$')
|
||
let view = winsaveview()
|
||
let ul = &l:undolevels
|
||
noautocmd let &l:undolevels = 0
|
||
silent! execute cmd
|
||
noautocmd let &l:undolevels = ul
|
||
|
||
if tick == b:changedtick
|
||
return
|
||
endif
|
||
|
||
let s:buf[s:nr].changed = 1
|
||
|
||
call winrestview(view)
|
||
let s:redraw_later = 1
|
||
let lines = lines - line('$')
|
||
|
||
if lines && !get(s:, 'entire_file') && !empty(range)
|
||
if len(range) == 1
|
||
call add(range, range[0])
|
||
endif
|
||
let range[-1] -= lines
|
||
call s:highlight('Visual', s:get_selection_regexp(range), 100)
|
||
endif
|
||
call s:highlight('TracesSearch', '', 101)
|
||
call s:highlight('TracesReplace', s:buf[s:nr].s_mark . '\_.\{-}' . s:buf[s:nr].s_mark, 101)
|
||
call s:highlight('Conceal', s:buf[s:nr].s_mark . '\|' . s:buf[s:nr].s_mark, 102)
|
||
endfunction
|
||
|
||
function! s:preview_global(cmdl) abort
|
||
if empty(a:cmdl.range.specifier) && has_key(a:cmdl.cmd.args, 'pattern')
|
||
let pattern = a:cmdl.cmd.args.pattern
|
||
let range = a:cmdl.range.abs
|
||
call s:highlight('TracesSearch', s:add_hl_guard(pattern, range, 'g'), 101)
|
||
call s:pos_pattern(pattern, range, a:cmdl.cmd.args.delimiter, 0)
|
||
call s:preview_window(range, pattern, 'g', '')
|
||
endif
|
||
endfunction
|
||
|
||
function! s:preview_sort(cmdl) abort
|
||
if empty(a:cmdl.range.specifier) && has_key(a:cmdl.cmd.args, 'pattern')
|
||
let pattern = a:cmdl.cmd.args.pattern
|
||
let range = a:cmdl.range.abs
|
||
call s:highlight('TracesSearch', s:add_hl_guard(pattern, range, 'g'), 101)
|
||
call s:pos_pattern(pattern, range, a:cmdl.cmd.args.delimiter, 0)
|
||
call s:preview_window(range, pattern, 'g', '')
|
||
endif
|
||
endfunction
|
||
|
||
function! s:clear_cursors() abort
|
||
if exists('g:traces_cursors')
|
||
silent! call map(g:traces_cursors, 'matchdelete(v:val)')
|
||
unlet g:traces_cursors
|
||
endif
|
||
endfunction
|
||
|
||
function! s:preview_normal(cmdl) abort
|
||
if !empty(getcmdwintype())
|
||
return
|
||
endif
|
||
let str = a:cmdl.cmd.args.string[0]
|
||
if !g:traces_normal_preview || &readonly || !&modifiable || empty(str)
|
||
\ || (!has("patch-8.2.2961") && !has('nvim'))
|
||
call s:clear_cursors()
|
||
return
|
||
endif
|
||
|
||
let cmd = a:cmdl.cmd.name
|
||
let range = ''
|
||
if len(a:cmdl.range.abs) == 1
|
||
let range .= a:cmdl.range.abs[0]
|
||
elseif len(a:cmdl.range.abs) > 1
|
||
let range .= max([a:cmdl.range.abs[-2], line("w0")])
|
||
let range .= ';'
|
||
let range .= min([a:cmdl.range.abs[-1], line("w$")])
|
||
endif
|
||
|
||
call s:save_undo_history()
|
||
|
||
if exists('g:traces_cursors') && !empty(g:traces_cursors)
|
||
silent! call map(g:traces_cursors, 'matchdelete(v:val)')
|
||
endif
|
||
|
||
let g:traces_cursors = []
|
||
let tick = b:changedtick
|
||
let winid = win_getid()
|
||
let view = winsaveview()
|
||
let ul = &l:undolevels
|
||
noautocmd let &l:undolevels = 1
|
||
try
|
||
execute 'silent noautocmd keepjumps' range . cmd str . "\<cmd>call add(g:traces_cursors, matchaddpos('TracesCursor', [getcurpos()[1:2]], 101))\<cr>"
|
||
catch
|
||
finally
|
||
execute "noautocmd keepjumps normal! \<esc>\<esc>"
|
||
endtry
|
||
noautocmd call win_gotoid(winid)
|
||
noautocmd let &l:undolevels = ul
|
||
call winrestview(view)
|
||
|
||
" required to highlight EOL with matchaddpos()
|
||
noautocmd setlocal list
|
||
noautocmd setlocal listchars=eol:\ ,tab:\ \
|
||
noautocmd let &hlsearch = 0
|
||
let s:redraw_later = 1
|
||
call s:restore_marks()
|
||
|
||
if tick == b:changedtick
|
||
return
|
||
endif
|
||
let s:buf[s:nr].changed = 1
|
||
endfunction
|
||
|
||
function! s:cmdl_enter(view) abort
|
||
let s:buf[s:nr] = {}
|
||
let s:buf[s:nr].search_cache = {}
|
||
let s:buf[s:nr].view = winsaveview()
|
||
let s:buf[s:nr].show_range = 0
|
||
let s:buf[s:nr].duration = 0
|
||
let s:buf[s:nr].hlsearch = &hlsearch
|
||
let s:buf[s:nr].cword = expand('<cword>')
|
||
let s:buf[s:nr].cWORD = expand('<cWORD>')
|
||
let s:buf[s:nr].cfile = expand('<cfile>')
|
||
let s:buf[s:nr].cur_init_pos = [line('.'), col('.')]
|
||
let s:buf[s:nr].seq_last = undotree().seq_last
|
||
let s:buf[s:nr].empty_undotree = empty(undotree().entries)
|
||
let s:buf[s:nr].changed = 0
|
||
let s:buf[s:nr].cmdheight = &cmdheight
|
||
let s:buf[s:nr].redraw = 1
|
||
let s:buf[s:nr].s_mark = (&encoding == 'utf-8' ? "\uf8b4" : '' )
|
||
let s:buf[s:nr].cur_win = win_getid()
|
||
let s:buf[s:nr].alt_win = win_getid(winnr('#'))
|
||
let s:buf[s:nr].winwidth = &winwidth
|
||
let s:buf[s:nr].winheight = &winheight
|
||
let s:buf[s:nr].list = &l:list
|
||
let s:buf[s:nr].listchars = &l:listchars
|
||
let s:buf[s:nr].pre_cmdl_view = a:view
|
||
let s:buf[s:nr].win = {}
|
||
call s:save_marks()
|
||
endfunction
|
||
|
||
function! traces#cmdl_leave() abort
|
||
let s:nr = bufnr('%')
|
||
if !exists('s:buf[s:nr]')
|
||
return
|
||
endif
|
||
|
||
call s:restore_undo_history()
|
||
|
||
" clear highlights
|
||
for id in keys(s:buf[s:nr].win)
|
||
if empty(getcmdwintype()) && !s:has_matchdelete_win
|
||
noautocmd call win_gotoid(id)
|
||
endif
|
||
for group in keys(get(s:buf[s:nr].win[id], 'matches', {}))
|
||
if s:has_matchdelete_win
|
||
silent! call matchdelete(s:buf[s:nr].win[id].matches[group].match_id, id)
|
||
else
|
||
silent! call matchdelete(s:buf[s:nr].win[id].matches[group].match_id)
|
||
endif
|
||
endfor
|
||
endfor
|
||
|
||
" restore previous window <c-w>p
|
||
if empty(getcmdwintype()) && win_getid(winnr('#')) isnot s:buf[s:nr].alt_win
|
||
let winrestcmd = winrestcmd()
|
||
noautocmd call win_gotoid(s:buf[s:nr].alt_win)
|
||
noautocmd call win_gotoid(s:buf[s:nr].cur_win)
|
||
execute winrestcmd
|
||
endif
|
||
|
||
" restore local options
|
||
for id in keys(s:buf[s:nr].win)
|
||
for option in keys(get(s:buf[s:nr].win[id], 'options', {}))
|
||
call setwinvar(id, '&' . option, s:buf[s:nr].win[id].options[option])
|
||
endfor
|
||
endfor
|
||
|
||
" restore global options
|
||
if &hlsearch !=# s:buf[s:nr].hlsearch
|
||
noautocmd let &hlsearch = s:buf[s:nr].hlsearch
|
||
endif
|
||
if &cmdheight !=# s:buf[s:nr].cmdheight
|
||
noautocmd let &cmdheight = s:buf[s:nr].cmdheight
|
||
endif
|
||
if &winwidth isnot s:buf[s:nr].winwidth
|
||
noautocmd let &winwidth = s:buf[s:nr].winwidth
|
||
endif
|
||
if &winheight isnot s:buf[s:nr].winheight
|
||
noautocmd let &winheight = s:buf[s:nr].winheight
|
||
endif
|
||
|
||
noautocmd let &l:list = s:buf[s:nr].list
|
||
noautocmd let &l:listchars = s:buf[s:nr].listchars
|
||
|
||
call s:clear_cursors()
|
||
call s:preview_window_close()
|
||
|
||
if winsaveview() !=# s:buf[s:nr].view
|
||
call winrestview(s:buf[s:nr].view)
|
||
endif
|
||
|
||
unlet s:buf[s:nr]
|
||
endfunction
|
||
|
||
function! s:evaluate_cmdl(string) abort
|
||
let cmdl = {}
|
||
let cmdl.string = a:string
|
||
let r = s:evaluate_range(s:parse_range([], cmdl.string))
|
||
let cmdl.range = {}
|
||
let cmdl.range.abs = r.range
|
||
let cmdl.range.end = r.end
|
||
let cmdl.range.pattern = s:get_selection_regexp(r.range)
|
||
let cmdl.range.specifier = s:add_hl_guard(s:add_opt(r.pattern, cmdl), r.end, 'r')
|
||
|
||
let cmdl.cmd = {}
|
||
let cmdl.cmd.args = {}
|
||
call s:parse_command(cmdl)
|
||
|
||
return cmdl
|
||
endfunction
|
||
|
||
function! s:save_marks() abort
|
||
if !exists('s:buf[s:nr].marks')
|
||
let types = ['[', ']', '<', '>']
|
||
let s:buf[s:nr].marks = {}
|
||
for mark in types
|
||
let s:buf[s:nr].marks[mark] = getpos("'" . mark)
|
||
endfor
|
||
endif
|
||
endfunction
|
||
|
||
function! s:restore_marks() abort
|
||
if exists('s:buf[s:nr].marks')
|
||
for mark in keys(s:buf[s:nr].marks)
|
||
call setpos("'" . mark, s:buf[s:nr].marks[mark])
|
||
endfor
|
||
endif
|
||
endfunction
|
||
|
||
function! s:save_undo_history() abort
|
||
if exists('s:buf[s:nr].undo_file')
|
||
return
|
||
endif
|
||
if !empty(getcmdwintype()) || s:buf[s:nr].empty_undotree
|
||
let s:buf[s:nr].undo_file = 1
|
||
return
|
||
endif
|
||
let s:buf[s:nr].undo_file = tempname()
|
||
let time = reltime()
|
||
noautocmd silent execute 'wundo ' . s:buf[s:nr].undo_file
|
||
let s:wundo_time = reltimefloat(reltime(time)) * 1000
|
||
if !filereadable(s:buf[s:nr].undo_file)
|
||
let s:buf[s:nr].undo_file = 0
|
||
return
|
||
endif
|
||
endfunction
|
||
|
||
function! s:restore_undo_history() abort
|
||
if s:buf[s:nr].changed
|
||
noautocmd keepjumps silent undo
|
||
call s:restore_marks()
|
||
endif
|
||
|
||
if type(get(s:buf[s:nr], 'undo_file')) isnot v:t_string
|
||
return
|
||
endif
|
||
|
||
if has('nvim')
|
||
" can't use try/catch on Neovim inside CmdlineLeave
|
||
" https://github.com/neovim/neovim/issues/7876
|
||
silent! execute 'noautocmd rundo ' . s:buf[s:nr].undo_file
|
||
if undotree().seq_last !=# s:buf[s:nr].seq_last
|
||
echohl WarningMsg
|
||
echom 'traces.vim - undo history could not be restored'
|
||
echohl None
|
||
endif
|
||
else
|
||
try
|
||
silent execute 'noautocmd rundo ' . s:buf[s:nr].undo_file
|
||
catch
|
||
echohl WarningMsg
|
||
echom 'traces.vim - ' . v:exception
|
||
echohl None
|
||
endtry
|
||
endif
|
||
call delete(s:buf[s:nr].undo_file)
|
||
endfunction
|
||
|
||
function! s:adjust_cmdheight() abort
|
||
let len = strwidth(strtrans(getcmdline())) + 2
|
||
let col = &columns
|
||
let height = &cmdheight
|
||
if col * height < len
|
||
noautocmd let &cmdheight=(len / col) + 1
|
||
redraw
|
||
elseif col * (height - 1) >= len && height > s:buf[s:nr].cmdheight
|
||
noautocmd let &cmdheight=max([(len / col), s:buf[s:nr].cmdheight])
|
||
redraw
|
||
endif
|
||
endfunction
|
||
|
||
function! s:skip_modifiers(cmdl) abort
|
||
let cmdl = a:cmdl
|
||
" skip leading colons
|
||
let cmdl = substitute(cmdl, '\v^[[:space:]:]+', '', '')
|
||
" skip modifiers
|
||
let pattern = '\v^%(%('
|
||
\ . 'sil%[ent]%(\!|\H@=)|'
|
||
\ . 'verb%[ose]\H@=|'
|
||
\ . 'noa%[utocmd]\H@=|'
|
||
\ . 'loc%[kmarks]\H@=|'
|
||
\ . 'keepp%[atterns]\H@=|'
|
||
\ . 'keepa%[lt]\H@=|'
|
||
\ . 'keepj%[umps]\H@=|'
|
||
\ . 'kee%[pmarks]\H@='
|
||
\ . ')\s*)+'
|
||
let cmdl = substitute(cmdl, pattern, '', '')
|
||
|
||
if g:traces_skip_modifiers
|
||
" skip *do modifiers
|
||
let cmdl = substitute(cmdl,
|
||
\ '\v^%(%(%(\d+|\.|\$|\%)\s*[,;]=\s*)+)=\s*%(cdo|cfdo|ld%[o]|lfdo'
|
||
\ . '|bufd%[o]|tabd%[o]\!@!|argdo|wind%[o]\!@!)%(\!|\H@=)\s*', '', '')
|
||
" skip modifiers again
|
||
let cmdl = substitute(cmdl, pattern, '', '')
|
||
endif
|
||
|
||
return cmdl
|
||
endfunction
|
||
|
||
function! traces#init(cmdl, view) abort
|
||
let s:nr = bufnr('%')
|
||
if !exists('s:buf[s:nr]')
|
||
call s:cmdl_enter(a:view)
|
||
endif
|
||
|
||
let s:redraw_later = 0
|
||
let s:moved = 0
|
||
let s:last_pattern = @/
|
||
let s:specifier_delimiter = 0
|
||
let s:wundo_time = 0
|
||
let s:search_timeout_remaining = s:search_timeout
|
||
|
||
if s:buf[s:nr].duration < s:timeout
|
||
let start_time = reltime()
|
||
endif
|
||
|
||
if s:buf[s:nr].changed
|
||
let view = winsaveview()
|
||
noautocmd keepjumps silent undo
|
||
let s:buf[s:nr].changed = 0
|
||
let s:redraw_later = 1
|
||
call s:restore_marks()
|
||
call winrestview(view)
|
||
endif
|
||
|
||
if s:buf[s:nr].duration < s:timeout
|
||
let cmdl = s:evaluate_cmdl([s:skip_modifiers(a:cmdl)])
|
||
" range preview
|
||
if (!empty(cmdl.cmd.name) && !empty(cmdl.cmd.args) || s:buf[s:nr].show_range
|
||
\ || s:specifier_delimiter && g:traces_num_range_preview)
|
||
\ && !get(s:, 'entire_file')
|
||
call s:highlight('Visual', cmdl.range.pattern, 100)
|
||
if empty(cmdl.cmd.name)
|
||
call s:preview_window_close()
|
||
call s:highlight('TracesSearch', cmdl.range.specifier, 101)
|
||
endif
|
||
call s:pos_range(cmdl.range.end, cmdl.range.specifier)
|
||
else
|
||
" clear unnecessary range hl
|
||
call s:highlight('Visual', '', 100)
|
||
endif
|
||
|
||
" cmd preview
|
||
if cmdl.cmd.name =~# '\v^%(s%[ubstitute]|sm%[agic]|sno%[magic])$'
|
||
call s:preview_substitute(cmdl)
|
||
elseif cmdl.cmd.name =~# '\v^%(g%[lobal]\!=|v%[global])$'
|
||
call s:preview_global(cmdl)
|
||
elseif cmdl.cmd.name =~# '\v^%(sor%[t]\!=)$'
|
||
call s:preview_sort(cmdl)
|
||
elseif cmdl.cmd.name =~# '\v^norm%[al]\!=$'
|
||
call s:preview_normal(cmdl)
|
||
endif
|
||
|
||
if empty(cmdl.cmd.name) && empty(cmdl.range.specifier)
|
||
\ || !empty(cmdl.cmd.name) && empty(cmdl.cmd.args)
|
||
call s:highlight('TracesSearch', '', 101)
|
||
call s:preview_window_close()
|
||
call s:clear_cursors()
|
||
endif
|
||
else
|
||
call s:preview_window_close()
|
||
endif
|
||
|
||
" move to starting position if necessary
|
||
if !s:moved && winsaveview() != s:buf[s:nr].view && !wildmenumode()
|
||
call winrestview(s:buf[s:nr].view)
|
||
endif
|
||
|
||
" redraw screen if necessary
|
||
if s:redraw_later && !wildmenumode()
|
||
call s:adjust_cmdheight()
|
||
if has('nvim')
|
||
redraw
|
||
else
|
||
" https://github.com/markonm/traces.vim/issues/17
|
||
" if Vim is missing CmdlineChanged, use explicit redraw only at the
|
||
" start of preview or else it is going to be slow
|
||
if exists('##CmdlineChanged') || s:buf[s:nr].redraw
|
||
redraw
|
||
let s:buf[s:nr].redraw = 0
|
||
else
|
||
call winline()
|
||
endif
|
||
" after patch 8.0.1449, necessary for linux cui, otherwise highlighting
|
||
" is not drawn properly, fixed by 8.0.1476
|
||
if has('unix') && !has('gui_running') && has("patch-8.0.1449") && !has("patch-8.0.1476")
|
||
silent! call feedkeys(getcmdpos() is 1 ? "\<right>\<left>" : "\<left>\<right>", 'tn')
|
||
endif
|
||
endif
|
||
endif
|
||
|
||
if exists('start_time')
|
||
let s:buf[s:nr].duration = reltimefloat(reltime(start_time)) * 1000 - s:wundo_time
|
||
endif
|
||
endfunction
|
||
|
||
function! traces#get_cword() abort
|
||
return s:buf[s:nr].cword
|
||
endfunction
|
||
|
||
function! traces#get_cWORD() abort
|
||
return s:buf[s:nr].cWORD
|
||
endfunction
|
||
|
||
function! traces#get_cfile() abort
|
||
return s:buf[s:nr].cfile
|
||
endfunction
|
||
|
||
function! traces#get_pfile() abort
|
||
let result = split(globpath(&path, s:buf[s:nr].cfile), '\n')
|
||
if len(result) && len(s:buf[s:nr].cfile)
|
||
return result[-1]
|
||
endif
|
||
return ''
|
||
endfunction
|
||
|
||
function! traces#check_b() abort
|
||
let s:nr = bufnr('%')
|
||
if getcmdtype() == ':' && exists('s:buf[s:nr]')
|
||
return 1
|
||
endif
|
||
endfunction
|
||
|
||
let &cpo = s:cpo_save
|
||
unlet s:cpo_save
|