" " limp/vim/bridge.vim " " URL: " http://mikael.jansson.be/hacking " " Description: " Handle communication between Vim and Lisp, including boot, connect and " display. Relies on 'lisp.sh' from the Limp package. " " Version: " 0.2 " " Date: " 2008-04-25 " " Authors: " Mikael Jansson " Larry Clapp " Changelog: " 2008-08-26 by Mikael Jansson " * Optionally specify core at startup and exit. " " 2008-08-25 by Mikael Jansson " * Now boots a new Lisp or connects to an existing via screen. " No longer needs the funnel (although it does need a file to read to/from " screen: it doesn't seem as if 'stuff' can handle very large amounts of " texts) " " 2008-08-18 by Mikael Jansson " * Tab-completed prompt that lets you choose the Lisp process to connect to. " Moved the startup to before the loaded check. "------------------------------------------------------------------- " startup "------------------------------------------------------------------- " only do these things once let s:Limp_version="0.3.5-pre" let s:Limp_location = expand("$LIMPRUNTIME") "if s:Limp_location == "" || s:Limp_location == "$LIMPRUNTIME" if !filereadable(s:Limp_location . "/vim/limp.vim") let s:Limp_location = "/usr/local/limp/" . s:Limp_version endif " prefix for the pipe used for communication let s:limp_bridge_channel_base = $HOME . "/.limp_bridge_channel-" let s:limp_bridge_connected=0 exe "setlocal complete+=s" . s:Limp_location . "/vim/thesaurus" "------------------------------------------------------------------- " talk to multiple Lisps using LimpBridge_connect() "------------------------------------------------------------------- fun! LimpBridge_complete_lisp(A,L,P) let prefix = s:limp_bridge_channel_base "echom "ls -1 ".prefix."*" let output = system("ls -1 ".prefix."*") if stridx(output, prefix."*") >= 0 echom "No Lisps started yet?" return endif let files = split(output, "\n") let names = [] for f in files let names += [f[strlen(prefix):]] endfor return names endfun " optionally specify the screen id to connect to " " return values: " " -1 if user didn't want to connect " 0 if connection wasn't possible " 1 if the user did connect " 2 if the user was already connected " fun! LimpBridge_connect(...) if s:limp_bridge_connected == 1 echom "Already connected to Lisp!" return 2 endif if a:0 == 1 && a:1 != "" " format: 7213.limp_listener-foo let pid = a:1[:stridx(a:1, '.')-1] let fullname = a:1[stridx(a:1, '.')+1:] let name = fullname[strlen("limp_listener-"):] let s:limp_bridge_channel = s:limp_bridge_channel_base.name.".".pid else let s:limp_bridge_channel = s:limp_bridge_channel_base let name = input("Connect to [boot new]: ", "", "customlist,LimpBridge_complete_lisp") if name == "" return -1 endif let s:limp_bridge_channel .= name if 0 == filewritable(s:limp_bridge_channel) "|| s:limp_bridge_channel = s:limp_bridge_channel_base echom "Not a Limp channel." return 0 endif endif " extract the PID from format: foo.104982 " (backward from screen sty naming to ease tab completion) " bridge id is the file used for communication between Vim and screen let s:limp_bridge_id = strpart(s:limp_bridge_channel, strlen(s:limp_bridge_channel_base)) " bridge screenid is the screen in which the Lisp is running let s:limp_bridge_screenid = s:limp_bridge_id[strridx(s:limp_bridge_id, '.')+1:] "let s:limp_bridge_scratch = $HOME . "/.limp_bridge_scratch-" . s:limp_bridge_id let s:limp_bridge_test = $HOME . '/.limp_bridge_test-' . s:limp_bridge_id silent exe "new" s:limp_bridge_channel if exists( "#BufEnter#*.lisp#" ) doauto BufEnter x.lisp endif setlocal syntax=lisp " XXX: in ViLisp, buftype=nowrite, but w/ limp_bridge_channel, vim " complains about the file being write-only. "setlocal buftype=nowrite setlocal bufhidden=hide setlocal nobuflisted setlocal noswapfile hide silent exe "new" s:limp_bridge_test if exists( "#BufEnter#*.lisp#" ) doauto BufEnter x.lisp endif setlocal syntax=lisp " setlocal buftype=nofile setlocal bufhidden=hide setlocal nobuflisted " setlocal noswapfile hide " hide from the user that we created and deleted (hid, really) a couple of " buffers "normal! redraw let s:limp_bridge_connected=1 echom "Welcome to Limp. May your journey be pleasant." return 1 endfun fun! LimpBridge_connection_status() if s:limp_bridge_connected == 1 return "Connected to ".s:limp_bridge_id else return "Disconnected" endif endfun fun! LimpBridge_disconnect() let s:limp_bridge_connected = 0 let s:limp_bridge_id = "" endfun " " optionally, specify the path of the core to save to " fun! LimpBridge_quit_lisp(...) " we were given a file if a:0 == 1 && a:1 != "" let core = a:1 call LimpBridge_send_to_lisp("(sb-ext:save-lisp-and-die \"".core."\")\n") echom "Lisp ".s:limp_bridge_id." is gone, core saved to ".core."." else call LimpBridge_send_to_lisp("(sb-ext:quit)\n") echom "Lisp ".s:limp_bridge_id." is gone." endif call LimpBridge_disconnect() endfun fun! LimpBridge_shutdown_lisp() if s:limp_bridge_connected == 1 let core = input("Name of core to save [none]: ", "", "file") call LimpBridge_quit_lisp(core) else echom "Not connected." endif endfun " " when not connected, start new or connect to existing " otherwise, switch to Lisp (screen) fun! LimpBridge_boot_or_connect_or_display() if s:limp_bridge_connected " is it still running? let status = system("screen -ls") if stridx(status, s:limp_bridge_screenid) == -1 call LimpBridge_disconnect() return endif let cmd = "screen -x ".s:limp_bridge_screenid if has("gui_running") || b:listener_always_open_window == 1 let cmd = "xterm -e " . cmd if b:listener_keep_open == 1 let cmd .= " &" endif endif silent exe "!".cmd redraw! else " connect to a fresh Lisp let what = LimpBridge_connect() if what <= 0 " user didn't want to connect, let's boot! let name = input("Name the Lisp: ") if strlen(name) == 0 " give up return endif let core = input("Path to core to boot [use system-default]: ", "", "file") let core_opt = "" if filereadable(core) let core_opt = "-c ".core echom "Booting ".core."..." else echom "Booting..." endif let styfile = tempname() let cmd = s:Limp_location . "/bin/lisp.sh ".core_opt."-s ".styfile." -b ".name call system(cmd) while getfsize(styfile) <= len("limp_listener") sleep 200m endwhile " needs to be binary, or readfile() expects a newline... let lines = readfile(styfile, 'b') if len(lines) < 1 echom "Error getting screen ID!" return endif let sty = lines[0] call delete(styfile) call LimpBridge_connect(sty) call LimpBridge_boot_or_connect_or_display() endif endif endfun augroup LimpBridge au! autocmd BufLeave .LimpBridge_* setlocal nobuflisted autocmd BufLeave *.lisp let g:limp_bridge_last_lisp = bufname( "%" ) augroup END "------------------------------------------------------------------- " plugin <-> function mappings "------------------------------------------------------------------- nnoremap LimpBootConnectDisplay :call LimpBridge_boot_or_connect_or_display() nnoremap LimpDisconnect :call LimpBridge_disconnect() nnoremap LimpShutdownLisp :call LimpBridge_shutdown_lisp() nnoremap EvalTop :call LimpBridge_eval_top_form() nnoremap EvalCurrent :call LimpBridge_eval_current_form() nnoremap EvalExpression :call LimpBridge_prompt_eval_expression() vnoremap EvalBlock :call LimpBridge_eval_block() nnoremap AbortReset :call LimpBridge_send_to_lisp( "ABORT\n" ) nnoremap AbortInterrupt :call LimpBridge_send_to_lisp( "" ) nnoremap TestCurrent :call LimpBridge_stuff_current_form() nnoremap TestTop :call LimpBridge_stuff_top_form() nnoremap LoadThisFile :call LimpBridge_send_to_lisp( "(load \"" . expand( "%:p" ) . "\")\n") nnoremap LoadAnyFile :call LimpBridge_send_to_lisp( "(load \"" . expand( "%:p:r" ) . "\")\n") nnoremap CompileFile :w! call LimpBridge_send_to_lisp("(compile-file \"".expand("%:p")."\")\n") " XXX: What's the proprer syntax for calling >1 Plug? ""nnoremap CompileAndLoadFile CompileFile LoadAnyFile nnoremap CompileAndLoadFile :w! call LimpBridge_send_to_lisp("(compile-file \"".expand("%:p")."\")\n") call LimpBridge_send_to_lisp( "(load \"" . expand( "%:p:r" ) . "\")\n") " Goto Test Buffer: " Goto Split: split current buffer and goto test buffer nnoremap GotoTestBuffer :call LimpBridge_goto_buffer_or_window(g:limp_bridge_test) nnoremap GotoTestBufferAndSplit :sb call LimpBridge_goto_buffer_or_window(g:limp_bridge_test) " Goto Last: return to g:limp_bridge_last_lisp, i.e. last buffer nnoremap GotoLastLispBuffer :call LimpBridge_goto_buffer_or_window(g:limp_bridge_last_lisp) " HyperSpec: nnoremap HyperspecExact :call LimpBridge_hyperspec("exact", 0) nnoremap HyperspecPrefix :call LimpBridge_hyperspec("prefix", 1) nnoremap HyperspecSuffix :call LimpBridge_hyperspec("suffix", 1) nnoremap HyperspecGrep :call LimpBridge_hyperspec("grep", 1) nnoremap HyperspecFirstLetterIndex :call LimpBridge_hyperspec("index", 0) nnoremap HyperspecFullIndex :call LimpBridge_hyperspec("index-page", 0) " Help Describe: ask Lisp about the current symbol nnoremap HelpDescribe :call LimpBridge_send_to_lisp("(describe '".expand("").")") "------------------------------------------------------------------- " library "------------------------------------------------------------------- " assume that all of the file has been loaded & defined once " if one of the functions are defined. if exists("*LimpBridge_goto_buffer_or_window") finish endif function! LimpBridge_goto_buffer_or_window( buff ) if -1 == bufwinnr( a:buff ) exe "hide bu" a:buff else exe bufwinnr( a:buff ) . "wincmd w" endif endfunction function! LimpBridge_get_pos() " what buffer are we in? let bufnr = bufnr( "%" ) " get current position let c_cur = virtcol( "." ) let l_cur = line( "." ) normal! H let l_top = line( "." ) let pos = bufnr . "|" . l_top . "," . l_cur . "," . c_cur " go back exe "normal! " l_cur . "G" . c_cur . "|" return( pos ) endfunction function! LimpBridge_goto_pos( pos ) let mx = '\(\d\+\)|\(\d\+\),\(\d\+\),\(\d\+\)' let bufnr = substitute( a:pos, mx, '\1', '' ) let l_top = substitute( a:pos, mx, '\2', '' ) let l_cur = substitute( a:pos, mx, '\3', '' ) let c_cur = substitute( a:pos, mx, '\4', '' ) silent exe "hide bu" bufnr silent exe "normal! " . l_top . "Gzt" . l_cur . "G" . c_cur . "|" endfunction function! LimpBridge_yank( motion ) let value = '' let p = LimpBridge_get_pos() silent! exec 'normal!' a:motion let new_p = LimpBridge_get_pos() " did we move? if p != new_p " go back silent! exec 'normal!' a:motion let old_l = @l exec 'normal! "ly' . a:motion let value = @l let @l = old_l endif call LimpBridge_goto_pos( p ) return( value ) endfunction " copy an expression to a buffer function! LimpBridge_send_sexp_to_buffer( sexp, buffer ) let p = LimpBridge_get_pos() " go to the given buffer, go to the bottom exe "hide bu" a:buffer silent normal! G " tried append() -- doesn't work the way I need it to let old_l = @l let @l = a:sexp silent exe "put l" " normal! "lp let @l = old_l call LimpBridge_goto_pos( p ) endfunction " destroys contents of LimpBridge_channel buffer function! LimpBridge_send_to_lisp( sexp ) if a:sexp == '' return endif if !s:limp_bridge_connected echom "Not connected to Lisp!" return endif let p = LimpBridge_get_pos() " goto LimpBridge_channel, delete it, put s-exp, write it to lisp try exe "hide bu" s:limp_bridge_channel exe "%d" normal! 1G " tried append() -- doesn't work the way I need it to let old_l = @l let @l = a:sexp normal! "lP let @l = old_l silent exe 'w!' call system('screen -x '.s:limp_bridge_screenid.' -p 0 -X eval "readbuf" "paste ."') catch /^Vim:E211:/ echom "Lisp is gone!" " file not available, Lisp disappeared call LimpBridge_disconnect() endtry call LimpBridge_goto_pos( p ) endfunction function! LimpBridge_prompt_eval_expression() let whatwhat = input("Eval: ") call LimpBridge_send_to_lisp(whatwhat) endfun " Actually evals current top level form function! LimpBridge_eval_top_form() " save position let p = LimpBridge_get_pos() silent! exec "normal! 99[(" call LimpBridge_send_to_lisp( LimpBridge_yank( "%" ) ) " fix cursor position, in case of error below call LimpBridge_goto_pos( p ) endfunction function! LimpBridge_eval_current_form() " save position let pos = LimpBridge_get_pos() " find & yank current s-exp normal! [( let sexp = LimpBridge_yank( "%" ) call LimpBridge_send_to_lisp( sexp ) call LimpBridge_goto_pos( pos ) endfunction function! LimpBridge_eval_block() range " save position let pos = LimpBridge_get_pos() " yank current visual block let old_l = @l '<,'> yank l let sexp = @l let @l = old_l call LimpBridge_send_to_lisp( sexp ) call LimpBridge_goto_pos( pos ) endfunction function! LimpBridge_stuff_current_form() " save position let pos = LimpBridge_get_pos() " find & yank current s-exp normal! [( call LimpBridge_send_sexp_to_buffer( LimpBridge_yank( "%" ), s:limp_bridge_test ) call LimpBridge_goto_pos( pos ) endfunction function! LimpBridge_stuff_top_form() " save position let pos = LimpBridge_get_pos() " find & yank top-level s-exp silent! exec "normal! 99[(" call LimpBridge_send_sexp_to_buffer( LimpBridge_yank( "%" ), s:limp_bridge_test ) call LimpBridge_goto_pos( pos ) endfunction function! LimpBridge_hyperspec(type, make_page) " get current word under cursor let word = expand( "" ) let cmd = "! perl " . s:Limp_location . "/bin/limp-hyperspec.pl" let cmd = cmd . " " . a:type . " " . a:make_page . " '" . word . "'" silent! exe cmd redraw! endfunction