diff --git a/README.rst b/README.rst index 0e0dccc..113c5fc 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 @@ -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 + 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 **Disabling default mappings** In your own ``.vimrc``, if you don't like the mappings provided by default, @@ -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 ---------------- diff --git a/TODO.md b/TODO.md index e9e4098..c5059d7 100644 --- a/TODO.md +++ b/TODO.md @@ -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 diff --git a/ftplugin/python/ipy.vim b/ftplugin/python/ipy.vim index 8c7011b..9c7f362 100644 --- a/ftplugin/python/ipy.vim +++ b/ftplugin/python/ipy.vim @@ -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. @@ -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 +if !exists('g:ipy_run_flags') + let g:ipy_run_flags = '-i' +endif + python << EOF import vim import sys @@ -95,6 +120,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() @@ -108,11 +134,13 @@ noremap (IPython-PlotClearCurrent) :python run_command("plt.clf()") noremap (IPython-PlotCloseAll) :python run_command("plt.close('all')") noremap (IPython-RunLineAsTopLevel) :python dedent_run_this_line() xnoremap (IPython-RunLinesAsTopLevel) :python dedent_run_these_lines() +noremap (IPython-EnableFoldByCell) :call EnableFoldByCell() 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 +152,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 +166,7 @@ if g:ipy_perform_mappings != 0 map (IPython-RunLineAsTopLevel) xmap (IPython-RunLines) xmap (IPython-RunLinesAsTopLevel) + map (IPython-RunCell) noremap I# xnoremap I# @@ -205,3 +235,24 @@ endpython return res endif endfun + +" Custom folding function to fold cells +function! FoldByCell(lnum) + let pattern = '\v^\s*(##|' . escape('# ', '<>') . ').*$' + 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 diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 075c1bf..a0ca697 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -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 -current_line = '' - try: from queue import Empty # python3 convention except ImportError: @@ -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' @@ -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'): @@ -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) @@ -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 = ['##', '# '] + 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("\'","\\\'"))