guns / vim-sexp

Precision Editing for S-expressions
MIT License
612 stars 33 forks source link

Cannot insert unbalanced paren #22

Closed ianconsolata closed 7 years ago

ianconsolata commented 7 years ago

The insert mode mappings by default don't allow me to match unbalanced parentheses (| denotes the cursor). What I have: ([(|]) What I want: ([()|]) What I get: ([(]|)

Rather than recognizing that the internal paren is unmatched, and therefore allowing me to insert, it moves my cursor to the closing ). This comment in the source explains that it's a tradeoff made for performance reasons. Is there any way to get the desired functionality without taking the performance hit? I'm happy to do the work if you have a solution in mind.

guns commented 7 years ago

Hi jungziege!

The simplest solution to this situation is to forcibly insert the desired character with <C-v>.

IMHO, the insert mode mappings do a good job of keeping brackets balanced in most cases, so requiring the user to type <C-v>) on the rare occasion that they are not is a reasonable demand.

I definitely welcome a solution to this issue if it comes at an imperceptible performance cost. Here is the commit that introduced the change:

https://github.com/guns/vim-sexp/commit/d86afd5c246ca9fa9af65404ae7316835f5c5670

And the previous version of the function:

" Position of nearest _paired_ bracket: 0 for opening, 1 for closing. Returns
" [0, 0, 0, 0] if none found.
function! s:nearest_bracket(closing)
    let closest = []
    let flags = a:closing ? 'nW' : 'bnW'
    let stopline = g:sexp_maxlines ? line('.') + (a:closing ? g:sexp_maxlines : -g:sexp_maxlines) : 0

    for [start, end] in s:pairs
        let [line, col] = searchpairpos(start, '', end, flags, 's:is_ignored_scope(line("."), col("."))', stopline)

        if line < 1
            continue
        elseif empty(closest)
            let closest = [0, line, col, 0]
        else
            let closest = s:min_by_distance_from(getpos('.'), closest, [0, line, col, 0])
        endif
    endfor

    return empty(closest) ? [0, 0, 0, 0] : closest
endfunction

function! s:min_by_distance_from(pos, a, b)
    " First return closest by line difference
    let line_delta_a = abs(a:pos[1] - a:a[1])
    let line_delta_b = abs(a:pos[1] - a:b[1])
    if line_delta_a > line_delta_b
        return a:b
    elseif line_delta_a < line_delta_b
        return a:a
    " They are on the same line as the cursor
    elseif line_delta_a == 0
        let col_delta_a = abs(a:pos[2] - a:a[2])
        let col_delta_b = abs(a:pos[2] - a:b[2])
        return col_delta_a > col_delta_b ? a:b : a:a
    " They are on the same line, but not on the same line as the cursor. If
    " below the cursor, proximity is closest to bol and vice versa.
    else
        let op = a:pos[1] - a:a[1] < 0 ? '<' : '>'
        let a_is_closer = eval(a:a[2] . op . a:b[2])
        return a_is_closer ? a:a : a:b
    endif
endfunction

I recall trying to optimize the above before throwing in the towel, but couldn't get the order of magnitude decrease in runtime that I wanted to keep things snappy. Perhaps you can do better! I would be happy to accept it.

Thanks for reading the source before reporting!

ianconsolata commented 7 years ago

Oh, sweet. Didn't know about <C-v>). That works great, thanks! Closing for now.