diff --git a/README.rst b/README.rst index 0e0dccc..2b829fe 100644 --- a/README.rst +++ b/README.rst @@ -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 ````. +To execute predefined sections of a script, you can define Matlab-like cells +using either ``##`` or ``# `` markers. To execute a cell, move the +cursor somewhere within it and press ````:: + + ## Do something + print('Hello') + + ## Do something else + print('IPython') + + # This is an alternative cell marker + print('World!') + +Cells (when deliminated by '# ' 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 ```` instead - it will dedent a single line, and remove the leading @@ -294,6 +311,7 @@ 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. Similar Projects ---------------- diff --git a/ftplugin/python/ipy.vim b/ftplugin/python/ipy.vim index 8c7011b..6b79686 100644 --- a/ftplugin/python/ipy.vim +++ b/ftplugin/python/ipy.vim @@ -95,6 +95,7 @@ au BufEnter vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython s noremap (IPython-RunFile) :python run_this_file() noremap (IPython-RunLine) :python run_this_line() noremap (IPython-RunLines) :python run_these_lines() +noremap (IPython-RunCell) :python run_this_cell() noremap (IPython-OpenPyDoc) :python get_doc_buffer() noremap (IPython-UpdateShell) :python if update_subchannel_msgs(force=True): echo("vim-ipython shell updated",'Operator') noremap (IPython-ToggleReselect) :python toggle_reselect() @@ -113,6 +114,7 @@ if g:ipy_perform_mappings != 0 map (IPython-RunFile) map (IPython-RunLine) map (IPython-RunLines) + map (IPython-RunCell) map d (IPython-OpenPyDoc) map s (IPython-UpdateShell) map (IPython-ToggleReselect) @@ -124,6 +126,7 @@ if g:ipy_perform_mappings != 0 imap (IPython-RunFile) imap (IPython-RunLines) imap (IPython-RunFile) + imap (IPython-RunCell) map (IPython-ToggleSendOnSave) "" Example of how to quickly clear the current plot with a keystroke "map (IPython-PlotClearCurrent) @@ -137,6 +140,7 @@ if g:ipy_perform_mappings != 0 map (IPython-RunLineAsTopLevel) xmap (IPython-RunLines) xmap (IPython-RunLinesAsTopLevel) + map (IPython-RunCell) noremap I# xnoremap I# diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 075c1bf..711f496 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -627,6 +627,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 = ['##', '# '] + 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("\'","\\\'"))