diff options
Diffstat (limited to 'vim/bundle/slimv/plugin')
-rw-r--r-- | vim/bundle/slimv/plugin/paredit.vim | 1863 |
1 files changed, 1863 insertions, 0 deletions
diff --git a/vim/bundle/slimv/plugin/paredit.vim b/vim/bundle/slimv/plugin/paredit.vim new file mode 100644 index 0000000..befc118 --- /dev/null +++ b/vim/bundle/slimv/plugin/paredit.vim @@ -0,0 +1,1863 @@ +" paredit.vim: +" Paredit mode for Slimv +" Version: 0.9.13 +" Last Change: 15 Jan 2017 +" Maintainer: Tamas Kovacs <kovisoft at gmail dot com> +" License: This file is placed in the public domain. +" No warranty, express or implied. +" *** *** Use At-Your-Own-Risk! *** *** +" +" ===================================================================== +" +" Load Once: +if &cp || exists( 'g:paredit_loaded' ) + finish +endif + +let g:paredit_loaded = 1 + +" Needed to load filetype and indent plugins +filetype plugin on +filetype indent on + +" ===================================================================== +" Global variable definitions +" ===================================================================== + +" Paredit mode selector +if !exists( 'g:paredit_mode' ) + let g:paredit_mode = 1 +endif + +" Match delimiter this number of lines before and after cursor position +if !exists( 'g:paredit_matchlines' ) + let g:paredit_matchlines = 100 +endif + +" Use short keymaps, i.e. J instead of <Leader>J +if !exists( 'g:paredit_shortmaps' ) + let g:paredit_shortmaps = 0 +endif + +" Use smart jumping to the nearest paren, curly brace, or square bracket in +" clojure +if !exists( 'g:paredit_smartjump' ) + let g:paredit_smartjump = 0 +endif + +" Custom <Leader> for the Paredit plugin +if !exists( 'g:paredit_leader' ) + if exists( 'mapleader' ) + let g:paredit_leader = '<leader>' + else + let g:paredit_leader = ',' + endif +endif + +" Use 'Electric Return', i.e. add double newlines if enter pressed before a closing paren +if !exists( 'g:paredit_electric_return' ) + let g:paredit_electric_return = 1 +endif + +" ===================================================================== +" Other variable definitions +" ===================================================================== + +" Valid macro prefix characters +let s:any_macro_prefix = "'" . '\|`\|#\|@\|\~\|,\|\^' + +" Repeat count for some remapped edit functions (like 'd') +let s:repeat = 0 + +let s:yank_pos = [] + +" Filetypes with [] and {} pairs balanced as well +let s:fts_balancing_all_brackets = '.*\(clojure\|hy\|scheme\|racket\|shen\).*' + +" ===================================================================== +" General utility functions +" ===================================================================== +" Buffer specific initialization +function! PareditInitBuffer() + let b:paredit_init = 1 + " in case they are accidentally removed + " Also define regular expressions to identify special characters used by paredit + if &ft =~ s:fts_balancing_all_brackets + let b:any_matched_char = '(\|)\|\[\|\]\|{\|}\|\"' + let b:any_matched_pair = '()\|\[\]\|{}\|\"\"' + let b:any_opening_char = '(\|\[\|{' + let b:any_closing_char = ')\|\]\|}' + let b:any_openclose_char = '(\|)\|\[\|\]\|{\|}' + let b:any_wsopen_char = '\s\|(\|\[\|{' + let b:any_wsclose_char = '\s\|)\|\]\|}' + else + let b:any_matched_char = '(\|)\|\"' + let b:any_matched_pair = '()\|\"\"' + let b:any_opening_char = '(' + let b:any_closing_char = ')' + let b:any_openclose_char = '(\|)' + let b:any_wsopen_char = '\s\|(' + let b:any_wsclose_char = '\s\|)' + endif + + if g:paredit_mode + " Paredit mode is on: add buffer specific keybindings + inoremap <buffer> <expr> ( PareditInsertOpening('(',')') + inoremap <buffer> <silent> ) <C-R>=PareditInsertClosing('(',')')<CR> + inoremap <buffer> <expr> " PareditInsertQuotes() + inoremap <buffer> <expr> <BS> PareditBackspace(0) + inoremap <buffer> <expr> <C-h> PareditBackspace(0) + inoremap <buffer> <expr> <Del> PareditDel() + if &ft =~ s:fts_balancing_all_brackets && g:paredit_smartjump + noremap <buffer> <silent> ( :<C-U>call PareditSmartJumpOpening(0)<CR> + noremap <buffer> <silent> ) :<C-U>call PareditSmartJumpClosing(0)<CR> + vnoremap <buffer> <silent> ( <Esc>:<C-U>call PareditSmartJumpOpening(1)<CR> + vnoremap <buffer> <silent> ) <Esc>:<C-U>call PareditSmartJumpClosing(1)<CR> + else + noremap <buffer> <silent> ( :<C-U>call PareditFindOpening('(',')',0)<CR> + noremap <buffer> <silent> ) :<C-U>call PareditFindClosing('(',')',0)<CR> + vnoremap <buffer> <silent> ( <Esc>:<C-U>call PareditFindOpening('(',')',1)<CR> + vnoremap <buffer> <silent> ) <Esc>:<C-U>call PareditFindClosing('(',')',1)<CR> + endif + noremap <buffer> <silent> [[ :<C-U>call PareditFindDefunBck()<CR> + noremap <buffer> <silent> ]] :<C-U>call PareditFindDefunFwd()<CR> + + call RepeatableNNoRemap('x', ':<C-U>call PareditEraseFwd()') + nnoremap <buffer> <silent> <Del> :<C-U>call PareditEraseFwd()<CR> + call RepeatableNNoRemap('X', ':<C-U>call PareditEraseBck()') + nnoremap <buffer> <silent> s :<C-U>call PareditEraseFwd()<CR>i + call RepeatableNNoRemap('D', 'v$:<C-U>call PareditDelete(visualmode(),1)') + nnoremap <buffer> <silent> C v$:<C-U>call PareditChange(visualmode(),1)<CR> + nnoremap <buffer> <silent> d :<C-U>call PareditSetDelete(v:count)<CR>g@ + vnoremap <buffer> <silent> d :<C-U>call PareditDelete(visualmode(),1)<CR> + vnoremap <buffer> <silent> x :<C-U>call PareditDelete(visualmode(),1)<CR> + vnoremap <buffer> <silent> <Del> :<C-U>call PareditDelete(visualmode(),1)<CR> + nnoremap <buffer> <silent> c :set opfunc=PareditChange<CR>g@ + vnoremap <buffer> <silent> c :<C-U>call PareditChange(visualmode(),1)<CR> + call RepeatableNNoRemap('dd', ':<C-U>call PareditDeleteLines()') + nnoremap <buffer> <silent> cc :<C-U>call PareditChangeLines()<CR> + nnoremap <buffer> <silent> cw :<C-U>call PareditChangeSpec('cw',1)<CR> + nnoremap <buffer> <silent> cW :set opfunc=PareditChange<CR>g@E + nnoremap <buffer> <silent> cb :<C-U>call PareditChangeSpec('cb',0)<CR> + nnoremap <buffer> <silent> ciw :<C-U>call PareditChangeSpec('ciw',1)<CR> + nnoremap <buffer> <silent> caw :<C-U>call PareditChangeSpec('caw',1)<CR> + call RepeatableNNoRemap('p', ':<C-U>call PareditPut("p")') + call RepeatableNNoRemap('P', ':<C-U>call PareditPut("P")') + call RepeatableNNoRemap(g:paredit_leader . 'w(', ':<C-U>call PareditWrap("(",")")') + execute 'vnoremap <buffer> <silent> ' . g:paredit_leader.'w( :<C-U>call PareditWrapSelection("(",")")<CR>' + call RepeatableNNoRemap(g:paredit_leader . 'w"', ':<C-U>call PareditWrap('."'".'"'."','".'"'."')") + execute 'vnoremap <buffer> <silent> ' . g:paredit_leader.'w" :<C-U>call PareditWrapSelection('."'".'"'."','".'"'."')<CR>" + " Spliec s-expression killing backward/forward + execute 'nmap <buffer> <silent> ' . g:paredit_leader.'<Up> d[(:<C-U>call PareditSplice()<CR>' + execute 'nmap <buffer> <silent> ' . g:paredit_leader.'<Down> d])%:<C-U>call PareditSplice()<CR>' + call RepeatableNNoRemap(g:paredit_leader . 'I', ':<C-U>call PareditRaise()') + if &ft =~ s:fts_balancing_all_brackets + inoremap <buffer> <expr> [ PareditInsertOpening('[',']') + inoremap <buffer> <silent> ] <C-R>=PareditInsertClosing('[',']')<CR> + inoremap <buffer> <expr> { PareditInsertOpening('{','}') + inoremap <buffer> <silent> } <C-R>=PareditInsertClosing('{','}')<CR> + call RepeatableNNoRemap(g:paredit_leader . 'w[', ':<C-U>call PareditWrap("[","]")') + execute 'vnoremap <buffer> <silent> ' . g:paredit_leader.'w[ :<C-U>call PareditWrapSelection("[","]")<CR>' + call RepeatableNNoRemap(g:paredit_leader . 'w{', ':<C-U>call PareditWrap("{","}")') + execute 'vnoremap <buffer> <silent> ' . g:paredit_leader.'w{ :<C-U>call PareditWrapSelection("{","}")<CR>' + endif + + if g:paredit_shortmaps + " Shorter keymaps: old functionality of KEY is remapped to <Leader>KEY + call RepeatableNNoRemap('<', ':<C-U>call PareditMoveLeft()') + call RepeatableNNoRemap('>', ':<C-U>call PareditMoveRight()') + call RepeatableNNoRemap('O', ':<C-U>call PareditSplit()') + call RepeatableNNoRemap('J', ':<C-U>call PareditJoin()') + call RepeatableNNoRemap('W', ':<C-U>call PareditWrap("(",")")') + vnoremap <buffer> <silent> W :<C-U>call PareditWrapSelection('(',')')<CR> + call RepeatableNNoRemap('S', ':<C-U>call PareditSplice()') + execute 'nnoremap <buffer> <silent> ' . g:paredit_leader.'< :<C-U>normal! <<CR>' + execute 'nnoremap <buffer> <silent> ' . g:paredit_leader.'> :<C-U>normal! ><CR>' + execute 'nnoremap <buffer> <silent> ' . g:paredit_leader.'O :<C-U>normal! O<CR>' + execute 'nnoremap <buffer> <silent> ' . g:paredit_leader.'J :<C-U>normal! J<CR>' + execute 'nnoremap <buffer> <silent> ' . g:paredit_leader.'W :<C-U>normal! W<CR>' + execute 'vnoremap <buffer> <silent> ' . g:paredit_leader.'W :<C-U>normal! W<CR>' + execute 'nnoremap <buffer> <silent> ' . g:paredit_leader.'S :<C-U>normal! S<CR>' + else + " Longer keymaps with <Leader> prefix + nnoremap <buffer> <silent> S V:<C-U>call PareditChange(visualmode(),1)<CR> + call RepeatableNNoRemap(g:paredit_leader . '<', ':<C-U>call PareditMoveLeft()') + call RepeatableNNoRemap(g:paredit_leader . '>', ':<C-U>call PareditMoveRight()') + call RepeatableNNoRemap(g:paredit_leader . 'O', ':<C-U>call PareditSplit()') + call RepeatableNNoRemap(g:paredit_leader . 'J', ':<C-U>call PareditJoin()') + call RepeatableNNoRemap(g:paredit_leader . 'W', ':<C-U>call PareditWrap("(",")")') + execute 'vnoremap <buffer> <silent> ' . g:paredit_leader.'W :<C-U>call PareditWrapSelection("(",")")<CR>' + call RepeatableNNoRemap(g:paredit_leader . 'S', ':<C-U>call PareditSplice()') + endif + + if g:paredit_electric_return && mapcheck( "<CR>", "i" ) == "" + " Do not override any possible mapping for <Enter> + inoremap <buffer> <expr> <CR> PareditEnter() + endif + else + " Paredit mode is off: remove keybindings + silent! iunmap <buffer> ( + silent! iunmap <buffer> ) + silent! iunmap <buffer> " + silent! iunmap <buffer> <BS> + silent! iunmap <buffer> <C-h> + silent! iunmap <buffer> <Del> + silent! unmap <buffer> ( + silent! unmap <buffer> ) + silent! unmap <buffer> [[ + silent! unmap <buffer> ]] + silent! unmap <buffer> x + silent! unmap <buffer> <Del> + silent! unmap <buffer> X + silent! unmap <buffer> s + silent! unmap <buffer> D + silent! unmap <buffer> C + silent! unmap <buffer> d + silent! unmap <buffer> c + silent! unmap <buffer> dd + silent! unmap <buffer> cc + silent! unmap <buffer> cw + silent! unmap <buffer> cW + silent! unmap <buffer> cb + silent! unmap <buffer> ciw + silent! unmap <buffer> caw + if &ft =~ s:fts_balancing_all_brackets + silent! iunmap <buffer> [ + silent! iunmap <buffer> ] + silent! iunmap <buffer> { + silent! iunmap <buffer> } + endif + if mapcheck( "<CR>", "i" ) == "PareditEnter()" + " Remove only if we have added this mapping + silent! iunmap <buffer> <CR> + endif + endif +endfunction + +" Run the command normally but append a call to repeat#set afterwards +function! RepeatableMap(map_type, keys, command) + let escaped_keys = substitute(a:keys, '["<]', '\\\0', "g") + execute a:map_type . ' <silent> <buffer> ' . + \ a:keys . ' ' . a:command . + \ '\|silent! call repeat#set("' . escaped_keys . '")<CR>' +endfunction + +function! RepeatableNMap(keys, command) + call RepeatableMap('nmap', a:keys, a:command) +endfunction + +function! RepeatableNNoRemap(keys, command) + call RepeatableMap('nnoremap', a:keys, a:command) +endfunction + +" Include all prefix and special characters in 'iskeyword' +function! s:SetKeyword() + let old_value = &iskeyword + if &ft =~ s:fts_balancing_all_brackets + setlocal iskeyword+=+,-,*,/,%,<,=,>,:,$,?,!,@-@,94,~,#,\|,& + else + setlocal iskeyword+=+,-,*,/,%,<,=,>,:,$,?,!,@-@,94,~,#,\|,&,.,{,},[,] + endif + return old_value +endfunction + +" General Paredit operator function +function! PareditOpfunc( func, type, visualmode ) + let sel_save = &selection + let ve_save = &virtualedit + set virtualedit=all + let regname = v:register + let save_0 = getreg( '0' ) + + if a:visualmode " Invoked from Visual mode, use '< and '> marks. + silent exe "normal! `<" . a:type . "`>" + elseif a:type == 'line' + let &selection = "inclusive" + silent exe "normal! '[V']" + elseif a:type == 'block' + let &selection = "inclusive" + silent exe "normal! `[\<C-V>`]" + else + let &selection = "inclusive" + silent exe "normal! `[v`]" + endif + + if !g:paredit_mode || (a:visualmode && (a:type == 'block' || a:type == "\<C-V>")) + " Block mode is too difficult to handle at the moment + silent exe "normal! d" + let putreg = getreg( '"' ) + else + silent exe "normal! y" + let putreg = getreg( '"' ) + if a:func == 'd' + " Register "0 is corrupted by the above 'y' command + call setreg( '0', save_0 ) + elseif a:visualmode && &selection == "inclusive" && len(getline("'>")) < col("'>") && len(putreg) > 0 + " Remove extra space added at the end of line when selection=inclusive, all, or onemore + let putreg = putreg[:-2] + endif + + " Find and keep unbalanced matched characters in the region + let instring = s:InsideString( line("'<"), col("'<") ) + if col("'>") > 1 && !s:InsideString( line("'<"), col("'<") - 1 ) + " We are at the beginning of the string + let instring = 0 + endif + let matched = s:GetMatchedChars( putreg, instring, s:InsideComment( line("'<"), col("'<") ) ) + let matched = s:Unbalanced( matched ) + let matched = substitute( matched, '\s', '', 'g' ) + + if matched == '' + if a:func == 'c' && (a:type == 'v' || a:type == 'V' || a:type == 'char') + silent exe "normal! gvc" + else + silent exe "normal! gvd" + endif + else + silent exe "normal! gvc" . matched + silent exe "normal! l" + let offs = len(matched) + if matched[0] =~ b:any_closing_char + let offs = offs + 1 + endif + if a:func == 'd' + let offs = offs - 1 + elseif instring && matched == '"' + " Keep cursor inside the double quotes + let offs = offs + 1 + endif + if offs > 0 + silent exe "normal! " . string(offs) . "h" + endif + endif + endif + + let &selection = sel_save + let &virtualedit = ve_save + if a:func == 'd' && regname == '"' + " Do not currupt the '"' register and hence the "0 register + call setreg( '1', putreg ) + else + call setreg( regname, putreg ) + endif +endfunction + +" Set delete mode also saving repeat count +function! PareditSetDelete( count ) + let s:repeat = a:count + set opfunc=PareditDelete +endfunction + +" General delete operator handling +function! PareditDelete( type, ... ) + call PareditOpfunc( 'd', a:type, a:0 ) + if s:repeat > 1 + call feedkeys( (s:repeat-1) . "." ) + endif + let s:repeat = 0 +endfunction + +" General change operator handling +function! PareditChange( type, ... ) + let ve_save = &virtualedit + set virtualedit=all + call PareditOpfunc( 'c', a:type, a:0 ) + if len(getline('.')) == 0 + let v:lnum = line('.') + let expr = &indentexpr + if expr == '' + " No special 'indentexpr', call default lisp indent + let expr = 'lispindent(v:lnum)' + endif + execute "call setline( v:lnum, repeat( ' ', " . expr . " ) )" + call cursor(v:lnum, len(getline(v:lnum))+1) + else + normal! l + endif + startinsert + let &virtualedit = ve_save +endfunction + +" Delete v:count number of lines +function! PareditDeleteLines() + if v:count > 1 + silent exe "normal! V" . (v:count-1) . "j\<Esc>" + else + silent exe "normal! V\<Esc>" + endif + call PareditDelete(visualmode(),1) +endfunction + +" Change v:count number of lines +function! PareditChangeLines() + if v:count > 1 + silent exe "normal! V" . (v:count-1) . "j\<Esc>" + else + silent exe "normal! V\<Esc>" + endif + call PareditChange(visualmode(),1) +endfunction + +" Handle special change command, e.g. cw +" Check if we may revert to its original Vim function +" This way '.' can be used to repeat the command +function! PareditChangeSpec( cmd, dir ) + let line = getline( '.' ) + if a:dir == 0 + " Changing backwards + let c = col( '.' ) - 2 + while c >= 0 && line[c] =~ b:any_matched_char + " Shouldn't delete a matched character, just move left + call feedkeys( 'h', 'n') + let c = c - 1 + endwhile + if c < 0 && line[0] =~ b:any_matched_char + " Can't help, still on matched character, insert instead + call feedkeys( 'i', 'n') + return + endif + else + " Changing forward + let c = col( '.' ) - 1 + while c < len(line) && line[c] =~ b:any_matched_char + " Shouldn't delete a matched character, just move right + call feedkeys( 'l', 'n') + let c = c + 1 + endwhile + if c == len(line) + " Can't help, still on matched character, append instead + call feedkeys( 'a', 'n') + return + endif + endif + + " Safe to use Vim's built-in change function + call feedkeys( a:cmd, 'n') +endfunction + +" Paste text from put register in a balanced way +function! PareditPut( cmd ) + let regname = v:register + let reg_save = getreg( regname ) + let putreg = reg_save + + " Find unpaired matched characters by eliminating paired ones + let matched = s:GetMatchedChars( putreg, s:InsideString(), s:InsideComment() ) + let matched = s:Unbalanced( matched ) + + if matched !~ '\S\+' + " Register contents is balanced, perform default put function + silent exe "normal! " . (v:count>1 ? v:count : '') . (regname=='"' ? '' : '"'.regname) . a:cmd + return + endif + + " Replace all unpaired matched characters with a space in order to keep balance + let i = 0 + while i < len( putreg ) + if matched[i] !~ '\s' + let putreg = strpart( putreg, 0, i ) . ' ' . strpart( putreg, i+1 ) + endif + let i = i + 1 + endwhile + + " Store balanced text in put register and call the appropriate put command + call setreg( regname, putreg ) + silent exe "normal! " . (v:count>1 ? v:count : '') . (regname=='"' ? '' : '"'.regname) . a:cmd + call setreg( regname, reg_save ) +endfunction + +" Toggle paredit mode +function! PareditToggle() + " Don't disable paredit if it was not initialized yet for the current buffer + if exists( 'b:paredit_init') || g:paredit_mode == 0 + let g:paredit_mode = 1 - g:paredit_mode + endif + echo g:paredit_mode ? 'Paredit mode on' : 'Paredit mode off' + call PareditInitBuffer() +endfunction + +" Does the current syntax item match the given regular expression? +function! s:SynIDMatch( regexp, line, col, match_eol ) + let col = a:col + if a:match_eol && col > len( getline( a:line ) ) + let col = col - 1 + endif + return synIDattr( synID( a:line, col, 0), 'name' ) =~ a:regexp +endfunction + +" Expression used to check whether we should skip a match with searchpair() +function! s:SkipExpr() + let l = line('.') + let c = col('.') + if synIDattr(synID(l, c, 0), "name") =~ "[Ss]tring\\|[Cc]omment\\|[Ss]pecial\\|clojureRegexp\\|clojurePattern" + " Skip parens inside strings, comments, special elements + return 1 + endif + if getline(l)[c-2] == "\\" && getline(l)[c-3] != "\\" + " Skip parens escaped by '\' + return 1 + endif + return 0 +endfunction + +" Is the current cursor position inside a comment? +function! s:InsideComment( ... ) + let l = a:0 ? a:1 : line('.') + let c = a:0 ? a:2 : col('.') + if &syntax == '' + " No help from syntax engine, + " remove strings and search for ';' up to the cursor position + let line = strpart( getline(l), 0, c - 1 ) + let line = substitute( line, '\\"', '', 'g' ) + let line = substitute( line, '"[^"]*"', '', 'g' ) + return match( line, ';' ) >= 0 + endif + if s:SynIDMatch( 'clojureComment', l, c, 1 ) + if strpart( getline(l), c-1, 2 ) == '#_' || strpart( getline(l), c-2, 2 ) == '#_' + " This is a commented out clojure form of type #_(...), treat it as regular form + return 0 + endif + endif + return s:SynIDMatch( '[Cc]omment', l, c, 1 ) +endfunction + +" Is the current cursor position inside a string? +function! s:InsideString( ... ) + let l = a:0 ? a:1 : line('.') + let c = a:0 ? a:2 : col('.') + if &syntax == '' + " No help from syntax engine, + " count quote characters up to the cursor position + let line = strpart( getline(l), 0, c - 1 ) + let line = substitute( line, '\\"', '', 'g' ) + let quotes = substitute( line, '[^"]', '', 'g' ) + return len(quotes) % 2 + endif + " VimClojure and vim-clojure-static define special syntax for regexps + return s:SynIDMatch( '[Ss]tring\|clojureRegexp\|clojurePattern', l, c, 0 ) +endfunction + +" Is this a Slimv or VimClojure REPL buffer? +function! s:IsReplBuffer() + if exists( 'b:slimv_repl_buffer' ) || exists( 'b:vimclojure_repl' ) + return 1 + else + return 0 + endif +endfunction + +" Get Slimv or VimClojure REPL buffer last command prompt position +" Return [0, 0] if this is not the REPL buffer +function! s:GetReplPromptPos() + if !s:IsReplBuffer() + return [0, 0] + endif + if exists( 'b:vimclojure_repl') + let cur_pos = getpos( '.' ) + call cursor( line( '$' ), 1) + call cursor( line( '.' ), col( '$') ) + call search( b:vimclojure_namespace . '=>', 'bcW' ) + let target_pos = getpos( '.' )[1:2] + call setpos( '.', cur_pos ) + return target_pos + else + return [ b:repl_prompt_line, b:repl_prompt_col ] + endif +endfunction + +" Is the current top level form balanced, i.e all opening delimiters +" have a matching closing delimiter +function! s:IsBalanced() + let l = line( '.' ) + let c = col( '.' ) + let line = getline( '.' ) + if c > len(line) + let c = len(line) + endif + let matchb = max( [l-g:paredit_matchlines, 1] ) + let matchf = min( [l+g:paredit_matchlines, line('$')] ) + let [prompt, cp] = s:GetReplPromptPos() + if s:IsReplBuffer() && l >= prompt && matchb < prompt + " Do not go before the last command prompt in the REPL buffer + let matchb = prompt + endif + if line[c-1] == '(' + let p1 = searchpair( '(', '', ')', 'brnmWc', 's:SkipExpr()', matchb ) + let p2 = searchpair( '(', '', ')', 'rnmW' , 's:SkipExpr()', matchf ) + elseif line[c-1] == ')' + let p1 = searchpair( '(', '', ')', 'brnmW' , 's:SkipExpr()', matchb ) + let p2 = searchpair( '(', '', ')', 'rnmWc', 's:SkipExpr()', matchf ) + else + let p1 = searchpair( '(', '', ')', 'brnmW' , 's:SkipExpr()', matchb ) + let p2 = searchpair( '(', '', ')', 'rnmW' , 's:SkipExpr()', matchf ) + endif + if p1 != p2 + " Number of opening and closing parens differ + return 0 + endif + + if &ft =~ s:fts_balancing_all_brackets + if line[c-1] == '[' + let b1 = searchpair( '\[', '', '\]', 'brnmWc', 's:SkipExpr()', matchb ) + let b2 = searchpair( '\[', '', '\]', 'rnmW' , 's:SkipExpr()', matchf ) + elseif line[c-1] == ']' + let b1 = searchpair( '\[', '', '\]', 'brnmW' , 's:SkipExpr()', matchb ) + let b2 = searchpair( '\[', '', '\]', 'rnmWc', 's:SkipExpr()', matchf ) + else + let b1 = searchpair( '\[', '', '\]', 'brnmW' , 's:SkipExpr()', matchb ) + let b2 = searchpair( '\[', '', '\]', 'rnmW' , 's:SkipExpr()', matchf ) + endif + if b1 != b2 + " Number of opening and closing brackets differ + return 0 + endif + if line[c-1] == '{' + let b1 = searchpair( '{', '', '}', 'brnmWc', 's:SkipExpr()', matchb ) + let b2 = searchpair( '{', '', '}', 'rnmW' , 's:SkipExpr()', matchf ) + elseif line[c-1] == '}' + let b1 = searchpair( '{', '', '}', 'brnmW' , 's:SkipExpr()', matchb ) + let b2 = searchpair( '{', '', '}', 'rnmWc', 's:SkipExpr()', matchf ) + else + let b1 = searchpair( '{', '', '}', 'brnmW' , 's:SkipExpr()', matchb ) + let b2 = searchpair( '{', '', '}', 'rnmW' , 's:SkipExpr()', matchf ) + endif + if b1 != b2 + " Number of opening and closing curly braces differ + return 0 + endif + endif + return 1 +endfunction + +" Filter out all non-matched characters from the region +function! s:GetMatchedChars( lines, start_in_string, start_in_comment ) + let inside_string = a:start_in_string + let inside_comment = a:start_in_comment + let matched = repeat( ' ', len( a:lines ) ) + let i = 0 + while i < len( a:lines ) + if inside_string + " We are inside a string, skip parens, wait for closing '"' + " but skip escaped \" characters + if a:lines[i] == '"' && a:lines[i-1] != '\' + let matched = strpart( matched, 0, i ) . a:lines[i] . strpart( matched, i+1 ) + let inside_string = 0 + endif + elseif inside_comment + " We are inside a comment, skip parens, wait for end of line + if a:lines[i] == "\n" + let inside_comment = 0 + endif + elseif i > 0 && a:lines[i-1] == '\' && (i < 2 || a:lines[i-2] != '\') + " This is an escaped character, ignore it + else + " We are outside of strings and comments, now we shall count parens + if a:lines[i] == '"' + let matched = strpart( matched, 0, i ) . a:lines[i] . strpart( matched, i+1 ) + let inside_string = 1 + endif + if a:lines[i] == ';' + let inside_comment = 1 + endif + if a:lines[i] =~ b:any_openclose_char + let matched = strpart( matched, 0, i ) . a:lines[i] . strpart( matched, i+1 ) + endif + endif + let i = i + 1 + endwhile + return matched +endfunction + +" Find unpaired matched characters by eliminating paired ones +function! s:Unbalanced( matched ) + let matched = a:matched + let tmp = matched + while 1 + let matched = tmp + let tmp = substitute( tmp, '(\(\s*\))', ' \1 ', 'g') + if &ft =~ s:fts_balancing_all_brackets + let tmp = substitute( tmp, '\[\(\s*\)\]', ' \1 ', 'g') + let tmp = substitute( tmp, '{\(\s*\)}', ' \1 ', 'g') + endif + let tmp = substitute( tmp, '"\(\s*\)"', ' \1 ', 'g') + if tmp == matched + " All paired chars eliminated + let tmp = substitute( tmp, ')\(\s*\)(', ' \1 ', 'g') + if &ft =~ s:fts_balancing_all_brackets + let tmp = substitute( tmp, '\]\(\s*\)\[', ' \1 ', 'g') + let tmp = substitute( tmp, '}\(\s*\){', ' \1 ', 'g') + endif + if tmp == matched + " Also no more inverse pairs can be eliminated + break + endif + endif + endwhile + return matched +endfunction + +" Find opening matched character +function! PareditFindOpening( open, close, select ) + let open = escape( a:open , '[]' ) + let close = escape( a:close, '[]' ) + call searchpair( open, '', close, 'bW', 's:SkipExpr()' ) + if a:select + call searchpair( open, '', close, 'W', 's:SkipExpr()' ) + let save_ve = &ve + set ve=all + normal! lvh + let &ve = save_ve + call searchpair( open, '', close, 'bW', 's:SkipExpr()' ) + if &selection == 'inclusive' + " Trim last character from the selection, it will be included anyway + normal! oho + endif + endif +endfunction + +" Find closing matched character +function! PareditFindClosing( open, close, select ) + let open = escape( a:open , '[]' ) + let close = escape( a:close, '[]' ) + if a:select + let line = getline( '.' ) + if line[col('.')-1] != a:open + normal! h + endif + call searchpair( open, '', close, 'W', 's:SkipExpr()' ) + call searchpair( open, '', close, 'bW', 's:SkipExpr()' ) + normal! v + call searchpair( open, '', close, 'W', 's:SkipExpr()' ) + if &selection != 'inclusive' + normal! l + endif + else + call searchpair( open, '', close, 'W', 's:SkipExpr()' ) + endif +endfunction + +" Returns the nearest opening character to the cursor +" Used for smart jumping in Clojure +function! PareditSmartJumpOpening( select ) + let [paren_line, paren_col] = searchpairpos('(', '', ')', 'bWn', 's:SkipExpr()') + let [bracket_line, bracket_col] = searchpairpos('\[', '', '\]', 'bWn', 's:SkipExpr()') + let [brace_line, brace_col] = searchpairpos('{', '', '}', 'bWn', 's:SkipExpr()') + let paren_score = paren_line * 10000 + paren_col + let bracket_score = bracket_line * 10000 + bracket_col + let brace_score = brace_line * 10000 + brace_col + if (brace_score > paren_score || paren_score == 0) && (brace_score > bracket_score || bracket_score == 0) && brace_score != 0 + call PareditFindOpening('{','}', a:select) + elseif (bracket_score > paren_score || paren_score == 0) && bracket_score != 0 + call PareditFindOpening('[',']', a:select) + else + call PareditFindOpening('(',')', a:select) + endif +endfunction + +" Returns the nearest opening character to the cursor +" Used for smart jumping in Clojure +function! PareditSmartJumpClosing( select ) + let [paren_line, paren_col] = searchpairpos('(', '', ')', 'Wn', 's:SkipExpr()') + let [bracket_line, bracket_col] = searchpairpos('\[', '', '\]', 'Wn', 's:SkipExpr()') + let [brace_line, brace_col] = searchpairpos('{', '', '}', 'Wn', 's:SkipExpr()') + let paren_score = paren_line * 10000 + paren_col + let bracket_score = bracket_line * 10000 + bracket_col + let brace_score = brace_line * 10000 + brace_col + if (brace_score < paren_score || paren_score == 0) && (brace_score < bracket_score || bracket_score == 0) && brace_score != 0 + call PareditFindClosing('{','}', a:select) + elseif (bracket_score < paren_score || paren_score == 0) && bracket_score != 0 + call PareditFindClosing('[',']', a:select) + else + call PareditFindClosing('(',')', a:select) + endif +endfunction + +" Find defun start backwards +function! PareditFindDefunBck() + let l = line( '.' ) + let matchb = max( [l-g:paredit_matchlines, 1] ) + let oldpos = getpos( '.' ) + let newpos = searchpairpos( '(', '', ')', 'brW', 's:SkipExpr()', matchb ) + if newpos[0] == 0 + " Already standing on a defun, find the end of the previous one + let newpos = searchpos( ')', 'bW' ) + while newpos[0] != 0 && (s:InsideComment() || s:InsideString()) + let newpos = searchpos( ')', 'W' ) + endwhile + if newpos[0] == 0 + " No ')' found, don't move cursor + call setpos( '.', oldpos ) + else + " Find opening paren + let pairpos = searchpairpos( '(', '', ')', 'brW', 's:SkipExpr()', matchb ) + if pairpos[0] == 0 + " ')' has no matching pair + call setpos( '.', oldpos ) + endif + endif + endif +endfunction + +" Find defun start forward +function! PareditFindDefunFwd() + let l = line( '.' ) + let matchf = min( [l+g:paredit_matchlines, line('$')] ) + let oldpos = getpos( '.' ) + call searchpair( '(', '', ')', 'brW', 's:SkipExpr()', matchf ) + normal! % + let newpos = searchpos( '(', 'W' ) + while newpos[0] != 0 && (s:InsideComment() || s:InsideString()) + let newpos = searchpos( '(', 'W' ) + endwhile + if newpos[0] == 0 + " No '(' found, don't move cursor + call setpos( '.', oldpos ) + endif +endfunction + +" Insert opening type of a paired character, like ( or [. +function! PareditInsertOpening( open, close ) + if !g:paredit_mode || s:InsideComment() || s:InsideString() || !s:IsBalanced() + return a:open + endif + let line = getline( '.' ) + let pos = col( '.' ) - 1 + if pos > 0 && line[pos-1] == '\' && (pos < 2 || line[pos-2] != '\') + " About to enter a \( or \[ + return a:open + elseif line[pos] !~ b:any_wsclose_char && pos < len( line ) + " Add a space after if needed + let retval = a:open . a:close . " \<Left>\<Left>" + else + let retval = a:open . a:close . "\<Left>" + endif + if pos > 0 && line[pos-1] !~ b:any_wsopen_char && line[pos-1] !~ s:any_macro_prefix + " Add a space before if needed + let retval = " " . retval + endif + return retval +endfunction + +" Re-gather electric returns up +function! s:ReGatherUp() + if g:paredit_electric_return && getline('.') =~ '^\s*)' + " Re-gather electric returns in the current line for ')' + normal! k + while getline( line('.') ) =~ '^\s*$' + " Delete all empty lines + normal! ddk + endwhile + normal! Jl + elseif g:paredit_electric_return && getline('.') =~ '^\s*\(\]\|}\)' && &ft =~ s:fts_balancing_all_brackets + " Re-gather electric returns in the current line for ']' and '}' + normal! k + while getline( line('.') ) =~ '^\s*$' + " Delete all empty lines + normal! ddk + endwhile + call setline( line('.'), substitute( line, '\s*$', '', 'g' ) ) + normal! Jxl + endif + " Already have the desired character, move right + normal! l +endfunction + +" Insert closing type of a paired character, like ) or ]. +function! PareditInsertClosing( open, close ) + let retval = "" + if pumvisible() + let retval = "\<C-Y>" + endif + let save_ve = &ve + set ve=all + let line = getline( '.' ) + let pos = col( '.' ) - 1 + if !g:paredit_mode || s:InsideComment() || s:InsideString() || !s:IsBalanced() + call setline( line('.'), line[0 : pos-1] . a:close . line[pos : -1] ) + normal! l + let &ve = save_ve + return retval + endif + if pos > 0 && line[pos-1] == '\' && (pos < 2 || line[pos-2] != '\') + " About to enter a \) or \] + call setline( line('.'), line[0 : pos-1] . a:close . line[pos : -1] ) + normal! l + let &ve = save_ve + return retval + elseif line[pos] == a:close + call s:ReGatherUp() + let &ve = save_ve + return retval + endif + let open = escape( a:open , '[]' ) + let close = escape( a:close, '[]' ) + let newpos = searchpairpos( open, '', close, 'nW', 's:SkipExpr()' ) + if g:paredit_electric_return && newpos[0] > line('.') + " Closing paren is in a line below, check if there are electric returns to re-gather + while getline('.') =~ '^\s*$' + " Delete all empty lines above the cursor + normal! ddk + endwhile + let oldpos = getpos( '.' ) + normal! j + while getline('.') =~ '^\s*$' + " Delete all empty lines below the cursor + normal! dd + endwhile + let nextline = substitute( getline('.'), '\s', '', 'g' ) + call setpos( '.', oldpos ) + if len(nextline) > 0 && nextline[0] == ')' + " Re-gather electric returns in the line of the closing ')' + call setline( line('.'), substitute( getline('.'), '\s*$', '', 'g' ) ) + normal! Jl + let &ve = save_ve + return retval + endif + if len(nextline) > 0 && nextline[0] =~ '\]\|}' && &ft =~ s:fts_balancing_all_brackets + " Re-gather electric returns in the line of the closing ']' or '}' + call setline( line('.'), substitute( line, '\s*$', '', 'g' ) ) + normal! Jxl + let &ve = save_ve + return retval + endif + elseif g:paredit_electric_return && line =~ '^\s*)' + " Re-gather electric returns in the current line + call s:ReGatherUp() + let &ve = save_ve + return retval + endif + if searchpair( open, '', close, 'W', 's:SkipExpr()' ) > 0 + normal! l + endif + "TODO: indent after going to closing character + let &ve = save_ve + return retval +endfunction + +" Insert an (opening or closing) double quote +function! PareditInsertQuotes() + if !g:paredit_mode || s:InsideComment() + return '"' + endif + let line = getline( '.' ) + let pos = col( '.' ) - 1 + if pos > 0 && line[pos-1] == '\' && (pos < 2 || line[pos-2] != '\') + " About to enter a \" + return '"' + elseif s:InsideString() + "TODO: skip comments in search(...) + if line[pos] == '"' + " Standing on a ", just move to the right + return "\<Right>" + elseif search('[^\\]"\|^"', 'nW') == 0 + " We don't have any closing ", insert one + return '"' + else + " Move to the closing " + return "\<C-O>:call search('" . '[^\\]"\|^"' . "','eW')\<CR>\<Right>" + endif + else + " Outside of string: insert a pair of "" + return '""' . "\<Left>" + endif +endfunction + +" Handle <Enter> keypress, insert electric return if applicable +function! PareditEnter() + if pumvisible() + " Pressing <CR> in a pop up selects entry. + return "\<C-Y>" + else + let line = getline( '.' ) + let pos = col( '.' ) - 1 + if g:paredit_electric_return && pos > 0 && line[pos] =~ b:any_closing_char && !s:InsideString() && s:IsBalanced() + " Electric Return + return "\<CR>\<CR>\<Up>" + else + " Regular Return + return "\<CR>" + endif + endif +endfunction + +" Handle <BS> keypress +function! PareditBackspace( repl_mode ) + let [lp, cp] = s:GetReplPromptPos() + if a:repl_mode && line( "." ) == lp && col( "." ) <= cp + " No BS allowed before the previous EOF mark in the REPL + " i.e. don't delete Lisp prompt + return "" + endif + + if !g:paredit_mode || s:InsideComment() + return "\<BS>" + endif + + let line = getline( '.' ) + let pos = col( '.' ) - 1 + + if pos == 0 + " We are at the beginning of the line + return "\<BS>" + elseif s:InsideString() && line[pos-1] =~ b:any_openclose_char + " Deleting a paren inside a string + return "\<BS>" + elseif pos > 1 && line[pos-1] =~ b:any_matched_char && line[pos-2] == '\' && (pos < 3 || line[pos-3] != '\') + " Deleting an escaped matched character + return "\<BS>\<BS>" + elseif line[pos-1] !~ b:any_matched_char + " Deleting a non-special character + return "\<BS>" + elseif line[pos-1] != '"' && !s:IsBalanced() + " Current top-form is unbalanced, can't retain paredit mode + return "\<BS>" + endif + + if line[pos-1:pos] =~ b:any_matched_pair + " Deleting an empty character-pair + return "\<Right>\<BS>\<BS>" + else + " Character-pair is not empty, don't delete just move inside + return "\<Left>" + endif +endfunction + +" Handle <Del> keypress +function! PareditDel() + if !g:paredit_mode || s:InsideComment() + return "\<Del>" + endif + + let line = getline( '.' ) + let pos = col( '.' ) - 1 + + if pos == len(line) + " We are at the end of the line + return "\<Del>" + elseif line[pos] == '\' && line[pos+1] =~ b:any_matched_char && (pos < 1 || line[pos-1] != '\') + " Deleting an escaped matched character + return "\<Del>\<Del>" + elseif line[pos] !~ b:any_matched_char + " Erasing a non-special character + return "\<Del>" + elseif line[pos] != '"' && !s:IsBalanced() + " Current top-form is unbalanced, can't retain paredit mode + return "\<Del>" + elseif pos == 0 + return "\<Right>" + endif + + if line[pos-1:pos] =~ b:any_matched_pair + " Erasing an empty character-pair + return "\<Left>\<Del>\<Del>" + else + " Character-pair is not empty, don't erase just move inside + return "\<Right>" + endif +endfunction + +" Initialize yank position list +function! s:InitYankPos() + call setreg( &clipboard == 'unnamed' ? '*' : '"', '' ) + let s:yank_pos = [] +endfunction + +" Add position to the yank list +function! s:AddYankPos( pos ) + let s:yank_pos = [a:pos] + s:yank_pos +endfunction + +" Remove the head of yank position list and return it +function! s:RemoveYankPos() + if len(s:yank_pos) > 0 + let pos = s:yank_pos[0] + let s:yank_pos = s:yank_pos[1:] + return pos + else + return 0 + endif +endfunction + +" Forward erasing a character in normal mode, do not check if current form balanced +function! s:EraseFwd( count, startcol ) + let line = getline( '.' ) + let pos = col( '.' ) - 1 + let reg = '' + let ve_save = &virtualedit + set virtualedit=all + let c = a:count + while c > 0 + if line[pos] == '\' && line[pos+1] =~ b:any_matched_char && (pos < 1 || line[pos-1] != '\') + " Erasing an escaped matched character + let reg = reg . line[pos : pos+1] + let line = strpart( line, 0, pos ) . strpart( line, pos+2 ) + elseif s:InsideComment() && line[pos] == ';' && a:startcol >= 0 + " Erasing the whole comment, only when erasing a block of characters + let reg = reg . strpart( line, pos ) + let line = strpart( line, 0, pos ) + elseif s:InsideComment() || ( s:InsideString() && line[pos] != '"' ) + " Erasing any character inside string or comment + let reg = reg . line[pos] + let line = strpart( line, 0, pos ) . strpart( line, pos+1 ) + elseif pos > 0 && line[pos-1:pos] =~ b:any_matched_pair + if pos > a:startcol + " Erasing an empty character-pair + let p2 = s:RemoveYankPos() + let reg = strpart( reg, 0, p2 ) . line[pos-1] . strpart( reg, p2 ) + let reg = reg . line[pos] + let line = strpart( line, 0, pos-1 ) . strpart( line, pos+1 ) + let pos = pos - 1 + normal! h + else + " Can't erase character-pair: it would move the cursor before startcol + let pos = pos + 1 + normal! l + endif + elseif line[pos] =~ b:any_matched_char + " Character-pair is not empty, don't erase just move inside + call s:AddYankPos( len(reg) ) + let pos = pos + 1 + normal! l + elseif pos < len(line) && pos >= a:startcol + " Erasing a non-special character + let chars = split(strpart(line, pos), '\zs') + if len(chars) > 0 + " Identify the character to be erased and it's length + " The length may be >1 if this is a multi-byte character + let ch = chars[0] + let reg = reg . ch + let line = strpart( line, 0, pos ) . strpart( line, pos+len(ch) ) + endif + endif + let c = c - 1 + endwhile + let &virtualedit = ve_save + call setline( '.', line ) + call setreg( &clipboard == 'unnamed' ? '*' : '"', reg ) +endfunction + +" Backward erasing a character in normal mode, do not check if current form balanced +function! s:EraseBck( count ) + let line = getline( '.' ) + let pos = col( '.' ) - 1 + let reg = '' + let c = a:count + while c > 0 && pos > 0 + if pos > 1 && line[pos-2] == '\' && line[pos-1] =~ b:any_matched_char && (pos < 3 || line[pos-3] != '\') + " Erasing an escaped matched character + let reg = reg . line[pos-2 : pos-1] + let line = strpart( line, 0, pos-2 ) . strpart( line, pos ) + normal! h + let pos = pos - 1 + elseif s:InsideComment() || ( s:InsideString() && line[pos-1] != '"' ) + let reg = reg . line[pos-1] + let line = strpart( line, 0, pos-1 ) . strpart( line, pos ) + elseif line[pos-1:pos] =~ b:any_matched_pair + " Erasing an empty character-pair + let p2 = s:RemoveYankPos() + let reg = strpart( reg, 0, p2 ) . line[pos-1] . strpart( reg, p2 ) + let reg = reg . line[pos] + let line = strpart( line, 0, pos-1 ) . strpart( line, pos+1 ) + elseif line[pos-1] =~ b:any_matched_char + " Character-pair is not empty, don't erase + call s:AddYankPos( len(reg) ) + else + " Erasing a non-special character + let chars = split(strpart(line, 0, pos), '\zs') + if len(chars) > 0 + " Identify the character to be erased and it's length + " The length may be >1 if this is a multi-byte character + let ch = chars[-1] + let reg = reg . ch + let line = strpart( line, 0, pos-len(ch) ) . strpart( line, pos ) + let pos = pos - len(ch) + 1 + endif + endif + normal! h + let pos = pos - 1 + let c = c - 1 + endwhile + call setline( '.', line ) + call setreg( &clipboard == 'unnamed' ? '*' : '"', reg ) +endfunction + +" Forward erasing a character in normal mode +function! PareditEraseFwd() + if !g:paredit_mode || !s:IsBalanced() + if v:count > 0 + silent execute 'normal! ' . v:count . 'x' + else + normal! x + endif + return + endif + + call s:InitYankPos() + call s:EraseFwd( v:count1, -1 ) +endfunction + +" Backward erasing a character in normal mode +function! PareditEraseBck() + if !g:paredit_mode || !s:IsBalanced() + if v:count > 0 + silent execute 'normal! ' . v:count . 'X' + else + normal! X + endif + return + endif + + call s:InitYankPos() + call s:EraseBck( v:count1 ) +endfunction + +" Find beginning of previous element (atom or sub-expression) in a form +" skip_whitespc: skip whitespaces before the previous element +function! s:PrevElement( skip_whitespc ) + let [l0, c0] = [line( '.' ), col( '.' )] + let symbol_pos = [0, 0] + let symbol_end = [0, 0] + + " Move to the beginning of the prefix if any + let line = getline( '.' ) + let c = col('.') - 1 + if c > 0 && line[c-1] =~ s:any_macro_prefix + normal! h + endif + + let moved = 0 + while 1 + " Go to previous character + if !moved + let [l1, c1] = [line( '.' ), col( '.' )] + let save_ww = &whichwrap + set whichwrap= + normal! h + let &whichwrap = save_ww + endif + let moved = 0 + let [l, c] = [line( '.' ), col( '.' )] + + if [l, c] == [l1, c1] + " Beginning of line reached + if symbol_pos != [0, 0] + let symbol_end = [l, c] + if !a:skip_whitespc && !s:InsideString() + " Newline before previous symbol + call setpos( '.', [0, l0, c0, 0] ) + return [l, c] + endif + endif + normal! k$ + let [l, c] = [line( '.' ), col( '.' )] + if [l, c] == [l1, c1] + " Beginning of file reached: stop + call setpos( '.', [0, l0, c0, 0] ) + return [0, 0] + endif + let moved = 1 + elseif s:InsideComment() + " Skip comments + else + let line = getline( '.' ) + if s:InsideString() && !(a:skip_whitespc && line[c] =~ '\s' && symbol_end != [0, 0]) + let symbol_pos = [l, c] + elseif symbol_pos == [0, 0] + if line[c-1] =~ b:any_closing_char + " Skip to the beginning of this sub-expression + let symbol_pos = [l, c] + normal! % + let line2 = getline( '.' ) + let c2 = col('.') - 1 + if c2 > 0 && line2[c2-1] =~ s:any_macro_prefix + normal! h + endif + elseif line[c-1] =~ b:any_opening_char + " Opening delimiter found: stop + call setpos( '.', [0, l0, c0, 0] ) + return [0, 0] + elseif line[c-1] =~ '\S' + " Previous symbol starting + let symbol_pos = [l, c] + endif + else + if line[c-1] =~ b:any_opening_char || (a:skip_whitespc && line[c-1] =~ '\S' && symbol_end != [0, 0]) + " Previous symbol beginning reached, opening delimiter or second previous symbol starting + call setpos( '.', [0, l0, c0, 0] ) + return [l, c+1] + elseif line[c-1] =~ '\s' || symbol_pos[0] != l + " Whitespace before previous symbol + let symbol_end = [l, c] + if !a:skip_whitespc + call setpos( '.', [0, l0, c0, 0] ) + return [l, c+1] + endif + endif + endif + endif + endwhile +endfunction + +" Find end of next element (atom or sub-expression) in a form +" skip_whitespc: skip whitespaces after the next element +function! s:NextElement( skip_whitespc ) + let [l0, c0] = [line( '.' ), col( '.' )] + let symbol_pos = [0, 0] + let symbol_end = [0, 0] + + while 1 + " Go to next character + let [l1, c1] = [line( '.' ), col( '.' )] + let save_ww = &whichwrap + set whichwrap= + normal! l + let &whichwrap = save_ww + let [l, c] = [line( '.' ), col( '.' )] + + " Skip comments + while [l, c] == [l1, c1] || s:InsideComment() + if symbol_pos != [0, 0] + let symbol_end = [l, c] + if !a:skip_whitespc && !s:InsideString() + " Next symbol ended with comment + call setpos( '.', [0, l0, c0, 0] ) + return [l, c + ([l, c] == [l1, c1])] + endif + endif + normal! 0j0 + let [l, c] = [line( '.' ), col( '.' )] + if [l, c] == [l1, c1] + " End of file reached: stop + call setpos( '.', [0, l0, c0, 0] ) + return [0, 0] + endif + endwhile + + let line = getline( '.' ) + if s:InsideString() && !(a:skip_whitespc && line[c-2] =~ '\s' && symbol_end != [0, 0]) + let symbol_pos = [l, c] + elseif symbol_pos == [0, 0] + if line[c-1] =~ s:any_macro_prefix && line[c] =~ b:any_opening_char + " Skip to the end of this prefixed sub-expression + let symbol_pos = [l, c] + normal! l% + elseif line[c-1] =~ b:any_opening_char + " Skip to the end of this sub-expression + let symbol_pos = [l, c] + normal! % + elseif line[c-1] =~ b:any_closing_char + " Closing delimiter found: stop + call setpos( '.', [0, l0, c0, 0] ) + return [0, 0] + elseif line[c-1] =~ '\S' + " Next symbol starting + let symbol_pos = [l, c] + endif + else + if line[c-1] =~ b:any_closing_char || (a:skip_whitespc && line[c-1] =~ '\S' && symbol_end != [0, 0]) + " Next symbol ended, closing delimiter or second next symbol starting + call setpos( '.', [0, l0, c0, 0] ) + return [l, c] + elseif line[c-1] =~ '\s' || symbol_pos[0] != l + " Next symbol ending with whitespace + let symbol_end = [l, c] + if !a:skip_whitespc + call setpos( '.', [0, l0, c0, 0] ) + return [l, c] + endif + endif + endif + endwhile +endfunction + +" Move character from [l0, c0] to [l1, c1] +" Set position to [l1, c1] +function! s:MoveChar( l0, c0, l1, c1 ) + let line = getline( a:l0 ) + let c = line[a:c0-1] + if a:l1 == a:l0 + " Move character inside line + if a:c1 > a:c0 + let line = strpart( line, 0, a:c0-1 ) . strpart( line, a:c0, a:c1-a:c0-1 ) . c . strpart( line, a:c1-1 ) + call setline( a:l0, line ) + call setpos( '.', [0, a:l1, a:c1-1, 0] ) + else + let line = strpart( line, 0, a:c1-1 ) . c . strpart( line, a:c1-1, a:c0-a:c1 ) . strpart( line, a:c0 ) + call setline( a:l0, line ) + call setpos( '.', [0, a:l1, a:c1, 0] ) + endif + else + " Move character to another line + let line = strpart( line, 0, a:c0-1 ) . strpart( line, a:c0 ) + call setline( a:l0, line ) + let line1 = getline( a:l1 ) + if a:c1 > 1 + let line1 = strpart( line1, 0, a:c1-1 ) . c . strpart( line1, a:c1-1 ) + call setline( a:l1, line1 ) + call setpos( '.', [0, a:l1, a:c1, 0] ) + else + let line1 = c . line1 + call setline( a:l1, line1 ) + call setpos( '.', [0, a:l1, 1, 0] ) + endif + endif +endfunction + +" Find a paren nearby to move +function! s:FindParenNearby() + let line = getline( '.' ) + let c0 = col( '.' ) + if line[c0-1] !~ b:any_openclose_char + " OK, we are not standing on a paren to move, but check if there is one nearby + if (c0 < 2 || line[c0-2] !~ b:any_openclose_char) && line[c0] =~ b:any_openclose_char + normal! l + elseif c0 > 1 && line[c0-2] =~ b:any_openclose_char && line[c0] !~ b:any_openclose_char + normal! h + endif + endif + + " Skip macro prefix character + let c0 = col( '.' ) + if line[c0-1] =~ s:any_macro_prefix && line[c0] =~ b:any_opening_char + normal! l + endif + + " If still not standing on a paren then find the next closing one + if line[c0-1] !~ b:any_openclose_char + call search(b:any_closing_char, 'W') + endif +endfunction + +" Reindent current form +function! PareditReindentForm() + let l = line('.') + let c = col('.') + let old_indent = len(matchstr(getline(l), '^\s*')) + normal! =ib + let new_indent = len(matchstr(getline(l), '^\s*')) + call cursor( l, c + new_indent - old_indent ) +endfunction + +" Move delimiter one atom or s-expression to the left +function! PareditMoveLeft() + call s:FindParenNearby() + + let line = getline( '.' ) + let l0 = line( '.' ) + let c0 = col( '.' ) + + if line[c0-1] =~ b:any_opening_char + let closing = 0 + elseif line[c0-1] =~ b:any_closing_char + let closing = 1 + else + " Can move only delimiters + return + endif + + let [lp, cp] = s:GetReplPromptPos() + let [l1, c1] = s:PrevElement( closing ) + if [l1, c1] == [0, 0] + " No previous element found + return + elseif [lp, cp] != [0, 0] && l0 >= lp && (l1 < lp || (l1 == lp && c1 < cp)) + " Do not go before the last command prompt in the REPL buffer + return + endif + if !closing && c0 > 0 && line[c0-2] =~ s:any_macro_prefix + call s:MoveChar( l0, c0-1, l1, c1 ) + call s:MoveChar( l0, c0 - (l0 != l1), l1, c1+1 ) + let len = 2 + else + call s:MoveChar( l0, c0, l1, c1 ) + let len = 1 + endif + let line = getline( '.' ) + let c = col( '.' ) - 1 + if closing && c+1 < len(line) && line[c+1] !~ b:any_wsclose_char + " Insert a space after if needed + execute "normal! a " + normal! h + endif + let line = getline( '.' ) + let c = col( '.' ) - 1 + if !closing && c > 0 && line[c-len] !~ b:any_wsopen_char + " Insert a space before if needed + if len > 1 + execute "normal! hi " + normal! ll + else + execute "normal! i " + normal! l + endif + endif + call PareditReindentForm() +endfunction + +" Move delimiter one atom or s-expression to the right +function! PareditMoveRight() + call s:FindParenNearby() + + "TODO: move ')' in '() xxx' leaves space + let line = getline( '.' ) + let l0 = line( '.' ) + let c0 = col( '.' ) + + if line[c0-1] =~ b:any_opening_char + let opening = 1 + elseif line[c0-1] =~ b:any_closing_char + let opening = 0 + else + " Can move only delimiters + return + endif + + let [lp, cp] = s:GetReplPromptPos() + let [l1, c1] = s:NextElement( opening ) + if [l1, c1] == [0, 0] + " No next element found + return + elseif [lp, cp] != [0, 0] && l0 < lp && l1 >= lp + " Do not go after the last command prompt in the REPL buffer + return + endif + if opening && c0 > 1 && line[c0-2] =~ s:any_macro_prefix + call s:MoveChar( l0, c0-1, l1, c1 ) + call s:MoveChar( l0, c0-1, l1, c1 + (l0 != l1) ) + let len = 2 + else + call s:MoveChar( l0, c0, l1, c1 ) + let len = 1 + endif + let line = getline( '.' ) + let c = col( '.' ) - 1 + if opening && c > 0 && line[c-len] !~ b:any_wsopen_char + " Insert a space before if needed + if len > 1 + execute "normal! hi " + normal! ll + else + execute "normal! i " + normal! l + endif + endif + let line = getline( '.' ) + let c = col( '.' ) - 1 + if !opening && c+1 < len(line) && line[c+1] !~ b:any_wsclose_char + " Insert a space after if needed + execute "normal! a " + normal! h + endif + call PareditReindentForm() +endfunction + +" Find closing of the innermost structure: (...) or [...] or {...} +" Return a list where first element is the closing character, +" second and third is its position (line, column) +function! s:FindClosing() + let l = line( '.' ) + let c = col( '.' ) + let paren = '' + let l2 = 0 + let c2 = 0 + + call PareditFindClosing( '(', ')', 0 ) + let lp = line( '.' ) + let cp = col( '.' ) + if [lp, cp] != [l, c] + " Do we have a closing ')'? + let paren = ')' + let l2 = lp + let c2 = cp + endif + call setpos( '.', [0, l, c, 0] ) + + if &ft =~ s:fts_balancing_all_brackets + call PareditFindClosing( '[', ']', 0 ) + let lp = line( '.' ) + let cp = col( '.' ) + if [lp, cp] != [l, c] && (lp < l2 || (lp == l2 && cp < c2)) + " Do we have a ']' closer? + let paren = ']' + let l2 = lp + let c2 = cp + endif + call setpos( '.', [0, l, c, 0] ) + + call PareditFindClosing( '{', '}', 0 ) + let lp = line( '.' ) + let cp = col( '.' ) + if [lp, cp] != [l, c] && (lp < l2 || (lp == l2 && cp < c2)) + " Do we have a '}' even closer? + let paren = '}' + let l2 = lp + let c2 = cp + endif + call setpos( '.', [0, l, c, 0] ) + endif + + return [paren, l2, c2] +endfunction + +" Split list or string at the cursor position +" Current symbol will be split into the second part +function! PareditSplit() + if !g:paredit_mode || s:InsideComment() + return + endif + + if s:InsideString() + normal! i" " + else + " Go back to the beginning of the current symbol + let c = col('.') - 1 + if getline('.')[c] =~ '\S' + if c == 0 || (c > 0 && getline('.')[c-1] =~ b:any_wsopen_char) + " OK, we are standing on the first character of the symbol + else + normal! b + endif + endif + + " First find which kind of paren is the innermost + let [p, l, c] = s:FindClosing() + if p !~ b:any_closing_char + " Not found any kind of parens + return + endif + + " Delete all whitespaces around cursor position + while getline('.')[col('.')-1] =~ '\s' + normal! x + endwhile + while col('.') > 1 && getline('.')[col('.')-2] =~ '\s' + normal! X + endwhile + + if p == ')' + normal! i) ( + elseif p == '}' + normal! i} { + else + normal! i] [ + endif + endif +endfunction + +" Join two neighboring lists or strings +function! PareditJoin() + if !g:paredit_mode || s:InsideComment() || s:InsideString() + return + endif + + "TODO: skip parens in comments + let [l0, c0] = searchpos(b:any_matched_char, 'nbW') + let [l1, c1] = searchpos(b:any_matched_char, 'ncW') + if [l0, c0] == [0, 0] || [l1, c1] == [0, 0] + return + endif + let line0 = getline( l0 ) + let line1 = getline( l1 ) + let p0 = line0[c0-1] + let p1 = line1[c1-1] + if (p0 == ')' && p1 == '(') || (p0 == ']' && p1 == '[') || (p0 == '}' && p1 == '{') || (p0 == '"' && p1 == '"') + if l0 == l1 + " First list ends on the same line where the second list begins + let line0 = strpart( line0, 0, c0-1 ) . ' ' . strpart( line0, c1 ) + call setline( l0, line0 ) + else + " First list ends on a line different from where the second list begins + let line0 = strpart( line0, 0, c0-1 ) + let line1 = strpart( line1, 0, c1-1 ) . strpart( line1, c1 ) + call setline( l0, line0 ) + call setline( l1, line1 ) + endif + endif +endfunction + +" Wrap current visual block in parens of the given kind +function! s:WrapSelection( open, close ) + let l0 = line( "'<" ) + let l1 = line( "'>" ) + let c0 = col( "'<" ) + let c1 = col( "'>" ) + if &selection == 'inclusive' + let c1 = c1 + strlen(matchstr(getline(l1)[c1-1 :], '.')) + endif + if [l0, c0] == [0, 0] || [l1, c1] == [0, 0] + " No selection + return + endif + if l0 > l1 || (l0 == l1 && c0 > c1) + " Swap both ends of selection to make [l0, c0] < [l1, c1] + let [ltmp, ctmp] = [l0, c0] + let [l0, c0] = [l1, c1] + let [l1, c1] = [ltmp, ctmp] + endif + let save_ve = &ve + set ve=all + call setpos( '.', [0, l0, c0, 0] ) + execute "normal! i" . a:open + call setpos( '.', [0, l1, c1 + (l0 == l1), 0] ) + execute "normal! i" . a:close + let &ve = save_ve +endfunction + +" Wrap current visual block in parens of the given kind +" Keep visual mode +function! PareditWrapSelection( open, close ) + call s:WrapSelection( a:open, a:close ) + " Always leave the cursor to the opening char's pos after + " wrapping selection. + if getline('.')[col('.')-1] =~ b:any_closing_char + normal! % + endif +endfunction + +" Wrap current symbol in parens of the given kind +" If standing on a paren then wrap the whole s-expression +" Stand on the opening paren (if not wrapping in "") +function! PareditWrap( open, close ) + let isk_save = s:SetKeyword() + let sel_save = &selection + let line = line('.') + let column = col('.') + let line_content = getline(line) + let current_char = line_content[column - 1] + + if a:open != '"' && current_char =~ b:any_openclose_char + execute "normal! " . "v%\<Esc>" + else + let inside_comment = s:InsideComment(line, column) + + if current_char == '"' && !inside_comment + let escaped_quote = line_content[column - 2] == "\\" + if escaped_quote + execute "normal! " . "vh\<Esc>" + else + let is_starting_quote = 1 + if column == 1 && line > 1 + let endOfPreviousLine = col([line - 1, '$']) + if s:InsideString(line - 1, endOfPreviousLine - 1) + let previous_line_content = getline(line - 1) + if previous_line_content[endOfPreviousLine - 2] != '"' + let is_starting_quote = 0 + elseif previous_line_content[endOfPreviousLine - 3] == "\\" + let is_starting_quote = 0 + endif + endif + elseif s:InsideString(line, column - 1) + if line_content[column - 2] != '"' + let is_starting_quote = 0 + elseif line_content[column - 3] == "\\" + let is_starting_quote = 0 + endif + endif + let &selection="inclusive" + normal! v + if is_starting_quote + call search( '\\\@<!"', 'W', 's:SkipExpr()' ) + else + call search( '\\\@<!"', 'bW', 's:SkipExpr()' ) + endif + execute "normal! " . "\<Esc>" + endif + else + execute "normal! " . "viw\<Esc>" + endif + endif + call s:WrapSelection( a:open, a:close ) + if a:open != '"' + normal! % + else + call cursor(line, column + 1) + endif + let &selection = sel_save + let &iskeyword = isk_save +endfunction + +" Splice current list into the containing list +function! PareditSplice() + if !g:paredit_mode + return + endif + + " First find which kind of paren is the innermost + let [p, l, c] = s:FindClosing() + if p !~ b:any_closing_char + " Not found any kind of parens + return + endif + + call setpos( '.', [0, l, c, 0] ) + normal! % + let l = line( '.' ) + let c = col( '.' ) + normal! %x + call setpos( '.', [0, l, c, 0] ) + normal! x + if c > 1 && getline('.')[c-2] =~ s:any_macro_prefix + normal! X + endif +endfunction + +" Raise: replace containing form with the current symbol or sub-form +function! PareditRaise() + let isk_save = s:SetKeyword() + let ch = getline('.')[col('.')-1] + if ch =~ b:any_openclose_char + " Jump to the closing char in order to find the outer + " closing char. + if ch =~ b:any_opening_char + normal! % + endif + + let [p, l, c] = s:FindClosing() + if p =~ b:any_closing_char + " Raise sub-form and re-indent + exe "normal! y%d%da" . p + if getline('.')[col('.')-1] == ' ' + normal! "0p=% + else + normal! "0P=% + endif + elseif ch =~ b:any_opening_char + " Restore position if there is no appropriate + " closing char. + normal! % + endif + else + let [p, l, c] = s:FindClosing() + if p =~ b:any_closing_char + " Raise symbol + exe "normal! yiwda" . p + normal! "0Pb + endif + endif + let &iskeyword = isk_save +endfunction + +" ===================================================================== +" Autocommands +" ===================================================================== + +if !exists("g:paredit_disable_lisp") + au FileType lisp call PareditInitBuffer() +endif + +if !exists("g:paredit_disable_clojure") + au FileType *clojure* call PareditInitBuffer() +endif + +if !exists("g:paredit_disable_hy") + au FileType hy call PareditInitBuffer() +endif + +if !exists("g:paredit_disable_scheme") + au FileType scheme call PareditInitBuffer() + au FileType racket call PareditInitBuffer() +endif + +if !exists("g:paredit_disable_shen") + au FileType shen call PareditInitBuffer() +endif |