khorser avatar khorser committed d170f75

Make writing to the repl buffer more robust, expose a function to send text to
REPL

Comments (0)

Files changed (2)

autoload/repl.vim

-" REPL plugin to interact with interpreters for various programming languages
+"y REPL plugin to interact with interpreters for various programming languages
 " Author: Sergey Khorev <sergey.khorev@gmail.com>
 " Last Change:	$HGLastChangedDate$
 
   try
     call vimproc#version()
   catch
-    echoerr 'Error running vimproc:' v:exception '. Is it installed?'
+    echoerr 'Error running vimproc:' v:exception '. Please check it has been built and installed'
     return
   endtry
 
   let s:ReplFullyInitialized = 1
   augroup REPL
-    " TODO setup more autocommand for async updates
-    autocmd CursorHold * call <SID>ReadFromRepl()
+    " TODO setup more autocommand for async updates, CursorMoved?
+    autocmd CursorHold,InsertLeave * call <SID>ReadFromRepl()
   augroup END
 
-  vnoremap <silent> <Plug>EvalSelection :call <SID>EvalSelection()<cr>
-  nnoremap <silent> <Plug>EvalLine :call <SID>EvalLine()<cr>
+  vnoremap <silent> <Plug>EvalSelection :call repl#SendText('', repl#GetSelection())<cr>
+  nnoremap <silent> <Plug>EvalLine :call repl#SendText('', getline('.'))<cr>
 
   if !hasmapto('<Plug>EvalSelection')
     vmap <unique> <silent> <Leader>e <Plug>EvalSelection
     if l:next != [0, 0]
       " delete previous output
       let l:from = l:current.line2 + 1
-      let l:to = l:next[0] - 2
+      let l:to = l:next[0] - 1
       exec 'silent' l:from ','  l:to 'delete _'
     endif
-    call s:SendToRepl(join(l:current.lines, b:replinfo.join), 0, 0)
+    call s:SendToRepl(join(l:current.lines, b:replinfo.join), 0, 0, bufnr(''))
   endif
 endfunction
 
   call filter(s:replbufs, '!empty(v:val)')
 endfunction
 
-function! s:FindReplBuffer(type)
+function! s:FindReplBufferWithType(type)
   call s:CleanupDeadBuffers()
   let l:b = 0
   if exists('b:replbuf') && bufexists(b:replbuf)
   elseif exists('s:replbufs["'.a:type.'"]')
     let l:b = s:replbufs[a:type][0]
   endif
-  if l:b > 0 && bufwinnr(l:b) == -1 " window not visible
-    exec getbufvar(l:b, 'replinfo').split 'sbuffer'
-    wincmd W
-  endif
   return l:b
-endfunction " FindReplBuffer
+endfunction " FindReplBufferWithType
 
 function! s:NewReplBuffer(args, type)
   call s:CleanupDeadBuffers()
   " populate REPL info using default entry as a prototype
   let l:replinfo = deepcopy(g:ReplDefaults)
   call extend(l:replinfo, g:ReplTypes[a:type], 'force')
-  call extend(l:replinfo, {'curpos': 1, 'ready': 0, 'more': '', 'type': a:type})
+  call extend(l:replinfo, {'curpos': 1, 'markerpending': 0, 'echo': [], 'type': a:type})
 
   try
     let l:replinfo.proc = vimproc#popen2(l:replinfo.command . ' ' . a:args)
   exec l:replinfo.split 'new'
   let b:replinfo = l:replinfo
 
-  call s:SendToRepl(b:replinfo.init, 0, 0)
+  let l:buf = bufnr('')
+  call s:SendToRepl(b:replinfo.init, 0, 0, l:buf)
 
-  let l:buf = bufnr('')
   if exists('s:replbufs["'.a:type.'"]')
     let l:bufs = s:replbufs[a:type]
   else
   if a:new
     let l:b = 0
   else
-    let l:b = s:FindReplBuffer(a:type)
+    let l:b = s:FindReplBufferWithType(a:type)
+    if l:b > 0 && bufwinnr(l:b) == -1 " window not visible
+      exec getbufvar(l:b, 'replinfo').split 'sbuffer' l:b
+      wincmd W
+    endif
   endif
   if !l:b
     let l:b = s:NewReplBuffer(a:args, a:type)
       endif
     endfor
   endfor
