vim/bundle/slimv/plugin/paredit.vim @ c4d3b4507707

vim: better next mappings and slimv
author Steve Losh <steve@stevelosh.com>
date Fri, 12 Nov 2010 18:30:35 -0500
parents (none)
children (none)
" paredit.vim:
"               Paredit mode for Slimv
" Version:      0.7.1
" Last Change:  07 Nov 2010
" 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

" =====================================================================
"  Global variable definitions
" =====================================================================

" Paredit mode selector
if !exists( 'g:paredit_mode' )
    let g:paredit_mode = 1
endif

"TODO: automatic indentation
" Automatic indentation after some editing commands
"if !exists( 'g:paredit_autoindent' )
"    let g:paredit_autoindent = 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

" =====================================================================
"  Other variable definitions
" =====================================================================

" Skip matches inside string or comment
let s:skip_c  = 'synIDattr(synID(line("."), col("."), 0), "name") =~ "[Cc]omment"'
let s:skip_sc = 'synIDattr(synID(line("."), col("."), 0), "name") =~ "[Ss]tring\\|[Cc]omment"'

" Regular expressions to identify special characters combinations used by paredit
"TODO: add curly brace
let s:any_matched_char   = '(\|)\|\[\|\]\|\"'
let s:any_matched_pair   = '()\|\[\]\|\"\"'
let s:any_opening_char   = '(\|\['
let s:any_closing_char   = ')\|\]'
let s:any_openclose_char = '(\|\[\|)\|\]'
let s:any_wsopen_char    = '\s\|(\|\['
let s:any_wsclose_char   = '\s\|)\|\]'
let s:any_macro_prefix   = "'" . '\|`\|#\|@\|\~'

" Repeat count for some remapped edit functions (like 'd')
let s:repeat             = 0

let s:yank_pos           = []

" =====================================================================
"  General utility functions
" =====================================================================

