let s:cache = {} let s:surround_specs = { \ '(': { 'finder': ['(\s*', '\s*)'], 'wrapper': ['( ', ' )'] }, \ ')': { 'finder': ['(', ')'], 'wrapper': ['(', ')'] }, \ '[': { 'finder': ['\[\s*', '\s*]'], 'wrapper': ['[ ', ' ]'] }, \ ']': { 'finder': ['\[', ']'], 'wrapper': ['[', ']'] }, \ '{': { 'finder': ['{\s*', '\s*}'], 'wrapper': ['{ ', ' }'] }, \ '}': { 'finder': ['{', '}'], 'wrapper': ['{', '}'] }, \ '<': { 'finder': ['<\s*', '\s*>'], 'wrapper': ['< ', ' >'] }, \ '>': { 'finder': ['<', '>'], 'wrapper': ['<', '>'] }, \ 'b': { 'finder': ['(\s*', '\s*)', '\[\s*', '\s*]', '{\s*', '\s*}'], 'wrapper': ['(', ')'] }, \ 'r': { 'finder': ['\[\s*', '\s*]'], 'wrapper': ['[', ']'] }, \ 'c': { 'finder': ['{\s*', '\s*}'], 'wrapper': ['{', '}'] }, \ 'a': { 'finder': ['<\s*', '\s*>'], 'wrapper': ['<', '>'] }, \ 'q': { 'finder': ['"', '"', '''', '''', '`', '`'], 'wrapper': ['''', ''''] }, \ 'f': { 'finder': ['\<\k\+(\s*', '\s*)'], 'wrapper': ['function_name(', ')', 'function_name'] }, \ 'F': { 'finder': ['\<\k\+(\s*', '\s*)'], 'wrapper': ['$CURSOR(', ')'], 'after_wrap': 'startinsert' }, \ 'g': { 'finder': ['\<\k\+<\s*', '\s*>'], 'wrapper': ['generics_name<', '>', 'generics_name'] }, \ 'G': { 'finder': ['\<\k\+<\s*', '\s*>'], 'wrapper': ['$CURSOR<', '>'], 'after_wrap': 'startinsert' }, \ 't': { 'finder': ['<\w\+[^>]*\/\@', ''], 'wrapper': ['', '', 'tag_name'] }, \ } function! s:invert(obj) abort let result = {} for [key, val] in items(a:obj) let result[val] = key endfor return result endfunction function! s:removestr(from_pos, to_pos) abort let from_pos = a:from_pos let to_pos = a:to_pos if a:from_pos[0] > a:to_pos[0] let [from_pos, to_pos] = [to_pos, from_pos] endif let before = strcharpart(getline(from_pos[0]), 0, from_pos[1] - 1) let after = strcharpart(getline(to_pos[0]), to_pos[1]) call setline(from_pos[0], before .. after) if from_pos[0] < to_pos[0] call deletebufline(bufnr(), from_pos[0] + 1, to_pos[0]) endif endfunction function! s:putstr(lnum, col, str) abort let line = getline(a:lnum) call setline(a:lnum, strcharpart(line, 0, a:col - 1) .. a:str .. strcharpart(line, a:col - 1)) endfunction function! s:get_wrapper() abort let cache = get(s:cache, 'add', []) if !empty(cache) return cache endif let char = nr2char(getchar()) if char !~ '\p' return [] endif if !has_key(s:surround_specs, char) let s:cache.add = [char, char] return s:cache.add endif let [open, close; replace_marks] = s:surround_specs[char]['wrapper'] for replace_mark in replace_marks let user_input = input('[surround] input ' .. replace_mark .. ': ') if user_input !~ '\p' return [] endif let open = substitute(open, replace_mark, user_input, '') let close = substitute(close, replace_mark, user_input, '') endfor if has_key(s:surround_specs[char], 'after_wrap') let s:after_wrap = s:surround_specs[char]['after_wrap'] endif let s:cache.add = [open, close] return s:cache.add endfunction function! s:get_finder() abort let cache = get(s:cache, 'find', []) if !empty(cache) return cache endif let char = nr2char(getchar()) if char !~ '\p' return [] endif if !has_key(s:surround_specs, char) let s:cache.find = [char, char] else let s:cache.find = s:surround_specs[char]['finder'] endif return s:cache.find endfunction function! mi#surround#operator(task) abort let s:cache = {} execute "set operatorfunc=function('mi#surround#" .. a:task .. "')" return 'g@' endfunction function! mi#surround#add(type = '') abort if a:type == '' set operatorfunc=function('mi#surround#add') return 'g@' endif let wrapper = s:get_wrapper() if empty(wrapper) return "\" endif let [open, close] = wrapper let [head_lnum, head_col] = getcharpos("'[")[1:2] let [tail_lnum, tail_col] = getcharpos("']")[1:2] let c_lnum = head_lnum let c_col = head_col " cursor set on start of close wrapper by default " cursor position can be specified by $CURSOR let CURSOR_MARK = '$CURSOR' let cursor_in_wrapper = stridx(open, CURSOR_MARK) if cursor_in_wrapper < 0 let c_lnum = tail_lnum let c_col = tail_col + 1 + strchars(open) let cursor_in_wrapper = stridx(close, CURSOR_MARK) endif let open = substitute(open, CURSOR_MARK, '', '') let close = substitute(close, CURSOR_MARK, '', '') let c_col += max([cursor_in_wrapper, 0]) call s:putstr(tail_lnum, tail_col + 1, close) call s:putstr(head_lnum, head_col, open) call setcursorcharpos(c_lnum, c_col) if exists('s:after_wrap') execute s:after_wrap unlet! s:after_wrap endif endfunction function! mi#surround#find_pair() abort " debug " let s:cache.find = [] let result = [] let finder = s:get_finder() while v:true if empty(finder) break endif let open = finder[0] let close = finder[1] let finder = finder[2:] if open ==# close let open_from = searchpos(open, 'nbW', line('w0')) let close_from = searchpos(close, 'nW', line('w$')) else let open_from = searchpairpos(open, '', close, 'nbW', '', line('w0')) let close_from = searchpairpos(open, '', close, 'nW', '', line('w$')) endif if open_from == [0, 0] || close_from == [0, 0] continue endif " use cursor() and searchpos() because searchpairpos() doesn't have 'e' flag let cursorpos = getpos('.')[1:2] call cursor(open_from[0], open_from[1]) let open_to = searchpos(open, 'ceW', line('w$')) call cursor(close_from[0], close_from[1]) let close_to = searchpos(close, 'ceW', line('w$')) call cursor(cursorpos[0], cursorpos[1]) " choose the last open_to if empty(result) || (result[1][0] <= open_to[0] && result[1][1] < open_to[1]) let result = [open_from, open_to, close_from, close_to] endif endwhile return result endfunction function! mi#surround#delete(type = '') abort if a:type == '' set operatorfunc=function('mi#surround#delete') return 'g@' endif let find_pair = mi#surround#find_pair() if empty(find_pair) return "\" endif echo find_pair let [open_from, open_to, close_from, close_to] = find_pair call s:removestr(close_from, close_to) call s:removestr(open_from, open_to) call cursor(open_from[0], open_from[1]) endfunction function! mi#surround#replace(type = '') abort if a:type == '' set operatorfunc=function('mi#surround#replace') return 'g@' endif let find_pair = mi#surround#find_pair() if empty(find_pair) return "\" endif let wrapper = s:get_wrapper() if empty(wrapper) return "\" endif let [open_from, open_to, close_from, close_to] = find_pair call s:removestr(close_from, close_to) call s:putstr(close_from[0], close_from[1], wrapper[1]) call s:removestr(open_from, open_to) call s:putstr(open_from[0], open_from[1], wrapper[0]) " TODO set cursor on new close pos call cursor(open_from[0], open_from[1]) endfunction