-endfunction " ReadFromRepl
+endfunction
+
+" Manipulate text to imitate command line experience
+function! s:EnrichText(text)
+  let l:prompt = b:replinfo.prompt
+  if b:replinfo.markerpending
+    if !empty(b:replinfo.echo)
+      let l:echo = remove(b:replinfo.echo, 0)
+    else
+      let l:echo = ''
+    endif
+    call extend(a:text, [l:echo, b:replinfo.outmarker], 0)
+    let b:replinfo.markerpending = 0
+  endif
+  let l:promptPos = match(a:text, l:prompt)
+  let a:text[0] = getline(b:replinfo.curpos) . a:text[0]
+  while l:promptPos > -1
+    let l:line = a:text[l:promptPos]
+    let l:promptEnd = match(l:line, l:prompt . '\m\zs')
+    if !empty(b:replinfo.echo)
+      let l:echo = remove(b:replinfo.echo, 0)
+    else
+      let l:echo = ''
+    endif
+    let a:text[l:promptPos] = l:line[: l:promptEnd] . l:echo
+    if l:promptPos == len(a:text) - 1
+      let b:replinfo.markerpending = 1
+    else " more text after the prompt
+      call extend(a:text,
+            \ [b:replinfo.outmarker, strpart(l:line, l:promptEnd+1)],
+            \ l:promptPos+1)
+    endif
+    let l:promptPos = match(a:text, l:prompt, l:promptPos + 1)
+  endwhile
+endfunction
 
 function! s:WriteToBuffer(buf, text)
-  let l:src = bufwinnr('')
-  if !empty(a:text)
-    exec bufwinnr(a:buf) 'wincmd w'
-    let l:text = split(a:text, '\m[\r]\?\n', 1)
-    if b:replinfo.curpos < 1
-      let b:replinfo.curpos = line('$')
-    endif
-    if b:replinfo.ready
-      " new round of interaction
-      if !empty(b:replinfo.more)
-        call setline(b:replinfo.curpos, getline(b:replinfo.curpos) . b:replinfo.more)
-        let b:replinfo.more = ''
-      endif
-
-      let b:replinfo.ready = 0
-      call append(b:replinfo.curpos, b:replinfo.outmarker)
-      let b:replinfo.curpos += 1
-      call append(b:replinfo.curpos, l:text)
-      let b:replinfo.curpos += len(l:text)
-    else
-      let l:text[0] = getline(b:replinfo.curpos) . l:text[0]
-      call setline(b:replinfo.curpos, l:text)
-      let b:replinfo.curpos += len(l:text) - 1
-    endif
-    if b:replinfo.scroll
-      call cursor(b:replinfo.curpos, len(getline(b:replinfo.curpos)))
-    endif
-    if !b:replinfo.ready
-      let l:prompt = b:replinfo.prompt
-      if matchstr(l:text[-1], l:prompt) != ''
-            \ || matchstr(getline(line(b:replinfo.curpos)), l:prompt) != ''
-        " if the last output line is our prompt
-        let b:replinfo.ready = 1
-        " don't print prompt if the line is not the last one
-        if b:replinfo.curpos < line('$')
-          let l:from = b:replinfo.curpos - 1
-          let l:to = b:replinfo.curpos
-          exec 'silent' l:from ',' l:to 'delete _'
-          call s:EndOfCurrOrPrevPrompt(1) " return to the command
-          call s:GoToEndOfNextOrCurrentCommand()
-        endif
-      endif
-    endif
-    exec l:src 'wincmd w'
-  endif
-endfunction "WriteToBuffer
-
-function! s:SendToRepl(text, echo, append)
-  call s:CleanupDeadBuffers()
   if empty(a:text)
     return
   endif
+
+  let l:src = bufwinnr('')
+  exec bufwinnr(a:buf) 'wincmd w'
+  let l:text = split(a:text, '\m[\r]\?\n', 1)
+  if b:replinfo.curpos < 1
+    let b:replinfo.curpos = line('$')
+  endif
+
+  call s:EnrichText(l:text)
+  call setline(b:replinfo.curpos, l:text[0])
+  call append(b:replinfo.curpos, l:text[1:])
+
+  let b:replinfo.curpos += len(l:text) - 1
+  if b:replinfo.scroll
+    call cursor(b:replinfo.curpos, len(getline(b:replinfo.curpos)))
+  endif
+
+  " if the last output line is our prompt
+  if b:replinfo.markerpending && b:replinfo.curpos < line('$')
+    " delete the prompt if the line is not the last one
+    exec 'silent' b:replinfo.curpos 'delete _'
+    call s:EndOfCurrOrPrevPrompt(1) " return to the command
+    call s:GoToEndOfNextOrCurrentCommand()
+  endif
+  exec l:src 'wincmd w'
+endfunction "WriteToBuffer
+
+function! s:FindReplBuffer(bufOrType)
+  if type(a:bufOrType) == type(0)
+    return a:bufOrType
+  elseif !empty(a:bufOrType)
+    return s:FindReplBufferWithType(a:bufOrType)
+  endif
+  let l:b = 0
   if exists('b:replinfo')
     let l:b = bufnr('')
   elseif exists('b:replbuf')
       endif
     endfor
   endif