" Buffer specific initialization
function! PareditInitBuffer()
    if g:paredit_mode
        " Paredit mode is on: add buffer specific keybindings
        inoremap <buffer> <expr>   (            PareditInsertOpening('(',')')
        inoremap <buffer> <expr>   )            PareditInsertClosing('(',')')
        inoremap <buffer> <expr>   [            PareditInsertOpening('[',']')
        inoremap <buffer> <expr>   ]            PareditInsertClosing('[',']')
        inoremap <buffer> <expr>   "            PareditInsertQuotes()
        inoremap <buffer> <expr>   <BS>         PareditBackspace(0)
        inoremap <buffer> <expr>   <Del>        PareditDel()
        nnoremap <buffer> <silent> (            :<C-U>call PareditFindOpening('(',')',0)<CR>
        nnoremap <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>
        nnoremap <buffer> <silent> x            :<C-U>call PareditEraseFwd()<CR>
        nnoremap <buffer> <silent> <Del>        :<C-U>call PareditEraseFwd()<CR>
        nnoremap <buffer> <silent> X            :<C-U>call PareditEraseBck()<CR>
        nnoremap <buffer> <silent> s            :<C-U>call PareditEraseFwd()<CR>i
        nnoremap <buffer> <silent> D            v$:<C-U>call PareditDelete(visualmode(),1)<CR>
        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>
        nnoremap <buffer> <silent> dd           :<C-U>call PareditDeleteLines()<CR>
        nnoremap <buffer> <silent> cc           :<C-U>call PareditChangeLines()<CR>
        nnoremap <buffer> <silent> p            :<C-U>call PareditPut('p')<CR>
        nnoremap <buffer> <silent> P            :<C-U>call PareditPut('P')<CR>
        nnoremap <buffer> <silent> <Leader>w(   :<C-U>call PareditWrap('(',')')<CR>
        vnoremap <buffer> <silent> <Leader>w(   :<C-U>call PareditWrapSelection('(',')')<CR>
        nnoremap <buffer> <silent> <Leader>w[   :<C-U>call PareditWrap('[',']')<CR>
        vnoremap <buffer> <silent> <Leader>w[   :<C-U>call PareditWrapSelection('[',']')<CR>
        nnoremap <buffer> <silent> <Leader>w"   :<C-U>call PareditWrap('"','"')<CR>
        vnoremap <buffer> <silent> <Leader>w"   :<C-U>call PareditWrapSelection('"','"')<CR>

        if g:paredit_shortmaps
            " Shorter keymaps: old functionality of KEY is remapped to <Leader>KEY
            nnoremap <buffer> <silent> <            :<C-U>call PareditMoveLeft()<CR>
            nnoremap <buffer> <silent> >            :<C-U>call PareditMoveRight()<CR>
            nnoremap <buffer> <silent> O            :<C-U>call PareditSplit()<CR>
            nnoremap <buffer> <silent> J            :<C-U>call PareditJoin()<CR>
            nnoremap <buffer> <silent> W            :<C-U>call PareditWrap()<CR>
            vnoremap <buffer> <silent> W            :<C-U>call PareditWrapSelection()<CR>
            nnoremap <buffer> <silent> S            :<C-U>call PareditSplice()<CR>
            nnoremap <buffer> <silent> <Leader><    :<C-U>normal! <<CR>
            nnoremap <buffer> <silent> <Leader>>    :<C-U>normal! ><CR>
            nnoremap <buffer> <silent> <Leader>O    :<C-U>normal! O<CR>
            nnoremap <buffer> <silent> <Leader>J    :<C-U>normal! J<CR>
            nnoremap <buffer> <silent> <Leader>W    :<C-U>normal! W<CR>
            vnoremap <buffer> <silent> <Leader>W    :<C-U>normal! W<CR>
            nnoremap <buffer> <silent> <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>
            nnoremap <buffer> <silent> <Leader><    :<C-U>call PareditMoveLeft()<CR>
            nnoremap <buffer> <silent> <Leader>>    :<C-U>call PareditMoveRight()<CR>
            nnoremap <buffer> <silent> <Leader>O    :<C-U>call PareditSplit()<CR>
            nnoremap <buffer> <silent> <Leader>J    :<C-U>call PareditJoin()<CR>
            nnoremap <buffer> <silent> <Leader>W    :<C-U>call PareditWrap('(',')')<CR>
            vnoremap <buffer> <silent> <Leader>W    :<C-U>call PareditWrapSelection('(',')')<CR>
            nnoremap <buffer> <silent> <Leader>S    :<C-U>call PareditSplice()<CR>
        endif
    else
        " Paredit mode is off: remove keybindings
        iunmap <buffer> (
        iunmap <buffer> )
        iunmap <buffer> [
        iunmap <buffer> ]
        iunmap <buffer> "
        iunmap <buffer> <BS>
        iunmap <buffer> <Del>
        unmap  <buffer> (
        unmap  <buffer> )
        unmap  <buffer> x
        unmap  <buffer> <Del>
        unmap  <buffer> X
        unmap  <buffer> s
        unmap  <buffer> D
        unmap  <buffer> C
        unmap  <buffer> d
        unmap  <buffer> c
        unmap  <buffer> dd
        unmap  <buffer> cc
    endif
endfunction

" General Paredit operator function
function! PareditOpfunc( func, type, visualmode )
    let sel_save = &selection
    let ve_save = &virtualedit
    set virtualedit=all
    let reg_save = @@

    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( '"' )

        " Find and keep unbalanced matched characters in the region
        let matched = s:GetMatchedChars( putreg, s:InsideString( "'<" ), s:InsideComment( "'<" ) )
        let matched = s:Unbalanced( matched )
        let matched = substitute( matched, '\s', '', 'g' )

        if matched == ''
            silent exe "normal! gvx"
        else
            silent exe "normal! gvc" . matched
            silent exe "normal! l"
        endif
    endif

    let &selection = sel_save
    let &virtualedit = ve_save
    let @@ = reg_save
    call setreg( '"', putreg ) 
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, ... )
    call PareditOpfunc( 'c', a:type, a:0 )
    startinsert
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

" Paste text from put register in a balanced way
function! PareditPut( cmd )
    let reg_save = @@
    let putreg = getreg( '"' )

    " Find unpaired matched characters by eliminating paired ones
    let matched = s:GetMatchedChars( putreg, s:InsideString( '.' ), s:InsideComment( '.' ) )
    let matched = s:Unbalanced( matched )

    " 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( '"', putreg ) 
    if v:count > 1
        silent exe "normal! " . v:count . a:cmd
    else
        silent exe "normal! " . a:cmd
    endif
    let @@ = reg_save
endfunction

" Toggle paredit mode
function! PareditToggle()
    let g:paredit_mode = 1 - g:paredit_mode
    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, pos, match_eol )
    let line = line( a:pos )
    let col  = col ( a:pos )
    if a:match_eol && col > len( getline( line ) )
        let col = col - 1
    endif
    return synIDattr( synID( line, col, 0), 'name' ) =~ a:regexp
endfunction

" Is the current cursor position inside a comment?
function! s:InsideComment( ... )
    return s:SynIDMatch( '[Cc]omment', a:0 ? a:1 : '.', 1 )
endfunction

" Is the current cursor position inside a string?
function! s:InsideString( ... )
    return s:SynIDMatch( '[Ss]tring', a:0 ? a:1 : '.', 0 )
endfunction

" Autoindent current top level form
function! PareditIndentTopLevelForm( level )
    if a:level < g:paredit_autoindent
        return
    endif
    let l = line( '.' )
    let c =  col( '.' )
    normal! ms
    let matchb = max( [l-g:paredit_matchlines, 1] )
    let [l0, c0] = searchpairpos( '(', '', ')', 'brmW', s:skip_sc, matchb )
    "let save_exp = &expandtab
    "set expandtab
    normal! v%=`s
    "let &expandtab = save_exp
endfunction

" Is this a Slimv REPL buffer?
function! s:IsReplBuffer()
    if exists( 'g:slimv_repl_dir' ) && exists( 'g:slimv_repl_file' )
        let repl_name = g:slimv_repl_dir . g:slimv_repl_file
        return bufnr( repl_name ) == bufnr( '%' )
    else
        return bufname( '%' ) =~ '.*\.repl\..*'
    endif
endfunction

" Get Slimv 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
    return [ line( "'s" ), col( "'s" ) ]
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( '.' )
    let matchb = max( [l-g:paredit_matchlines, 1] )
    let matchf = min( [l+g:paredit_matchlines, line('$')] )
    let prompt = line( "'s" )
    if s:IsReplBuffer() && l >= prompt && matchb < prompt
        " Do not go before the last command prompt in the REPL buffer
        let matchb = prompt
    endif
    let p1 = searchpair( '(', '', ')', 'brnmW', s:skip_sc, matchb )
    let p2 = searchpair( '(', '', ')',  'rnmW', s:skip_sc, matchf )
    if !(p1 == p2) && !(p1 == p2 - 1 && line[c-1] == '(') && !(p1 == p2 + 1 && line[c-1] == ')')
        " Number of opening and closing parens differ
        return 0
    endif
    let b1 = searchpair( '\[', '', '\]', 'brnmW', s:skip_sc, matchb )
    if b1 == 0
        " Outside of all bracket-pairs
        return 1
    endif
    let b2 = searchpair( '\[', '', '\]',  'rnmW', s:skip_sc, matchf )
    if !(b1 == b2) && !(b1 == b2 - 1 && line[c-1] == '[') && !(b1 == b2 + 1 && line[c-1] == ']')
        " Number of opening and closing brackets differ
        return 0
    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
        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] == '(' || a:lines[i] == '[' || a:lines[i] == ')' || a:lines[i] == ']'
                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')
        let tmp = substitute( tmp, '\[\(\s*\)\]', ' \1 ', 'g')
        let tmp = substitute( tmp, '"\(\s*\)"',   ' \1 ', 'g')
        if tmp == matched
            " All paired chars eliminated
            let tmp = substitute( tmp, ')\(\s*\)(',   ' \1 ', 'g')
            let tmp = substitute( tmp, '\]\(\s*\)\[', ' \1 ', 'g')
            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:skip_sc )
    if a:select
        call searchpair( open, '', close, 'W', s:skip_sc )
        let save_ve = &ve
        set ve=all 
        normal! lvh
        let &ve = save_ve
        call searchpair( open, '', close, 'bW', s:skip_sc )
    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:skip_sc )
        call searchpair( open, '', close, 'bW', s:skip_sc )
        normal! v
        call searchpair( open, '', close, 'W', s:skip_sc )
        normal! l
    else
        call searchpair( open, '', close, 'W', s:skip_sc )
    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 line[pos] !~ s: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] !~ s:any_wsopen_char && line[pos-1] !~ s:any_macro_prefix
        " Add a space before if needed
        let retval = " " . retval
    endif
    return retval
endfunction

" Insert closing type of a paired character, like ) or ].
function! PareditInsertClosing( open, close )
    if !g:paredit_mode || s:InsideComment() || s:InsideString() || !s:IsBalanced()
        return a:close
    endif
    let line = getline( '.' )
    let pos = col( '.' ) - 1
    if line[pos] == a:close
        return "\<Right>"
    else
        let open  = escape( a:open , '[]' )
        let close = escape( a:close, '[]' )
        return "\<C-O>:call searchpair('" . open . "','','" . close . "','W','" . s:skip_sc . "')\<CR>\<Right>"
        "TODO: indent after going to closing character
    endif
endfunction

" Insert an (opening or closing) double quote
function! PareditInsertQuotes()
    if !g:paredit_mode || s:InsideComment()
        return '"'
    endif
    if s:InsideString()
        let line = getline( '.' )
        let pos = col( '.' ) - 1
        "TODO: skip comments in search(...)
        if pos > 0 && line[pos-1] == '\' && (pos < 2 || line[pos-2] != '\')
            " About to enter a \" inside a string
            return '"'
        elseif 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 <BS> keypress
function! PareditBackspace( repl_mode )
    if a:repl_mode && line( "." ) == line( "'s" ) && col( "." ) <= col( "'s" )
        " 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 line[pos-1] !~ s: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] =~ s: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] !~ s: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] =~ s: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()
    let @" = ''
    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 c = a:count
    while c > 0
        if s:InsideString() && line[pos : pos+1] == '\"'
            " Erasing a \" inside string
            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] =~ s: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] =~ s: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 reg = reg . line[pos]
            let line = strpart( line, 0, pos ) . strpart( line, pos+1 )
        endif
        let c = c - 1
    endwhile
    call setline( '.', line )
    let @" = 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 s:InsideString() && pos > 1 && line[pos-2:pos-1] == '\"'
            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] =~ s: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] =~ s:any_matched_char
            " Character-pair is not empty, don't erase
            call s:AddYankPos( len(reg) )
        else
            " Erasing a non-special character
            let reg = reg . line[pos-1]
            let line = strpart( line, 0, pos-1 ) . strpart( line, pos )
        endif
        normal! h
        let pos = pos - 1
        let c = c - 1
    endwhile
    call setline( '.', line )
    let @" = 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( '.' )]
            normal! h
        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()
                let symbol_pos = [l, c]
            elseif symbol_pos == [0, 0]
                if line[c-1] =~ s: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] =~ s: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] =~ s: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( '.' )]
        normal! l
        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()
            let symbol_pos = [l, c]
        elseif symbol_pos == [0, 0]
            if line[c-1] =~ s:any_macro_prefix && line[c] =~ s:any_opening_char
                " Skip to the end of this prefixed sub-expression
                let symbol_pos = [l, c]
                normal! l%
            elseif line[c-1] =~ s:any_opening_char
                " Skip to the end of this sub-expression
                let symbol_pos = [l, c]
                normal! %
            elseif line[c-1] =~ s: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] =~ s: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] !~ s: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] !~ s:any_openclose_char) && line[c0] =~ s:any_openclose_char
            normal! l
        elseif c0 > 1 && line[c0-2] =~ s:any_openclose_char && line[c0] !~ s:any_openclose_char
            normal! h
        endif
    endif

    " Skip macro prefix character    
    let c0 =  col( '.' )
    if line[c0-1] =~ s:any_macro_prefix && line[c0] =~ s:any_opening_char
        normal! l
    endif
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] =~ s:any_opening_char
        let closing = 0
    elseif line[c0-1] =~ s: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 && line[c+1] !~ s:any_wsclose_char
        " Insert a space after if needed
        execute "normal! a "
        normal! h
    endif
    if !closing && c > 0 && line[c-len] !~ s: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
    return
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] =~ s:any_opening_char
        let opening = 1
    elseif line[c0-1] =~ s: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] !~ s: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
    if !opening && line[c+1] !~ s:any_wsclose_char
        " Insert a space after if needed
        execute "normal! a "
        normal! h
    endif
endfunction

" Find closing of the innermost structure: (...) 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( '.' )
    call PareditFindClosing( '(', ')', 0 )
    let lp = line( '.' )
    let cp = col( '.' )
    call setpos( '.', [0, l, c, 0] )
    call PareditFindClosing( '[', ']', 0 )
    let lb = line( '.' )
    let cb = col( '.' )
    call setpos( '.', [0, l, c, 0] )
    if [lp, cp] == [l, c] && [lb, cb] == [l, c]
        " Not found any kind of paren
        return ['', 0, 0]
    elseif [lb, cb] == [l, c] || lp < lb || (lp == lb && cp < cb)
        " The innermost structure is a (...)
        return [')', lp, cp]
    else
        " The innermost structure is a [...]
        return [']', lb, cb]
    endif
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] =~ s: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 !~ s: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) (
        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(s:any_matched_char, 'nbW')
    let [l1, c1] = searchpos(s:any_matched_char, 'ncW')
    if [l0, c0] == [0, 0] || [l1, c1] == [0, 0]
        return
    endif
    let line0 = getline( l0 )
    let line1 = getline( l1 )
    if (line0[c0-1] == ')' && line1[c1-1] == '(') || (line0[c0-1] == ']' && line1[c1-1] == '[') || (line0[c0-1] == '"' && line1[c1-1] == '"')
        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 + 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 )
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 )
    if a:open != '"' && getline('.')[col('.') - 1] =~ s:any_openclose_char
        execute "normal! " . "v%\<Esc>"
    else
        execute "normal! " . "viw\<Esc>"
    endif
    call s:WrapSelection( a:open, a:close )
    if a:open != '"'
        normal! %
    endif
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 !~ s: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


" =====================================================================
"  Autocommands
" =====================================================================

au BufNewFile,BufRead *.lisp call PareditInitBuffer()
au BufNewFile,BufRead *.clj  call PareditInitBuffer()