511 lines
16 KiB
VimL
511 lines
16 KiB
VimL
"
|
||
" 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 <mail@mikael.jansson.be>
|
||
" Larry Clapp <vim@theclapp.org>
|
||
|
||
" Changelog:
|
||
" 2008-08-26 by Mikael Jansson <mail@mikael.jansson.be>
|
||
" * Optionally specify core at startup and exit.
|
||
"
|
||
" 2008-08-25 by Mikael Jansson <mail@mikael.jansson.be>
|
||
" * 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 <mail@mikael.jansson.be>
|
||
" * 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 = "<disconnected>"
|
||
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 <silent> <buffer> <Plug>LimpBootConnectDisplay :call LimpBridge_boot_or_connect_or_display()<CR>
|
||
nnoremap <silent> <buffer> <Plug>LimpDisconnect :call LimpBridge_disconnect()<CR>
|
||
nnoremap <silent> <buffer> <Plug>LimpShutdownLisp :call LimpBridge_shutdown_lisp()<CR>
|
||
|
||
nnoremap <silent> <buffer> <Plug>EvalTop :call LimpBridge_eval_top_form()<CR>
|
||
nnoremap <silent> <buffer> <Plug>EvalCurrent :call LimpBridge_eval_current_form()<CR>
|
||
nnoremap <silent> <buffer> <Plug>EvalExpression :call LimpBridge_prompt_eval_expression()<CR>
|
||
|
||
vnoremap <silent> <buffer> <Plug>EvalBlock :call LimpBridge_eval_block()<cr>
|
||
|
||
nnoremap <silent> <buffer> <Plug>AbortReset :call LimpBridge_send_to_lisp( "ABORT\n" )<CR>
|
||
nnoremap <silent> <buffer> <Plug>AbortInterrupt :call LimpBridge_send_to_lisp( "" )<CR>
|
||
|
||
nnoremap <silent> <buffer> <Plug>TestCurrent :call LimpBridge_stuff_current_form()<CR>
|
||
nnoremap <silent> <buffer> <Plug>TestTop :call LimpBridge_stuff_top_form()<CR>
|
||
|
||
nnoremap <silent> <buffer> <Plug>LoadThisFile :call LimpBridge_send_to_lisp( "(load \"" . expand( "%:p" ) . "\")\n")<CR>
|
||
nnoremap <silent> <buffer> <Plug>LoadAnyFile :call LimpBridge_send_to_lisp( "(load \"" . expand( "%:p:r" ) . "\")\n")<CR>
|
||
|
||
nnoremap <silent> <buffer> <Plug>CompileFile :w! <bar> call LimpBridge_send_to_lisp("(compile-file \"".expand("%:p")."\")\n")<CR>
|
||
|
||
" XXX: What's the proprer syntax for calling >1 Plug?
|
||
""nnoremap <buffer> <Plug>CompileAndLoadFile <Plug>CompileFile <bar> <Plug>LoadAnyFile
|
||
nnoremap <silent> <buffer> <Plug>CompileAndLoadFile :w! <bar> call LimpBridge_send_to_lisp("(compile-file \"".expand("%:p")."\")\n") <bar> call LimpBridge_send_to_lisp( "(load \"" . expand( "%:p:r" ) . "\")\n")<CR>
|
||
|
||
" Goto Test Buffer:
|
||
" Goto Split: split current buffer and goto test buffer
|
||
nnoremap <silent> <buffer> <Plug>GotoTestBuffer :call LimpBridge_goto_buffer_or_window(g:limp_bridge_test)<CR>
|
||
nnoremap <silent> <buffer> <Plug>GotoTestBufferAndSplit :sb <bar> call LimpBridge_goto_buffer_or_window(g:limp_bridge_test)<CR>
|
||
|
||
" Goto Last: return to g:limp_bridge_last_lisp, i.e. last buffer
|
||
nnoremap <silent> <buffer> <Plug>GotoLastLispBuffer :call LimpBridge_goto_buffer_or_window(g:limp_bridge_last_lisp)<CR>
|
||
|
||
" HyperSpec:
|
||
nnoremap <silent> <buffer> <Plug>HyperspecExact :call LimpBridge_hyperspec("exact", 0)<CR>
|
||
nnoremap <silent> <buffer> <Plug>HyperspecPrefix :call LimpBridge_hyperspec("prefix", 1)<CR>
|
||
nnoremap <silent> <buffer> <Plug>HyperspecSuffix :call LimpBridge_hyperspec("suffix", 1)<CR>
|
||
nnoremap <silent> <buffer> <Plug>HyperspecGrep :call LimpBridge_hyperspec("grep", 1)<CR>
|
||
nnoremap <silent> <buffer> <Plug>HyperspecFirstLetterIndex :call LimpBridge_hyperspec("index", 0)<CR>
|
||
nnoremap <silent> <buffer> <Plug>HyperspecFullIndex :call LimpBridge_hyperspec("index-page", 0)<CR>
|
||
|
||
" Help Describe: ask Lisp about the current symbol
|
||
nnoremap <silent> <buffer> <Plug>HelpDescribe :call LimpBridge_send_to_lisp("(describe '".expand("<cword>").")")<CR>
|
||
|
||
|
||
"-------------------------------------------------------------------
|
||
" 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( "<cword>" )
|
||
let cmd = "! perl " . s:Limp_location . "/bin/limp-hyperspec.pl"
|
||
let cmd = cmd . " " . a:type . " " . a:make_page . " '" . word . "'"
|
||
silent! exe cmd
|
||
redraw!
|
||
endfunction
|
||
|