if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'julia') == -1 " Facilities for moving around Julia blocks (e.g. if/end, function/end etc.) " (AKA a collection of horrible hacks) let s:default_mappings = { \ "moveblock_n" : "]]", \ "moveblock_N" : "][", \ "moveblock_p" : "[[", \ "moveblock_P" : "[]", \ \ "move_n" : "]j", \ "move_N" : "]J", \ "move_p" : "[j", \ "move_P" : "[J", \ \ "select_a" : "aj", \ "select_i" : "ij", \ \ "whereami" : "", \ } function! s:getmapchars(function) if exists("g:julia_blocks_mappings") && has_key(g:julia_blocks_mappings, a:function) return s:escape(g:julia_blocks_mappings[a:function]) else return s:escape(s:default_mappings[a:function]) endif endfunction function! s:map_move(function, toend, backwards) let chars = s:getmapchars(a:function) if empty(chars) return endif let fn = "julia_blocks#" . a:function let lhs = " " . chars . " " let cnt = ":let b:jlblk_count=v:count1" exe "nnoremap " . lhs . cnt \ . " call " . fn . "()" exe "onoremap " . lhs . cnt \ . ":call julia_blocks#owrapper_move(v:operator, \"" . fn . "\", " . a:toend . ", " . a:backwards . ")" exe "xnoremap " . lhs . cnt \ . "gv:call julia_blocks#vwrapper_move(\"" . fn . "\")" let b:jlblk_mapped[a:function] = 1 endfunction function! julia_blocks#owrapper_move(oper, function, toend, backwards) let F = function(a:function) let save_redraw = &lazyredraw let save_select = &selection let restore_cmds = "\" \ . ":let &l:selection = \"" . save_select . "\"\" \ . ":let &l:lazyredraw = " . save_redraw . "\" \ . ":\" setlocal lazyredraw let start_pos = getpos('.') let b:jlblk_abort_calls_esc = 0 call F() let b:jlblk_abort_calls_esc = 1 let end_pos = getpos('.') if start_pos == end_pos call feedkeys(restore_cmds, 'n') endif let &l:selection = "inclusive" if a:backwards || !a:toend let &l:selection = "exclusive" endif if a:toend && a:backwards let end_pos[2] += 1 endif if s:compare_pos(start_pos, end_pos) > 0 let [start_pos, end_pos] = [end_pos, start_pos] endif call setpos("'<", start_pos) call setpos("'>", end_pos) " NOTE: the 'c' operator behaves differently, for mysterious reasons. We " simulate it with 'd' followed by 'i' instead call feedkeys("gv" . (a:oper == "c" ? "d" : a:oper) . restore_cmds . (a:oper == "c" ? "i" : ""), 'n') endfunction function! julia_blocks#vwrapper_move(function) let F = function(a:function) let s = getpos('.') let b1 = getpos("'<") let b2 = getpos("'>") let b = b1 == s ? b2 : b1 call setpos('.', s) let b:jlblk_abort_calls_esc = 0 call F() let b:jlblk_abort_calls_esc = 1 let e = getpos('.') call setpos('.', b) exe "normal " . visualmode() call setpos('.', e) endfunction function! s:unmap(function) if !get(b:jlblk_mapped, a:function, 0) return endif let chars = s:getmapchars(a:function) if empty(chars) " shouldn't happen return endif let mapids = a:function =~# "^move" ? ["n", "x", "o"] : \ a:function =~# "^select" ? ["x", "o"] : \ ["n"] let fn = "julia_blocks#" . a:function let cmd = " " . chars for m in mapids exe m . "unmap " . cmd endfor let b:jlblk_mapped[a:function] = 0 endfunction function! s:escape(chars) let c = a:chars let c = substitute(c, '|', '', 'g') return c endfunction function! s:map_select(function) let chars = s:getmapchars(a:function) if empty(chars) return endif let fn = "julia_blocks#" . a:function let lhs = " " . chars . " " let cnt = ":let b:jlblk_inwrapper=1:let b:jlblk_count=max([v:prevcount,1])" exe "onoremap " . lhs . "" . cnt \ . ":call julia_blocks#owrapper_select(v:operator, \"" . fn . "\")" exe "xnoremap " . lhs . cnt \ . ":call julia_blocks#vwrapper_select(\"" . fn . "\")" let b:jlblk_mapped[a:function] = 1 endfunction function! julia_blocks#owrapper_select(oper, function) ", toend, backwards) let F = function(a:function) let save_redraw = &lazyredraw let save_select = &selection let restore_cmds = "\" \ . ":let &l:selection = \"" . save_select . "\"\" \ . ":let &l:lazyredraw = " . save_redraw . "\" \ . ":\" setlocal lazyredraw let b:jlblk_abort_calls_esc = 0 let retF = F() let b:jlblk_abort_calls_esc = 1 if empty(retF) let b:jlblk_inwrapper = 0 call feedkeys(restore_cmds, 'n') return end let [start_pos, end_pos] = retF if start_pos == end_pos call feedkeys(restore_cmds, 'n') endif let &l:selection = "inclusive" call setpos("'<", start_pos) call setpos("'>", end_pos) let b:jlblk_inwrapper = 0 " NOTE: the 'c' operator behaves differently, for mysterious reasons. We " simulate it with 'd' followed by 'i' instead call feedkeys("gv" . (a:oper == "c" ? "d" : a:oper) . restore_cmds . (a:oper == "c" ? "i" : ""), 'n') endfunction function! julia_blocks#vwrapper_select(function) let F = function(a:function) let b:jlblk_abort_calls_esc = 0 let retF = F() let b:jlblk_abort_calls_esc = 1 if empty(retF) let b:jlblk_inwrapper = 0 return end let [start_pos, end_pos] = retF call setpos("'<", start_pos) call setpos("'>", end_pos) normal! gv let b:jlblk_inwrapper = 0 endfunction function! s:map_aux(function) let chars = s:getmapchars(a:function) if empty(chars) return endif let fn = "julia_blocks#" . a:function let lhs = " " . chars . " " exe "nnoremap " . lhs . ":echo " . fn . "()" let b:jlblk_mapped[a:function] = 1 endfunction let s:julia_blocks_functions = { \ "moveblock_N": [1, 0], \ "moveblock_n": [0, 0], \ "moveblock_p": [0, 1], \ "moveblock_P": [1, 1], \ \ "move_N": [1, 0], \ "move_n": [0, 0], \ "move_p": [0, 1], \ "move_P": [1, 1], \ \ "select_a": [], \ "select_i": [], \ \ "whereami": [], \ } function! julia_blocks#init_mappings() let b:jlblk_mapped = {} for f in keys(s:julia_blocks_functions) if f =~# "^move" let [te, bw] = s:julia_blocks_functions[f] call s:map_move(f, te, bw) elseif f =~# "^select" call s:map_select(f) else call s:map_aux(f) endif endfor call julia_blocks#select_reset() augroup JuliaBlocks au! au InsertEnter *.jl call julia_blocks#select_reset() au CursorMoved *.jl call s:cursor_moved() augroup END " we would need some autocmd event associated with exiting from " visual mode, but there isn't any, so we resort to this crude " hack " ACTUALLY this creates more problems than it solves, so the crude hack " is just disabled "vnoremap :call julia_blocks#select_reset() endfunction function! julia_blocks#remove_mappings() if exists("b:jlblk_mapped") for f in keys(s:julia_blocks_functions) call s:unmap(f) endfor endif unlet! b:jlblk_save_pos b:jlblk_view b:jlblk_count b:jlblk_abort_calls_esc unlet! b:jlblk_inwrapper b:jlblk_did_select b:jlblk_doing_select unlet! b:jlblk_last_start_pos b:jlblk_last_end_pos b:jlblk_last_mode augroup JuliaBlocks au! augroup END augroup! JuliaBlocks let md = maparg("", "x", 0, 1) if !empty(md) && md["buffer"] vunmap endif endfunction function! s:restore_view() "redraw! " would ensure correct behaviour, but is annoying let pos = getpos('.') if pos == b:jlblk_save_pos call winrestview(b:jlblk_view) return endif let oldtopline = b:jlblk_view["topline"] let newtopline = winsaveview()["topline"] let l = pos[1] if l >= oldtopline + &l:scrolloff && l <= oldtopline + winheight(0) - 1 - &l:scrolloff if newtopline > oldtopline exe ":normal! " . (newtopline - oldtopline) . "\" elseif newtopline < oldtopline exe ":normal! " . (oldtopline - newtopline) . "\" endif " these reduce the scrolling to the minimum (which is maybe not " standard ViM behaviour?) elseif newtopline < oldtopline && (l - newtopline - &l:scrolloff) > 0 exe ":normal! " . (l - newtopline - &l:scrolloff) . "\" elseif newtopline > oldtopline && (newtopline + &l:scrolloff - l) > 0 exe ":normal! " . (l - newtopline - &l:scrolloff) . "\" endif call setpos('.', pos) " make sure we didn't screw up " (since winsaveview may not be up to date) endfunction function! s:abort() call setpos('.', b:jlblk_save_pos) call s:restore_view() if get(b:, "jlblk_abort_calls_esc", 1) call feedkeys("\", 'n') endif return 0 endfunction function! s:set_mark_tick(...) " This could be a one-liner: " call setpos("''", b:jlblk_save_pos) " but we want to append to the jumplist, " which setpos doesn't do let p = getpos('.') call setpos('.', b:jlblk_save_pos) normal! m' call setpos('.', p) endfunction function! s:get_save_pos(...) if !exists("b:jlblk_save_pos") || (a:0 == 0) || (a:0 > 0 && a:1) let b:jlblk_save_pos = getpos('.') endif let b:jlblk_view = winsaveview() endfunction function! s:on_end() return getline('.')[col('.')-1] =~# '\k' && expand("") =~# b:julia_end_keywords endfunction function! s:on_begin() let [l,c] = [line('.'), col('.')] normal! ^ let patt = '\%<'.(c+1).'c\(' . b:julia_begin_keywordsm . '\)\%>'.(c-1).'c' let n = search(patt, 'Wnc', l) call cursor(l, c) return n > 0 endfunction function! s:matchit() let lkj = exists(":lockjumps") == 2 ? "lockjumps " : "" exe lkj . "normal %" endfunction function! s:move_before_begin() call search(b:julia_begin_keywordsm, 'Wbc') normal! h endfunction function! s:cycle_until_end() let pos = getpos('.') while !s:on_end() call s:matchit() let c = 0 if getpos('.') == pos || c > 1000 " shouldn't happen, but let's avoid infinite loops anyway return 0 endif let c += 1 endwhile return 1 endfunction function! s:moveto_block_delim(toend, backwards, ...) let pattern = a:toend ? b:julia_end_keywords : b:julia_begin_keywordsm let flags = a:backwards ? 'Wb' : 'W' let cnt = a:0 > 0 ? a:1 : b:jlblk_count if !a:toend && a:backwards && s:on_begin() call s:move_before_begin() endif let ret = 0 for c in range(cnt) if a:toend && a:backwards && s:on_end() normal! l normal! bh endif while 1 let searchret = search(pattern, flags) if !searchret return ret endif exe "let skip = " . b:match_skip if !skip let ret = 1 break endif endwhile endfor return ret endfunction function! s:compare_pos(pos1, pos2) if a:pos1[1] < a:pos2[1] return -1 elseif a:pos1[1] > a:pos2[1] return 1 elseif a:pos1[2] < a:pos2[2] return -1 elseif a:pos1[2] > a:pos2[2] return 1 else return 0 endif endfunction function! julia_blocks#move_N() call s:get_save_pos() let ret = s:moveto_block_delim(1, 0) if !ret return s:abort() endif normal! e call s:set_mark_tick() return 1 endfunction function! julia_blocks#move_n() call s:get_save_pos() let ret = s:moveto_block_delim(0, 0) if !ret return s:abort() endif call s:set_mark_tick() return 1 endfunction function! julia_blocks#move_p() call s:get_save_pos() let ret = s:moveto_block_delim(0, 1) if !ret return s:abort() endif call s:set_mark_tick() return 1 endfunction function! julia_blocks#move_P() call s:get_save_pos() let ret = s:moveto_block_delim(1, 1) if !ret return s:abort() endif normal! e call s:set_mark_tick() return 1 endfunction function! s:moveto_currentblock_end() let flags = 'W' if s:on_end() let flags .= 'c' " NOTE: using "normal! lb" fails at the end of the file (?!) normal! l normal! b endif let ret = searchpair(b:julia_begin_keywordsm, '', b:julia_end_keywords, flags, b:match_skip) if ret <= 0 return s:abort() endif normal! e return 1 endfunction function! julia_blocks#moveblock_N() call s:get_save_pos() let ret = 0 for c in range(b:jlblk_count) let last_seen_pos = getpos('.') if s:on_end() normal! hel let save_pos = getpos('.') let ret_start = s:moveto_block_delim(0, 0, 1) let start1_pos = ret_start ? getpos('.') : [0,0,0,0] call setpos('.', save_pos) if s:on_end() normal! h endif let ret_end = s:moveto_block_delim(1, 0, 1) let end1_pos = ret_end ? getpos('.') : [0,0,0,0] if ret_start && (!ret_end || s:compare_pos(start1_pos, end1_pos) < 0) call setpos('.', start1_pos) else call setpos('.', save_pos) endif endif let moveret = s:moveto_currentblock_end() if !moveret && c == 0 let moveret = s:moveto_block_delim(0, 0, 1) && s:cycle_until_end() if moveret normal! e endif endif if !moveret call setpos('.', last_seen_pos) break endif let ret = 1 endfor if !ret return s:abort() endif call s:set_mark_tick() return 1 endfunction function! julia_blocks#moveblock_n() call s:get_save_pos() let ret = 0 for c in range(b:jlblk_count) let last_seen_pos = getpos('.') call s:moveto_currentblock_end() if s:moveto_block_delim(0, 0, 1) let ret = 1 else call setpos('.', last_seen_pos) break endif endfor if !ret return s:abort() endif call s:set_mark_tick() return 1 endfunction function! julia_blocks#moveblock_p() call s:get_save_pos() let ret = 0 for c in range(b:jlblk_count) let last_seen_pos = getpos('.') if s:on_begin() call s:move_before_begin() if s:on_end() normal! l endif let save_pos = getpos('.') let ret_start = s:moveto_block_delim(0, 1, 1) let start1_pos = ret_start ? getpos('.') : [0,0,0,0] call setpos('.', save_pos) let ret_end = s:moveto_block_delim(1, 1, 1) let end1_pos = ret_end ? getpos('.') : [0,0,0,0] if ret_end && (!ret_start || s:compare_pos(start1_pos, end1_pos) < 0) call setpos('.', end1_pos) else call setpos('.', save_pos) endif endif let moveret = s:moveto_currentblock_end() if !moveret && c == 0 let moveret = s:moveto_block_delim(1, 1, 1) endif if !moveret call setpos('.', last_seen_pos) break endif call s:matchit() let ret = 1 endfor if !ret return s:abort() endif call s:set_mark_tick() call s:restore_view() return 1 endfunction function! julia_blocks#moveblock_P() call s:get_save_pos() let ret = 0 for c in range(b:jlblk_count) let last_seen_pos = getpos('.') call s:moveto_currentblock_end() if s:on_end() call s:matchit() endif if s:moveto_block_delim(1, 1, 1) " NOTE: normal! he does not work unless &whichwrap inlcudes h normal! h normal! e let ret = 1 else call setpos('.', last_seen_pos) endif endfor if !ret return s:abort() endif call s:set_mark_tick() call s:restore_view() return 1 endfunction function! julia_blocks#whereami() let b:jlblk_count = v:count1 let save_redraw = &lazyredraw setlocal lazyredraw let pos = getpos('.') let ret = julia_blocks#select_a('w') if empty(ret) call setpos('.', pos) let &l:lazyredraw = save_redraw return "" end let [start_pos, end_pos] = ret let m = getline(start_pos[1])[start_pos[2]-1:] " If cursor_moved was not forced from select_a, we force it now " (TODO: this is *really* ugly) if end_pos != pos call s:cursor_moved(1) endif call setpos('.', pos) call s:restore_view() let &l:lazyredraw = save_redraw return m endfunction " Block text objects function! s:find_block(current_mode) let flags = 'W' if b:jlblk_did_select call setpos('.', b:jlblk_last_start_pos) if !s:cycle_until_end() return s:abort() endif if !(a:current_mode[0] == 'a' && a:current_mode == b:jlblk_last_mode) let flags .= 'c' endif elseif s:on_end() let flags .= 'c' " NOTE: using "normal! lb" fails at the end of the file (?!) normal! l normal! b endif let searchret = searchpair(b:julia_begin_keywordsm, '', b:julia_end_keywords, flags, b:match_skip) if searchret <= 0 if !b:jlblk_did_select return s:abort() else call setpos('.', b:jlblk_last_end_pos) endif endif let end_pos = getpos('.') " Jump to match call s:matchit() let start_pos = getpos('.') let b:jlblk_last_start_pos = copy(start_pos) let b:jlblk_last_end_pos = copy(end_pos) return [start_pos, end_pos] endfunction function! s:repeated_find(ai_mode) let repeat = b:jlblk_count + (a:ai_mode == 'i' && v:count1 > 1 ? 1 : 0) for c in range(repeat) let current_mode = (c < repeat - 1 ? 'a' : a:ai_mode) let ret_find_block = s:find_block(current_mode) if empty(ret_find_block) return 0 endif let [start_pos, end_pos] = ret_find_block call setpos('.', end_pos) let b:jlblk_last_mode = current_mode if c < repeat - 1 let b:jlblk_doing_select = 0 let b:jlblk_did_select = 1 endif endfor return [start_pos, end_pos] endfunction function! julia_blocks#select_a(...) let mode_flag = a:0 > 0 ? a:1 : '' call s:get_save_pos(!b:jlblk_did_select) let current_pos = getpos('.') let ret_find_block = s:repeated_find('a' . mode_flag) if empty(ret_find_block) return 0 endif let [start_pos, end_pos] = ret_find_block call setpos('.', end_pos) normal! e let end_pos = getpos('.') let b:jlblk_doing_select = 1 " CursorMove is only triggered if end_pos " end_pos is different than the staring position; " so when starting from the 'd' in 'end' we need to " force it if current_pos == end_pos call s:cursor_moved(1) endif call s:set_mark_tick() return [start_pos, end_pos] endfunction function! julia_blocks#select_i() call s:get_save_pos(!b:jlblk_did_select) let current_pos = getpos('.') let ret_find_block = s:repeated_find('i') if empty(ret_find_block) return 0 endif let [start_pos, end_pos] = ret_find_block if end_pos[1] <= start_pos[1]+1 return s:abort() endif call setpos('.', end_pos) let b:jlblk_doing_select = 1 let start_pos[1] += 1 call setpos('.', start_pos) normal! ^ let start_pos = getpos('.') let end_pos[1] -= 1 let end_pos[2] = len(getline(end_pos[1])) " CursorMove is only triggered if end_pos " end_pos is different than the staring position; " so when starting from the 'd' in 'end' we need to " force it if current_pos == end_pos call s:cursor_moved(1) endif call s:set_mark_tick() return [start_pos, end_pos] endfunction function julia_blocks#select_reset() let b:jlblk_did_select = 0 let b:jlblk_doing_select = 0 let b:jlblk_inwrapper = 0 let b:jlblk_last_mode = "" endfunction function! s:cursor_moved(...) if b:jlblk_inwrapper && !(a:0 > 0 && a:1) return endif let b:jlblk_did_select = b:jlblk_doing_select let b:jlblk_doing_select = 0 endfunction endif