diff --git a/README.rst b/README.rst index 5b08abd..12e07a3 100644 --- a/README.rst +++ b/README.rst @@ -3,7 +3,7 @@ Vimteractive ============ :vimteractive: send commands from text files to interactive programs via vim :Author: Will Handley -:Version: 1.7 +:Version: 2.0 :Homepage: https://github.com/williamjameshandley/vimteractive :Documentation: ``:help vimteractive`` @@ -31,7 +31,13 @@ The activating commands are - autodetect based on filetype ``:Iterm`` Commands may be sent from a text file to the chosen terminal using -``CTRL-S``. +``CTRL-S``. If there is no terminal, ``CTRL-S`` will automatically +open one for you using ``:Iterm``. + +It's highly recommended to set your default Python shell to IPython +(see "Extending functionality" section for instructions). If you prefer +not to do it, make sure, that every top-level block has at least one newline +after it. Installation ------------ @@ -122,6 +128,10 @@ interactively into the prompt as if you had run it in the command line. You can save this buffer if you wish to a new file if it contains valuable output +You may want to send lines to one terminal from two buffers. To achieve that, +run ``:Iconn `` where ```` is a name of buffer +containing terminal. If there is only one terminal, you can use just ``:Iconn``. + Supported terminals ~~~~~~~~~~~~~~~~~~~ @@ -171,9 +181,10 @@ in your ``.vimrc``: \ } " If you see strange symbols like ^[[200~ when sending lines - " to your new interpreter, disable bracketed paste for it - " It's not needed for python3 -m asyncio, code below is only - " an example. + " to your new interpreter, disable bracketed paste for it. + " You can also try it when your shell is misbehaving some way. + " It's needed for any standard Python REPL including + " python3 -m asyncio let g:vimteractive_bracketed_paste = { \ 'asyncpython': 0 \ } @@ -185,6 +196,14 @@ in your ``.vimrc``: \ 'python': 'asyncpython' \ } + " If your interpreter startup time is big, you may want to + " wait before sending commands. Set time in milliseconds in + " this dict to do it. This is not needed for python3, but + " can be useful for other REPLs like Clojure. + let g:vimteractive_slow_prompt = { + \ 'asyncpython': 200 + \ } + Similar projects ---------------- @@ -200,6 +219,7 @@ Similar projects Changelist ---------- +:v2.0: `Multiple terminal functionality `__ :v1.7: `Autodetection of terminals `__ :v1.6: CtrlP `bugfix `__ :v1.5: Added julia support diff --git a/autoload/vimteractive.vim b/autoload/vimteractive.vim index 9f224ab..17b9873 100644 --- a/autoload/vimteractive.vim +++ b/autoload/vimteractive.vim @@ -1,66 +1,190 @@ +" Vimteractive implementation +" +" Variables +" s:vimteractive_buffers +" script-local variable that keeps track of vimteractive terminal buffers +" +" b:vimteractive_connected_term +" buffer-local variable held by buffer that indicates the name of the +" connected terminal buffer +" +" b:vimteractive_term_type +" buffer-local variable held by terminal buffer that indicates the terminal type + + +" Initialise the list of terminal buffer numbers on startup +if !exists('s:vimteractive_buffers') + let s:vimteractive_buffers = [] +end + +" Remove a terminal from the list on deletion. +function! s:del_term() + let l:term_bufname = expand('') + let l:term_bufnr = bufnr(term_bufname) + let l:term_index = index(s:vimteractive_buffers, l:term_bufnr) + if l:term_index >= 0 + call remove(s:vimteractive_buffers, l:term_index) + endif +endfunction + + +" Reopen a terminal buffer in a split window if necessary +function! s:show_term() + let l:open_bufnrs = map(range(1, winnr('$')), 'winbufnr(v:val)') + if index(l:open_bufnrs, bufnr(b:vimteractive_connected_term)) == -1 + split + execute ":b " . b:vimteractive_connected_term + wincmd p + endif +endfunction + + +" Generate a new terminal name +function! s:new_name(term_type) + " Create a new terminal name + let l:term_bufname = "term_" . a:term_type + let i = 1 + while bufnr(l:term_bufname) != -1 + let l:term_bufname = "term_" . a:term_type . '_' . i + let i += 1 + endwhile + return l:term_bufname +endfunction + + +" Listen for Buffer close events if they're in the terminal list +autocmd BufDelete * call del_term() + + +" List all running terminal names +function! vimteractive#buffer_list(...) + let l:vimteractive_buffers = copy(s:vimteractive_buffers) + return map(l:vimteractive_buffers, 'bufname(v:val)') +endfunction + + " Send list of lines to the terminal buffer, surrounded with a bracketed paste function! vimteractive#sendlines(lines) - if get(g:vimteractive_bracketed_paste, g:current_term_type, 1) - call term_sendkeys(g:vimteractive_buffer_name,"[200~" . join(a:lines, "\n") . "[201~\n") + " Autostart a terminal if desired + if !exists("b:vimteractive_connected_term") + if g:vimteractive_autostart + call vimteractive#term_start('-auto-') + else + echoerr "No terminal connected." + echoerr "call :Iterm to start a new one, or :Iconn to connect to an existing one" + return + endif + endif + + " Check if connected terminal is still alive + if index(s:vimteractive_buffers, b:vimteractive_connected_term) == -1 + echoerr "Vimteractive terminal " . b:vimteractive_connected_term . " has been deleted" + echoerr "call :Iterm to start a new one, or :Iconn to connect to an existing one" + return + endif + + call s:show_term() + + let l:term_type = getbufvar(b:vimteractive_connected_term, "term_type") + + mark` + if get(g:vimteractive_bracketed_paste, l:term_type, 1) + call term_sendkeys(b:vimteractive_connected_term,"[200~" . join(a:lines, "\n") . "[201~\n") else - call term_sendkeys(g:vimteractive_buffer_name, join(a:lines, "\n") . "\n") + call term_sendkeys(b:vimteractive_connected_term, join(a:lines, "\n") . "\n") endif endfunction -" Send a line to the terminal buffer -function! vimteractive#sendline(line) - call vimteractive#sendlines([a:line]) -endfunction - -" Start a vimteractive session -function! vimteractive#session(terminal_type) +" Start a vimteractive terminal +function! vimteractive#term_start(term_type) if has('terminal') == 0 echoerr "Your version of vim is not compiled with +terminal. Cannot use vimteractive" return endif - if a:terminal_type ==# '-auto-' - let term_type = get(g:vimteractive_default_shells, &filetype, &filetype) - if has_key(g:vimteractive_commands, term_type) - let l:terminal = get(g:vimteractive_commands, term_type) - let g:current_term_type = term_type - else - echoerr "Cannot determine terminal commmand for filetype " . &filetype - return - endif + " Determine the terminal type + if a:term_type ==# '-auto-' + let l:term_type = get(g:vimteractive_default_shells, &filetype, &filetype) else - let l:terminal = get(g:vimteractive_commands, a:terminal_type) - let g:current_term_type = a:terminal_type + let l:term_type = a:term_type endif - if g:vimteractive_terminal != '' && g:vimteractive_terminal != l:terminal - echoerr "Cannot run: " . l:terminal " Alreading running: " . g:vimteractive_terminal + " Retrieve starting command + if has_key(g:vimteractive_commands, l:term_type) + let l:term_command = get(g:vimteractive_commands, l:term_type) + else + echoerr "Cannot determine terminal commmand for type " . l:term_type return endif - if bufnr(g:vimteractive_buffer_name) == -1 - " If no vimteractive buffer exists: - " Start the terminal - let job = term_start(l:terminal, {"term_name":g:vimteractive_buffer_name}) - set nobuflisted " Unlist the buffer - set norelativenumber " Turn off line numbering if off - set nonumber " Turn off line numbering if off - wincmd p " Return to the previous window - let g:vimteractive_terminal = l:terminal " Name the current terminal - - elseif bufwinnr(g:vimteractive_buffer_name) == -1 - " Else if vimteractive buffer not open: - " Split the window - split - " switch the top window to the vimteractive buffer - execute ":b " . g:vimteractive_buffer_name - " Return to the previous window - wincmd p + " Create a new term + echom "Starting " . l:term_command + let l:term_bufname = s:new_name(l:term_type) + call term_start(l:term_command, { + \ "term_name": l:term_bufname, + \ "term_finish": "close", + \ "term_kill": "term" + \ }) - else - " Else if throw error - echoerr "vimteractive already open. Quit before opening a new buffer" + " Add this terminal to the buffer list, and store type + call add(s:vimteractive_buffers, bufnr(l:term_bufname)) + let b:vimteractive_term_type = l:term_type + + " Turn line numbering off + set nonumber norelativenumber + " Switch to terminal-normal mode when entering buffer + autocmd BufEnter call feedkeys("\N") + " Switch to insert mode when leaving buffer + autocmd BufLeave execute "silent! normal! i" + " Make :quit really do the right thing + cabbrev q bdelete! " + cabbrev qu bdelete! " + cabbrev qui bdelete! " + cabbrev quit bdelete! " + " Return to previous window + wincmd p + + " Store name and type of current buffer + let b:vimteractive_connected_term = bufnr(l:term_bufname) + + " Pause as necessary + while term_getline(b:vimteractive_connected_term, 1) == '' + sleep 10m " Waiting for prompt + endwhile + if get(g:vimteractive_slow_prompt, l:term_type) + execute "sleep " . l:slow . "m" endif + redraw + endfunction + +" Connect to vimteractive terminal +function! vimteractive#connect(bufname = '') + if len(s:vimteractive_buffers) == 0 + echoerr "No vimteractive terminal buffers present" + echoerr "call :Iterm to start a new one" + return + endif + + let l:bufname = a:bufname + if strlen(a:bufname) ==# 0 + if len(s:vimteractive_buffers) ==# 1 + let l:bufname = vimteractive#buffer_list()[0] + else + echom "Please specify terminal from " + echom vimteractive#buffer_list() + return + endif + endif + + if !bufexists(l:bufname) + echoerr "Buffer " . l:bufname . " is not found or already disconnected" + return + endif + + let b:vimteractive_connected_term = bufnr(l:bufname) + echom "Connected " . bufname("%") . " to " . l:bufname + +endfunction diff --git a/doc/vimteractive.txt b/doc/vimteractive.txt index 198f7be..51f419d 100644 --- a/doc/vimteractive.txt +++ b/doc/vimteractive.txt @@ -37,7 +37,16 @@ The activating commands are - clojure |:Iclojure| - zsh |:Izsh| -You can also let Vimteractive detect interpreter using |:Iterm| +You can also let Vimteractive detect interpreter using |:Iterm| or just send +some lines: Vimteractive will create terminal if needed. Note: it's highly +recommended to use IPython as your default Python interpreter. You can set it +like this: + + let g:vimteractive_default_shells = { 'python': 'ipython' } + +Default Python REPL support for pasting is really bad and you should use +IPython whenever possible. If you need to use default Python REPL, you must +put newline after every top-level block. Commands may be sent from a text file to the chosen terminal using CTRL-S. See |v_CTRL_S| for more details. @@ -87,6 +96,9 @@ enter the terminal, and be able to enter commands interactively into the prompt as if you had run it in the command line. You can save this buffer if you wish to a new file if it contains valuable output +By default every buffer is connected to separate terminal. If you want to +connect two buffers to one terminal, use |:Iconn| command. + Supported terminals *vimteractive-terminals* @@ -113,8 +125,17 @@ In |Visual-mode|, CTRL-S sends all currently selected lines to the terminal. ALT-S sends all lines from the start to the current line. +If there is no active terminal for current buffer, CTRL-S will automatically +create one for you using |:Iterm|. + ============================================================================== -3. Extending functionality *vimteractive-extending* +3. Connecting to existing REPLs *:Iconn* *vimteractive-connecting* +:Iconn [{buffer}] Connect current buffer to REPL in {buffer}. You can + connect any number of buffers to one REPL. {buffer} + can be omited if there is only one terminal. + +============================================================================== +4. Extending functionality *vimteractive-extending* To add a new interpreter to Vimteractive, you should define g:vimteractive_commands variable. For example: @@ -131,13 +152,19 @@ you may try to disable bracketed paste mode for it: let g:vimteractive_bracketed_paste = { 'pythonasync': 0 } +If your interpreter has slow-starting REPL (like Clojure), you may want to +wait before sending data to it at the first time. Specify time to wait in +milliseconds like this: + + let g:vimteractive_slow_prompt = { 'pythonasync': 200 } + This project is very much in an alpha phase, so if you have any issues that arise on your system, feel free to contact me: williamjameshandley@gmail.com. ============================================================================== -4. About *vimteractive-functionality* +5. About *vimteractive-functionality* The core maintainer of vimteractive is: @@ -148,6 +175,6 @@ Find the latest version of vimteractive at: http://github.com/williamjameshandley/vimteractive ============================================================================== -5. License *vimteractive-license* +6. License *vimteractive-license* Vimteractive is licensed under GPL 3.0 diff --git a/plugin/vimteractive.vim b/plugin/vimteractive.vim index 4ec198e..5c7196f 100644 --- a/plugin/vimteractive.vim +++ b/plugin/vimteractive.vim @@ -9,13 +9,12 @@ " Plugin variables " ================ -" Name of the vimteractive terminal buffer -let g:vimteractive_buffer_name = "vimteractive_buffer" -let g:vimteractive_terminal = '' -let g:current_term_type = '' +" Automatically start default terminal on first ^S +if !has_key(g:, 'vimteractive_autostart') + let g:vimteractive_autostart = 1 +endif " Variables for running the various sessions -" if !has_key(g:, 'vimteractive_commands') let g:vimteractive_commands = { } endif @@ -38,42 +37,48 @@ if !has_key(g:, 'vimteractive_default_shells') endif " If 0, disable bracketed paste escape sequences -let g:vimteractive_bracketed_paste = { - \ 'clojure': 0, - \ 'python': 0, - \ 'python2': 0, - \ 'python3': 0, - \ } +if !has_key(g:, 'vimteractive_bracketed_paste') + let g:vimteractive_bracketed_paste = { } +endif +let g:vimteractive_bracketed_paste.clojure = 0 +let g:vimteractive_bracketed_paste.python = 0 +let g:vimteractive_bracketed_paste.python2 = 0 +let g:vimteractive_bracketed_paste.python3 = 0 + +" If present, wait this amount of time in ms when starting term on ^S +if !has_key(g:, 'vimteractive_slow_prompt') + let g:vimteractive_slow_prompt = { } +endif +let g:vimteractive_slow_prompt.clojure = 200 + +" Plugin commands +" =============== if !has_key(g:, 'vimteractive_loaded') let g:vimteractive_loaded = 1 " Building :I* commands (like :Ipython, :Iipython and so) for term_type in keys(g:vimteractive_commands) - execute 'command! I' . term_type . " :call vimteractive#session('" . term_type . "')" + execute 'command! I' . term_type . " :call vimteractive#term_start('" . term_type . "')" endfor - command! Iterm :call vimteractive#session('-auto-') + command! Iterm :call vimteractive#term_start('-auto-') + command! -nargs=? -complete=customlist,vimteractive#buffer_list Iconn + \ :call vimteractive#connect() endif + + +" Plugin key mappings +" =================== " Control-S in normal mode to send current line -noremap :call vimteractive#sendline(getline('.')) +noremap :call vimteractive#sendlines([getline('.')]) " Control-S in insert mode to send current line -inoremap :call vimteractive#sendline(getline('.'))a +inoremap :call vimteractive#sendlines([getline('.')])a " Control-S in visual mode to send multiple lines vnoremap :call vimteractive#sendlines(getline("'<","'>")) " Alt-S in normal mode to send all lines up to this point noremap :call vimteractive#sendlines(getline(1,'.')) - - -" Plugin Behaviour -" ================ - -" Switch to normal mode when entering terminal window -autocmd BufEnter * if &buftype ==# 'terminal' && bufname('%') ==# g:vimteractive_buffer_name | call feedkeys("\N") | endif - -" Switch back to terminal mode when exiting -autocmd BufLeave * if &buftype ==# 'terminal' && bufname('%') ==# g:vimteractive_buffer_name | execute "silent! normal! i" | endif