From abdfef64ba486815ba9b152b759727c666478046 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 10:39:29 +0200 Subject: [PATCH 01/10] Deal with some changes in the IPython 3.x API The IPython API is ever moving and the newest version again breaks vim-ipython. This PR adds some try/catch blocks to deal with the API changes. --- ftplugin/python/vim_ipython.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 075c1bf..0a4ed08 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -172,17 +172,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 +577,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) From 3a9021446409a7308267a649a2e302808f67b846 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:04:26 +0200 Subject: [PATCH 02/10] Add support for MATLAB-like cells. When working on a longish script, its common to have chunks that need to be executed frequently. For example, a computation you are working on that spans multiple lines, or a plot that you are trying to get just right. When using an IPython notebook, you would put these chunks in cells. MATLAB has a similar idea, where %% markers deliminate cells, which can be executed with a single key press. With this PR, you can define cells like this: print('Performing computation') a = 1 + 2 b = a ** a ## Begin new cell print('Result of computation:') print(b) Now you can execute the two cells independently. --- README.rst | 18 ++++++++++ ftplugin/python/ipy.vim | 4 +++ ftplugin/python/vim_ipython.py | 65 ++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/README.rst b/README.rst index 0e0dccc..6c45e06 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("\'","\\\'")) From abae80e12a84b2c27d0de1251a126306e2219b19 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:14:00 +0200 Subject: [PATCH 03/10] Change default cell hotkey to C-Return --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 6c45e06..2b829fe 100644 --- a/README.rst +++ b/README.rst @@ -111,7 +111,7 @@ 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 ````:: +cursor somewhere within it and press ````:: ## Do something print('Hello') From d1cea33c41a90ff8b8526b48bf092ef20f4dfd95 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:21:18 +0200 Subject: [PATCH 04/10] Add option to fold by cells. Whether folding is performed by default is controlled through the `g:ipy_cell_folding` variable. In addition, the `(IPython-EnableFoldByCell)` hook is available to toggle cell folding. --- ftplugin/python/ipy.vim | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/ftplugin/python/ipy.vim b/ftplugin/python/ipy.vim index 6b79686..4601f78 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. @@ -109,6 +114,7 @@ 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) @@ -209,3 +215,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 From 0a4f622403be925636cd123c959a97612621565f Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:38:30 +0200 Subject: [PATCH 05/10] Control config through vim globals This PR allows the user to configure some aspects of vim-ipython through the usage of `g:ipy_*` globals. --- README.rst | 11 ++++++----- TODO.md | 2 +- ftplugin/python/ipy.vim | 20 ++++++++++++++++++++ ftplugin/python/vim_ipython.py | 13 +++++++------ 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 0e0dccc..7c08bac 100644 --- a/README.rst +++ b/README.rst @@ -153,12 +153,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 +294,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 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..b4b7245 100644 --- a/ftplugin/python/ipy.vim +++ b/ftplugin/python/ipy.vim @@ -41,6 +41,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_select = 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_un_flags = '-i' +endif + python << EOF import vim import sys diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 075c1bf..4ab2977 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 = vim.eval("g:ipy_reselect") +show_execution_count = vim.eval("g:ipy_show_execution_count") +monitor_subchannel = 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' From fb0b7bb656ff58ace7246d6c0524644ea93563ca Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:41:33 +0200 Subject: [PATCH 06/10] typo --- ftplugin/python/ipy.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftplugin/python/ipy.vim b/ftplugin/python/ipy.vim index b4b7245..5cffa9e 100644 --- a/ftplugin/python/ipy.vim +++ b/ftplugin/python/ipy.vim @@ -43,7 +43,7 @@ endif " reselect lines after sending from Visual mode if !exists('g:ipy_reselect') - let g:ipy_select = 0 + let g:ipy_reselect = 0 endif " wait to get numbers for In[43]: feedback? From 9a6e08103baa8a79f6a43d3977b5431cb689d819 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:42:38 +0200 Subject: [PATCH 07/10] typo --- ftplugin/python/ipy.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftplugin/python/ipy.vim b/ftplugin/python/ipy.vim index 5cffa9e..5f2b84e 100644 --- a/ftplugin/python/ipy.vim +++ b/ftplugin/python/ipy.vim @@ -58,7 +58,7 @@ endif " flags to for IPython's run magic when using if !exists('g:ipy_run_flags') - let g:ipy_un_flags = '-i' + let g:ipy_run_flags = '-i' endif python << EOF From 65b647b71dcf2bbd7cd6c1bb535d34d8d6da1297 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:45:22 +0200 Subject: [PATCH 08/10] debug --- ftplugin/python/vim_ipython.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 4ab2977..578db64 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -15,12 +15,14 @@ def __getattribute__(self, key): import sys # Read global configuration variables -reselect = vim.eval("g:ipy_reselect") -show_execution_count = vim.eval("g:ipy_show_execution_count") -monitor_subchannel = vim.eval("g:ipy_monitor_subchannel") +reselect = bool(vim.eval("g:ipy_reselect")) +show_execution_count = bool(vim.eval("g:ipy_show_execution_count")) +monitor_subchannel = bool(vim.eval("g:ipy_monitor_subchannel")) run_flags = vim.eval("g:ipy_run_flags") current_line = "" +print('Subchannel:', monitor_subchannel) + # get around unicode problems when interfacing with vim vim_encoding=vim.eval('&encoding') or 'utf-8' From 2fd82d68f235ab4fb7197fc4b55ff94d206de3fc Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:46:57 +0200 Subject: [PATCH 09/10] debug --- ftplugin/python/vim_ipython.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 578db64..3e6f590 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -15,9 +15,9 @@ def __getattribute__(self, key): import sys # Read global configuration variables -reselect = bool(vim.eval("g:ipy_reselect")) -show_execution_count = bool(vim.eval("g:ipy_show_execution_count")) -monitor_subchannel = bool(vim.eval("g:ipy_monitor_subchannel")) +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 = "" From affddf78c4168caf57a5af9a8db0e45c137cb424 Mon Sep 17 00:00:00 2001 From: Marijn van Vliet Date: Thu, 11 Jun 2015 11:38:30 +0200 Subject: [PATCH 10/10] Control config through vim globals This PR allows the user to configure some aspects of vim-ipython through the usage of `g:ipy_*` globals. --- README.rst | 11 ++++++----- TODO.md | 2 +- ftplugin/python/ipy.vim | 20 ++++++++++++++++++++ ftplugin/python/vim_ipython.py | 13 +++++++------ 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index 0e0dccc..7c08bac 100644 --- a/README.rst +++ b/README.rst @@ -153,12 +153,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 +294,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 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..5f2b84e 100644 --- a/ftplugin/python/ipy.vim +++ b/ftplugin/python/ipy.vim @@ -41,6 +41,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 diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 075c1bf..5457089 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'