diff options
Diffstat (limited to 'autoload/julia_blocks.vim')
-rw-r--r-- | autoload/julia_blocks.vim | 798 |
1 files changed, 798 insertions, 0 deletions
diff --git a/autoload/julia_blocks.vim b/autoload/julia_blocks.vim new file mode 100644 index 00000000..ef7cf016 --- /dev/null +++ b/autoload/julia_blocks.vim @@ -0,0 +1,798 @@ +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 = "<buffer> <nowait> <silent> " . chars . " " + let cnt = ":<C-U>let b:jlblk_count=v:count1" + exe "nnoremap " . lhs . cnt + \ . " <Bar> call " . fn . "()<CR>" + exe "onoremap " . lhs . cnt + \ . "<CR><Esc>:call julia_blocks#owrapper_move(v:operator, \"" . fn . "\", " . a:toend . ", " . a:backwards . ")<CR>" + exe "xnoremap " . lhs . cnt + \ . "<CR>gv<Esc>:call julia_blocks#vwrapper_move(\"" . fn . "\")<CR>" + 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 = "\<Esc>" + \ . ":let &l:selection = \"" . save_select . "\"\<CR>" + \ . ":let &l:lazyredraw = " . save_redraw . "\<CR>" + \ . ":\<BS>" + + 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 = "<buffer> " . 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, '|', '<Bar>', '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 = "<buffer> <nowait> <silent> " . chars . " " + let cnt = ":<C-U>let b:jlblk_inwrapper=1<CR>:let b:jlblk_count=max([v:prevcount,1])<CR>" + exe "onoremap " . lhs . "<Esc>" . cnt + \ . ":call julia_blocks#owrapper_select(v:operator, \"" . fn . "\")<CR>" + exe "xnoremap " . lhs . cnt + \ . ":call julia_blocks#vwrapper_select(\"" . fn . "\")<CR>" + 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 = "\<Esc>" + \ . ":let &l:selection = \"" . save_select . "\"\<CR>" + \ . ":let &l:lazyredraw = " . save_redraw . "\<CR>" + \ . ":\<BS>" + + 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 = "<buffer> <nowait> <silent> " . chars . " " + exe "nnoremap " . lhs . ":<C-U>echo " . fn . "()<CR>" + 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 <buffer><silent><unique> <Esc> <Esc>:call julia_blocks#select_reset()<CR> +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("<Esc>", "x", 0, 1) + if !empty(md) && md["buffer"] + vunmap <buffer> <Esc> + 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) . "\<C-Y>" + elseif newtopline < oldtopline + exe ":normal! " . (oldtopline - newtopline) . "\<C-E>" + 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) . "\<C-E>" + elseif newtopline > oldtopline && (newtopline + &l:scrolloff - l) > 0 + exe ":normal! " . (l - newtopline - &l:scrolloff) . "\<C-E>" + 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("\<Esc>", '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("<cword>") =~# 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 |