+  return l:b
+endfunction " FindReplBuffer
+
+function! s:SendToRepl(text, echo, append, bufOrType)
+  call s:CleanupDeadBuffers()
+  if empty(a:text)
+    return
+  endif
+  let l:b = s:FindReplBuffer(a:bufOrType)
   if !s:IsBufferValid(l:b)
     echoerr 'REPL is not connected'
     return
     let l:text = string(a:text)
   endif
   if a:echo
-    let l:info.more = l:text
+    call add(l:info.echo, l:text)
   endif
   let l:proc = l:info.proc
   if l:proc.is_valid && !l:proc.stdin.eof
   endif
 endfunction " SendToRepl
 
-function! s:EvalSelection()
-  let l:lines = getline(line("'<"), line("'>"))
-  let l:lines[0] = l:lines[0][col("'<")-1 : ]
-  let l:lines[-1] = l:lines[-1][: col("'>")-1]
-  call s:SendToRepl(l:lines, 1, 1)
-endfunction " EvalSelection
+function! repl#GetSelection()
+  let l:savereg = ['r', getreg('r'), getregtype('r')]
+  normal! gv"ry
+  let l:text = split(getreg('r'), '\n')
+  call call('setreg', l:savereg)
+  return l:text
+endfunction
 
-function! s:EvalLine()
-  call s:SendToRepl(getline('.'), 1, 1)
-endfunction " EvalLine
+function! repl#SendText(bufOrType, text) range
+  call s:SendToRepl(a:text, 1, 1, a:bufOrType)
+endfunction
 
 " vim: set ts=8 sw=2 sts=2 et:
 " Keybinding and syntax highlighting heavily use specific markers,
 "   you tamper with the markers on your own risk
 "
+" Functions:
+" The following function can be used to define your own mappings or
+" autocommands:
+" repl#SendText(bufOrType, text)
+"   bufOrType: buffer number or its type ('' means currently active buffer)
+"   text: a list of strings to join and send to REPL
+"   NOTE: the function will not open a REPL window
+"
+" e.g. to show the type of the expression in the current line in GHCi
+"   (in real life you probably would want to use ghc-mod)
+"   nmap <leader>t :call repl#SendText('GHCi', [':t', getline('.')])<cr>
+"
+" show information about visual selection:
+"   vmap <leader>i :call repl#SendText('GHCi', insert(repl#GetSelection(), ':i'))<cr>
+"
 " Hints:
 " You may edit and re-execute commands and the plugin should update output
 "   using markers
 " Also feel free to delete unneeded text, just try to keep the layout
-" Session transcript can be saved with "1,$w YourFileName"
+" The full transcript of the session (except deleted lines) can be saved with
+"     "1,$w YourFileName"
+"
+" Autocommands can be used to add settings specific to particular interpreters
+" like:
+"   autocmd FileType repl :if expand('<afile>')=~#'^\d\+GHCi$' | <setup GHCi mappings> | endif  
+"
 "
 " Customisation:
 " Define g:replUserDefaults and g:replUserTypes
       \ 'init'    : '',
       \ 'prompt'  : '\m\C^(%i\d\+)',
       \ 'syntax'  : 'maxima'}
-  \, 'Ocaml':
+  \, 'OCaml':
       \{'command' : 'ocaml',
       \ 'init'    : 'Toploop.read_interactive_input := let old = !Toploop.read_interactive_input in fun prompt buffer len -> old "\nocaml> " buffer len ;;',
       \ 'prompt'  : '\m\C^ocaml>',
       \ 'syntax'  : 'tcsh'}
   \ }
 
+"  \, 'Lambdabot':
+"      \{'command' : 'lambdabot',
+"      \ 'init'    : '',
+"      \ 'prompt'  : '\m\C^lambdabot>',
+"      \ 'syntax'  : 'haskell'}
+
 " Not operational:
 "  How can we change nested prompt or disable nested at all?
 "  \, 'Guile':
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.