parinfer.vim
Branches > main > ecf9761 > files / nvim / pack / plugins / opt / parinfer-rust / plugin / parinfer.vim
if !exists('g:parinfer_mode')
let g:parinfer_mode = "smart"
endif
if !exists('g:parinfer_enabled')
let g:parinfer_enabled = 1
endif
if !exists('g:parinfer_force_balance')
let g:parinfer_force_balance = 0
endif
if !exists('g:parinfer_comment_char')
let g:parinfer_comment_char = ";"
endif
if !exists('g:parinfer_string_delimiters')
let g:parinfer_string_delimiters = ['"']
endif
if !exists('g:parinfer_lisp_vline_symbols')
let g:parinfer_lisp_vline_symbols = 0
endif
if !exists('g:parinfer_lisp_block_comments')
let g:parinfer_lisp_block_comments = 0
endif
if !exists('g:parinfer_guile_block_comments')
let g:parinfer_guile_block_comments = 0
endif
if !exists('g:parinfer_scheme_sexp_comments')
let g:parinfer_scheme_sexp_comments = 0
endif
if !exists('g:parinfer_janet_long_strings')
let g:parinfer_janet_long_strings = 0
endif
if !exists('g:parinfer_hy_bracket_strings')
let g:parinfer_hy_bracket_strings = 0
endif
" Needs to be outside function because we want <sfile> to be the location of this file,
" not where it is getting called from.
let s:libdir = expand('<sfile>:p:h:h') . '/target/release'
function! s:guess_dylib_path() abort
if has('macunix')
return s:libdir . '/libparinfer_rust.dylib'
elseif has('unix')
let s:uname = system("uname")
if s:uname == "Darwin\n"
return s:libdir . '/libparinfer_rust.dylib'
else
return s:libdir . '/libparinfer_rust.so'
endif
elseif has('win32')
return s:libdir . '/parinfer_rust.dll'
else
" I hope we don't come here!
echoerr 'Parinfer was unable to guess its dynamic library path.'
endif
endfunction
command! ParinferOn let g:parinfer_enabled = 1
command! ParinferOff let g:parinfer_enabled = 0
" Common Lisp and Scheme: ignore parens in symbols enclosed by ||
au BufNewFile,BufRead *.lsp,*.lisp,*.cl,*.L,sbclrc,.sbclrc let b:parinfer_lisp_vline_symbols = 1
au BufNewFile,BufRead *.scm,*.sld,*.ss,*.rkt let b:parinfer_lisp_vline_symbols = 1
" Common Lisp and Scheme: ignore parens in block comments
au BufNewFile,BufRead *.lsp,*.lisp,*.cl,*.L,sbclrc,.sbclrc let b:parinfer_lisp_block_comments = 1
au BufNewFile,BufRead *.scm,*.sld,*.ss,*.rkt let b:parinfer_lisp_block_comments = 1
" Scheme (SRFI-62): S-expression comment
au BufNewFile,BufRead *.scm,*.sld,*.ss,*.rkt let b:parinfer_scheme_sexp_comments = 1
" Comment settings
au BufNewFile,BufRead *.janet let b:parinfer_comment_char = "#"
" Quote settings
au BufNewFile,BufRead *.yuck let b:parinfer_string_delimiters = ['"', "'", "`"]
" Long strings settings
au BufNewFile,BufRead *.janet let b:parinfer_janet_long_strings = 1
au BufNewFile,BufRead *.hy let b:parinfer_hy_bracket_strings = 1
" Logging {{{1
function! s:parinfer_log(...)
if a:0 > 0
let g:parinfer_logfile = a:1
echomsg 'Parinfer is now logging to '.a:1
else
unlet g:parinfer_logfile
echomsg 'Parinfer is no longer logging'
endif
endfunction
function! s:log(tag, data) abort
if exists('g:parinfer_logfile')
call writefile([printf('%20s: %s', a:tag, json_encode(a:data))], g:parinfer_logfile, 'a')
endif
endfunction
function! s:log_diff(from, to) abort
if exists('g:parinfer_logfile')
let l:from_lines = split(a:from, "\n")
let l:to_lines = split(a:to, "\n")
" Compute the edit distance
let l:table = map(range(0, len(l:from_lines)), 'repeat([19999], 1+len(l:to_lines))')
for i in range(0, len(l:from_lines)) | let l:table[i][0] = i | endfor
for j in range(0, len(l:to_lines)) | let l:table[0][j] = j | endfor
for i in range(1, len(l:from_lines))
for j in range(1, len(l:to_lines))
let l:table[i][j] = min([ 1 + l:table[i-1][j], 1 + l:table[i][j-1] ])
if l:from_lines[i-1] ==# l:to_lines[j-1]
let l:table[i][j] = min([ l:table[i][j], 0 + l:table[i-1][j-1] ])
endif
endfor
endfor
" Construct a diff
let l:i = len(l:from_lines)
let l:j = len(l:to_lines)
let l:diff = []
while l:i > 0 || l:j > 0
if l:i > 0 && l:j > 0 && l:table[i-1][j-1] == l:table[i][j] && l:from_lines[i-1] ==# l:to_lines[j-1]
let l:diff += [' ' . l:from_lines[i-1]]
let l:i -= 1
let l:j -= 1
elseif l:j > 0 && 1+l:table[i][j-1] == l:table[i][j]
let l:diff += [' +' . l:to_lines[j-1]]
let l:j -= 1
elseif l:i > 0 && 1+l:table[i-1][j] == l:table[i][j]
let l:diff += [' -' . l:from_lines[i-1]]
let l:i -= 1
else
throw 'bad case ' . l:i . ',' . l:j
endif
endwhile
call writefile(reverse(l:diff), g:parinfer_logfile, 'a')
endif
endfunction
command! -nargs=? ParinferLog call <SID>parinfer_log(<f-args>)
" Cursor Position {{{1
function! s:get_cursor_position() abort
let l:cursor = getpos('.')
let l:line = getline('.')
let l:byte_column = l:cursor[2] - 1
let l:cursor[1] -= 1
let l:cursor[2] = strdisplaywidth(strpart(l:line, 0, l:byte_column))
return l:cursor
endfunction
function! s:set_cursor_position(position) abort
let l:cursor = copy(a:position)
let l:cursor[1] += 1
let l:cursor[2] += 1
let line = getline(l:cursor[1])
let head = matchstr(line, '.\+\%<' . (l:cursor[2] + 1) . 'v') " text before cursor
let l:cursor[2] = strlen(head) + 1
call setpos('.', l:cursor)
endfunction
" }}}
function! s:enter_window() abort
let w:parinfer_previous_cursor = s:get_cursor_position()
endfunction
function! s:enter_buffer() abort
call s:enter_window()
if !exists('b:parinfer_last_changedtick')
let b:parinfer_last_changedtick = -10
let b:parinfer_previous_text = join(getline(1,'$'),"\n")
endif
let orig_mode = g:parinfer_mode
let g:parinfer_mode = 'paren'
call s:process_buffer()
let g:parinfer_mode = orig_mode
endfunction
function! s:process_buffer() abort
if !g:parinfer_enabled || &paste || !&modifiable
return
endif
if !exists('b:parinfer_last_changedtick')
call s:enter_buffer()
endif
if !exists('b:parinfer_comment_char')
let b:parinfer_comment_char = g:parinfer_comment_char
endif
if !exists('b:parinfer_string_delimiters')
let b:parinfer_string_delimiters = g:parinfer_string_delimiters
endif
if !exists('b:parinfer_lisp_vline_symbols')
let b:parinfer_lisp_vline_symbols = g:parinfer_lisp_vline_symbols
endif
if !exists('b:parinfer_lisp_block_comments')
let b:parinfer_lisp_block_comments = g:parinfer_lisp_block_comments
endif
if !exists('b:parinfer_guile_block_comments')
let b:parinfer_guile_block_comments = g:parinfer_guile_block_comments
endif
if !exists('b:parinfer_scheme_sexp_comments')
let b:parinfer_scheme_sexp_comments = g:parinfer_scheme_sexp_comments
endif
if !exists('b:parinfer_janet_long_strings')
let b:parinfer_janet_long_strings = g:parinfer_janet_long_strings
endif
if !exists('b:parinfer_hy_bracket_strings')
let b:parinfer_hy_bracket_strings = g:parinfer_hy_bracket_strings
endif
if b:parinfer_last_changedtick != b:changedtick
let l:cursor = s:get_cursor_position()
let l:orig_lines = getline(1,'$')
let l:orig_text = join(l:orig_lines, "\n")
let l:request = { "mode": g:parinfer_mode,
\ "text": l:orig_text,
\ "options": { "commentChar": b:parinfer_comment_char,
\ "stringDelimiters": b:parinfer_string_delimiters,
\ "cursorX": l:cursor[2],
\ "cursorLine": l:cursor[1],
\ "forceBalance": g:parinfer_force_balance ? v:true : v:false,
\ "lispVlineSymbols": b:parinfer_lisp_vline_symbols ? v:true : v:false,
\ "lispBlockComments": b:parinfer_lisp_block_comments ? v:true : v:false,
\ "guileBlockComments": b:parinfer_guile_block_comments ? v:true : v:false,
\ "schemeSexpComments": b:parinfer_scheme_sexp_comments ? v:true : v:false,
\ "janetLongStrings": b:parinfer_janet_long_strings ? v:true : v:false,
\ "hyBracketStrings": b:parinfer_hy_bracket_strings ? v:true : v:false,
\ "prevCursorX": w:parinfer_previous_cursor[2],
\ "prevCursorLine": w:parinfer_previous_cursor[1],
\ "prevText": b:parinfer_previous_text } }
if !exists('g:parinfer_dylib_path')
let g:parinfer_dylib_path = s:guess_dylib_path()
endif
let l:response = json_decode(libcall(g:parinfer_dylib_path, "run_parinfer", json_encode(l:request)))
if l:response["success"]
if l:response["text"] !=# l:orig_text
call s:log('change-request', l:request)
call s:log('change-response', l:response)
call s:log_diff(l:orig_text, l:response['text'])
let l:lines = split(l:response["text"], "\n", 1)
let l:changed = filter(range(len(l:lines)), 'l:lines[v:val] !=# l:orig_lines[v:val]')
silent! undojoin
try
call setline(l:changed[0]+1, l:lines[l:changed[0]:l:changed[-1]])
catch /E5\(23\|78\|65\):/ " not allowed here / not allowed to change text here / not allowed to chnage text or change window
" If an event doesn't allow us to modify the buffer, that's OK.
" Usually another event will happen before a redraw.
call s:log('not-allowed-here', {})
endtry
endif
let l:cursor[1] = l:response["cursorLine"]
let l:cursor[2] = l:response["cursorX"]
call s:set_cursor_position(l:cursor)
let b:parinfer_previous_text = l:response["text"]
else
call s:log('error-response', l:response)
let g:parinfer_last_error = l:response["error"]
let b:parinfer_previous_text = join(getline(1,'$'),"\n")
endif
let b:parinfer_last_changedtick = b:changedtick
endif
let w:parinfer_previous_cursor = s:get_cursor_position()
endfunction
let s:EVENTS = {
\ 'BufEnter': function('<SID>enter_buffer'),
\ 'CursorMoved': function('<SID>process_buffer'),
\ 'InsertCharPre': function('<SID>process_buffer'),
\ 'InsertEnter': function('<SID>process_buffer'),
\ 'TextChanged': function('<SID>process_buffer'),
\ 'TextChangedI': function('<SID>process_buffer'),
\ 'TextChangedP': function('<SID>process_buffer'),
\ 'WinEnter': function('<SID>enter_window') }
function! s:event(name) abort
call s:log('event', {'name': a:name, 'bufnr': bufnr('%'), 'changedtick': b:changedtick })
call call(s:EVENTS[a:name], [])
endfunction
function! s:initialize_buffer() abort
" We can't get the buffer in the command-line window, so don't initialize
" it. This happens with vim-fireplace's `cqq`.
if getcmdwintype() !=# ''
return
endif
" Don't enable if preview window
if &previewwindow
return
endif
for event_name in filter(keys(s:EVENTS),'exists("##".v:val)')
execute "autocmd! Parinfer ".event_name." <buffer> call <SID>event('".event_name."')"
endfor
endfunction
augroup Parinfer
autocmd FileType clojure,scheme,lisp,racket,hy,fennel,janet,carp,wast,yuck,dune call <SID>initialize_buffer()
augroup END
" Handle the case where parinfer was lazy-loaded
if (&filetype ==? 'clojure' || &filetype ==? 'scheme' || &filetype ==? 'lisp' || &filetype ==? 'racket' || &filetype ==? 'hy' || &filetype ==? 'fennel' || &filetype ==? 'janet' || &filetype ==? 'carp' || &filetype ==? 'wast' || &filetype ==? 'yuck' || &filetype ==? 'dune')
call <SID>initialize_buffer()
endif
let g:parinfer_loaded = v:true
" vim:set sts=2 sw=2 ai et foldmethod=marker: