Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Matlab-like cell behavior #41

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,23 @@ Then, go to the qtconsole and run this line::

You can also send whole files to IPython's ``%run`` magic using ``<F5>``.

To execute predefined sections of a script, you can define Matlab-like cells
using either ``##`` or ``# <codecell>`` markers. To execute a cell, move the
cursor somewhere within it and press ``<Ctrl-Enter>``::

## Do something
print('Hello')

## Do something else
print('IPython')

# <codecell> This is an alternative cell marker
print('World!')

Cells (when deliminated by '# <codecell>' markers) are two-way compatible with
IPython notebooks, so you can easily switch between browser and Vim without
loosing them.

**NEW in IPython 0.12**!
If you're trying to do run code fragments that have leading whitespace, use
``<Alt-S>`` instead - it will dedent a single line, and remove the leading
Expand Down Expand Up @@ -153,12 +170,12 @@ not work on Windows, please report the issue to ).
-------
Options
-------
You can change these at the top of the ipy.vim::
You can change these in your vimrc::

reselect = False # reselect lines after sending from Visual mode
show_execution_count = True # wait to get numbers for In[43]: feedback?
monitor_subchannel = True # update vim-ipython 'shell' on every send?
run_flags= "-i" # flags to for IPython's run magic when using <F5>
g:ipy_reselect = 0 # reselect lines after sending from Visual mode
g:ipy_show_execution_count = 1 # wait to get numbers for In[43]: feedback?
g:ipy_monitor_subchannel = 1 # update vim-ipython 'shell' on every send?
g:ipy_run_flags = '-i' # flags to for IPython's run magic when using <F5>

**Disabling default mappings**
In your own ``.vimrc``, if you don't like the mappings provided by default,
Expand Down Expand Up @@ -294,6 +311,8 @@ pull request with your attribution.
* @pydave for IPythonTerminate (sending SIGTERM using our hack)
* @luispedro for IPythonNew
* @jjhelmus for IPython 3.x support.
* @wmvanvliet for Matlab-like cell support.
* @wmvanvliet for config support through vim-globals.

Similar Projects
----------------
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This is a list of things I'm planning to work on with vim-ipython

[ ] support for non-python kernels (IJulia, IHaskell kernel)

[ ] provide g:ipy variables to set the initial state of python vars
[x] provide g:ipy variables to set the initial state of python vars
e.g. monitor_subchannel

[ ] put debugging support back in
Expand Down
51 changes: 51 additions & 0 deletions ftplugin/python/ipy.vim
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ if !exists('g:ipy_perform_mappings')
let g:ipy_perform_mappings = 1
endif

" Enable cell folding
if !exists('g:ipy_cell_folding')
let g:ipy_cell_folding = 0
endif

" Register IPython completefunc
" 'global' -- for all of vim (default).
" 'local' -- only for the current buffer.
Expand All @@ -41,6 +46,26 @@ if !exists('g:ipy_completefunc')
let g:ipy_completefunc = 'global'
endif

" reselect lines after sending from Visual mode
if !exists('g:ipy_reselect')
let g:ipy_reselect = 0
endif

" wait to get numbers for In[43]: feedback?
if !exists('g:ipy_show_execution_count')
let g:ipy_show_execution_count = 1
endif

" update vim-ipython 'shell' on every send?
if !exists('g:ipy_monitor_subchannel')
let g:ipy_monitor_subchannel = 1
endif

" flags to for IPython's run magic when using <F5>
if !exists('g:ipy_run_flags')
let g:ipy_run_flags = '-i'
endif

python << EOF
import vim
import sys
Expand Down Expand Up @@ -95,6 +120,7 @@ au BufEnter vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython s
noremap <Plug>(IPython-RunFile) :python run_this_file()<CR>
noremap <Plug>(IPython-RunLine) :python run_this_line()<CR>
noremap <Plug>(IPython-RunLines) :python run_these_lines()<CR>
noremap <Plug>(IPython-RunCell) :python run_this_cell()<CR>
noremap <Plug>(IPython-OpenPyDoc) :python get_doc_buffer()<CR>
noremap <Plug>(IPython-UpdateShell) :python if update_subchannel_msgs(force=True): echo("vim-ipython shell updated",'Operator')<CR>
noremap <Plug>(IPython-ToggleReselect) :python toggle_reselect()<CR>
Expand All @@ -108,11 +134,13 @@ noremap <Plug>(IPython-PlotClearCurrent) :python run_command("plt.clf()")<CR>
noremap <Plug>(IPython-PlotCloseAll) :python run_command("plt.close('all')")<CR>
noremap <Plug>(IPython-RunLineAsTopLevel) :python dedent_run_this_line()<CR>
xnoremap <Plug>(IPython-RunLinesAsTopLevel) :python dedent_run_these_lines()<CR>
noremap <Plug>(IPython-EnableFoldByCell) :call EnableFoldByCell()<CR>

if g:ipy_perform_mappings != 0
map <buffer> <silent> <F5> <Plug>(IPython-RunFile)
map <buffer> <silent> <S-F5> <Plug>(IPython-RunLine)
map <buffer> <silent> <F9> <Plug>(IPython-RunLines)
map <buffer> <silent> <C-M-F5> <Plug>(IPython-RunCell)
map <buffer> <silent> <LocalLeader>d <Plug>(IPython-OpenPyDoc)
map <buffer> <silent> <LocalLeader>s <Plug>(IPython-UpdateShell)
map <buffer> <silent> <S-F9> <Plug>(IPython-ToggleReselect)
Expand All @@ -124,6 +152,7 @@ if g:ipy_perform_mappings != 0
imap <buffer> <C-F5> <C-o><Plug>(IPython-RunFile)
imap <buffer> <S-F5> <C-o><Plug>(IPython-RunLines)
imap <buffer> <silent> <F5> <C-o><Plug>(IPython-RunFile)
imap <buffer> <silent> <C-M-F5> <C-o><Plug>(IPython-RunCell)
map <buffer> <C-F5> <Plug>(IPython-ToggleSendOnSave)
"" Example of how to quickly clear the current plot with a keystroke
"map <buffer> <silent> <F12> <Plug>(IPython-PlotClearCurrent)
Expand All @@ -137,6 +166,7 @@ if g:ipy_perform_mappings != 0
map <buffer> <silent> <M-s> <Plug>(IPython-RunLineAsTopLevel)
xmap <buffer> <silent> <C-S> <Plug>(IPython-RunLines)
xmap <buffer> <silent> <M-s> <Plug>(IPython-RunLinesAsTopLevel)
map <buffer> <silent> <C-Return> <Plug>(IPython-RunCell)

noremap <buffer> <silent> <M-c> I#<ESC>
xnoremap <buffer> <silent> <M-c> I#<ESC>
Expand Down Expand Up @@ -205,3 +235,24 @@ endpython
return res
endif
endfun

" Custom folding function to fold cells
function! FoldByCell(lnum)
let pattern = '\v^\s*(##|' . escape('# <codecell>', '<>') . ').*$'
if getline(a:lnum) =~? pattern
return '>1'
elseif getline(a:lnum+1) =~? pattern
return '<1'
else
return '='
endif
endfunction

function! EnableFoldByCell()
setlocal foldmethod=expr
setlocal foldexpr=FoldByCell(v:lnum)
endfunction

if g:ipy_cell_folding != 0
call EnableFoldByCell()
endif
111 changes: 95 additions & 16 deletions ftplugin/python/vim_ipython.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
reselect = False # reselect lines after sending from Visual mode
show_execution_count = True # wait to get numbers for In[43]: feedback?
monitor_subchannel = True # update vim-ipython 'shell' on every send?
run_flags= "-i" # flags to for IPython's run magic when using <F5>
current_line = ''

try:
from queue import Empty # python3 convention
except ImportError:
Expand All @@ -20,6 +14,13 @@ def __getattribute__(self, key):

import sys

# Read global configuration variables
reselect = bool(int(vim.eval("g:ipy_reselect")))
show_execution_count = bool(int(vim.eval("g:ipy_show_execution_count")))
monitor_subchannel = bool(int(vim.eval("g:ipy_monitor_subchannel")))
run_flags = vim.eval("g:ipy_run_flags")
current_line = ""

# get around unicode problems when interfacing with vim
vim_encoding=vim.eval('&encoding') or 'utf-8'

Expand Down Expand Up @@ -172,17 +173,25 @@ def km_from_string(s=''):
# 0.13
kc = km
kc.start_channels()
send = kc.shell_channel.execute

try:
send = kc.execute
except AttributeError:
# < 3.0
send = kc.shell_channel.execute

#XXX: backwards compatibility for IPython < 0.13
import inspect
sc = kc.shell_channel
num_oinfo_args = len(inspect.getargspec(sc.object_info).args)
if num_oinfo_args == 2:
# patch the object_info method which used to only take one argument
klass = sc.__class__
klass._oinfo_orig = klass.object_info
klass.object_info = lambda s,x,y: s._oinfo_orig(x)
try:
import inspect
sc = kc.shell_channel
num_oinfo_args = len(inspect.getargspec(sc.object_info).args)
if num_oinfo_args == 2:
# patch the object_info method which used to only take one argument
klass = sc.__class__
klass._oinfo_orig = klass.object_info
klass.object_info = lambda s,x,y: s._oinfo_orig(x)
except:
pass

#XXX: backwards compatibility for IPython < 1.0
if not hasattr(kc, 'iopub_channel'):
Expand Down Expand Up @@ -569,7 +578,12 @@ def set_pid():
"""
global pid
lines = '\n'.join(['import os', '_pid = os.getpid()'])
msg_id = send(lines, silent=True, user_variables=['_pid'])

try:
msg_id = send(lines, silent=True, user_variables=['_pid'])
except TypeError: # change in IPython 3.0+
msg_id = send(lines, silent=True, user_expressions={'_pid':'_pid'})

# wait to get message back from kernel
try:
child = get_child_msg(msg_id)
Expand Down Expand Up @@ -627,6 +641,71 @@ def dedent_run_this_line():
def dedent_run_these_lines():
run_these_lines(True)

def is_cell_separator(line):
'''Determines whether a given line is a cell separator'''
cell_sep = ['##', '# <codecell>']
for sep in cell_sep:
if line.strip().startswith(sep):
return True
return False

@with_subchannel
def run_this_cell():
'''Runs all the code in between two cell separators'''
b = vim.current.buffer
(cur_line, cur_col) = vim.current.window.cursor
cur_line -= 1

# Search upwards for cell separator
upper_bound = cur_line
while upper_bound > 0 and not is_cell_separator(vim.current.buffer[upper_bound]):
upper_bound -= 1

# Skip past the first cell separator if it exists
if is_cell_separator(vim.current.buffer[upper_bound]):
upper_bound += 1

# Search downwards for cell separator
lower_bound = min(upper_bound+1, len(vim.current.buffer)-1)

while lower_bound < len(vim.current.buffer)-1 and not is_cell_separator(vim.current.buffer[lower_bound]):
lower_bound += 1

# Move before the last cell separator if it exists
if is_cell_separator(vim.current.buffer[lower_bound]):
lower_bound -= 1

# Make sure bounds are within buffer limits
upper_bound = max(0, min(upper_bound, len(vim.current.buffer)-1))
lower_bound = max(0, min(lower_bound, len(vim.current.buffer)-1))

# Make sure of proper ordering of bounds
lower_bound = max(upper_bound, lower_bound)

# Calculate minimum indentation level of entire cell
shiftwidth = vim.eval('&shiftwidth')
count = lambda x: int(vim.eval('indent(%d)/%s' % (x,shiftwidth)))

min_indent = count(upper_bound+1)
for i in range(upper_bound+1, lower_bound):
indent = count(i)
if i < min_indent:
min_indent = i

# Perform dedent
if min_indent > 0:
vim.command('%d,%d%s' % (upper_bound+1, lower_bound+1, '<'*min_indent))

# Execute cell
lines = "\n".join(vim.current.buffer[upper_bound:lower_bound+1])
msg_id = send(lines)
prompt = "lines %d-%d "% (upper_bound+1,lower_bound+1)
print_prompt(prompt, msg_id)

# Re-indent
if min_indent > 0:
vim.command("silent undo")

#def set_this_line():
# # not sure if there's a way to do this, since we have multiple clients
# send("get_ipython().shell.set_next_input(\'%s\')" % vim.current.line.replace("\'","\\\'"))
Expand Down