Skip to content

Commit

Permalink
Merge pull request #9 from GoldsteinE/master
Browse files Browse the repository at this point in the history
Multiple terminals
  • Loading branch information
williamjameshandley authored Aug 5, 2019
2 parents 2411713 + d20cc6f commit f2277e0
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 78 deletions.
30 changes: 25 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``

Expand Down Expand Up @@ -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
------------
Expand Down Expand Up @@ -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 <buffer_name>`` where ``<buffer_name>`` is a name of buffer
containing terminal. If there is only one terminal, you can use just ``:Iconn``.

Supported terminals
~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -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
\ }
Expand All @@ -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
----------------
Expand All @@ -200,6 +219,7 @@ Similar projects

Changelist
----------
:v2.0: `Multiple terminal functionality <https://github.com/williamjameshandley/vimteractive/pull/9>`__
:v1.7: `Autodetection of terminals <https://github.com/williamjameshandley/vimteractive/pull/5>`__
:v1.6: CtrlP `bugfix <https://github.com/williamjameshandley/vimteractive/pull/4>`__
:v1.5: Added julia support
Expand Down
212 changes: 168 additions & 44 deletions autoload/vimteractive.vim
Original file line number Diff line number Diff line change
@@ -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('<afile>')
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 <SID>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 <buffer> call feedkeys("\<C-W>N")
" Switch to insert mode when leaving buffer
autocmd BufLeave <buffer> execute "silent! normal! i"
" Make :quit really do the right thing
cabbrev <buffer> q bdelete! "
cabbrev <buffer> qu bdelete! "
cabbrev <buffer> qui bdelete! "
cabbrev <buffer> 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
35 changes: 31 additions & 4 deletions doc/vimteractive.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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*

Expand All @@ -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:
Expand All @@ -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:

[email protected].

==============================================================================
4. About *vimteractive-functionality*
5. About *vimteractive-functionality*

The core maintainer of vimteractive is:

Expand All @@ -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
Loading

0 comments on commit f2277e0

Please sign in to comment.