diff --git a/.travis.yml b/.travis.yml index 8a554ea..c4ee499 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,19 @@ language: vim +before_install: | + sudo apt-get update -qq + sudo apt-get install python-pip + sudo pip install --upgrade pyzmq + before_script: | git clone https://github.com/junegunn/vader.vim.git + git clone https://github.com/ipython/ipython.git script: | - vim -Nu <(cat << VIMRC + PYTHONPATH=ipython python -m IPython notebook --no-browser & + PYTHONPATH=ipython vim -Nu <(cat << VIMRC filetype off set rtp+=vader.vim set rtp+=. - set rtp+=after filetype plugin indent on VIMRC) -c 'Vader! test/*' > /dev/null diff --git a/README.rst b/README.rst index c7ff5f2..daf0798 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ vim-ipython A two-way integration between Vim and IPython. -IPython versions 0.11.x, 0.12.x, 0.13.x, 1.x and 2.x +Supports IPython versions 0.11.x, 0.12.x, 0.13.x, 1.x, 2.x and 3.x. * author: Paul Ivanov (http://pirsquared.org) * github: http://github.com/ivanov/vim-ipython @@ -18,12 +18,12 @@ IPython. The big change from previous versions of ``ipy.vim`` is that it no longer requires the old brittle ``ipy_vimserver.py`` instantiation, and since -it uses just vim and python, it is platform independent (i.e. works -even on windows, unlike the previous \*nix only solution). The requirements -are IPython 0.11 or newer with zeromq capabilities, vim compiled with +python. +it uses just Vim and Python, it is platform independent (i.e. works +even on Windows, unlike the previous \*nix only solution). The requirements +are IPython 0.11 or newer with ZeroMQ capabilities, Vim compiled with +python. If you can launch ``ipython qtconsole`` or ``ipython kernel``, and -``:echo has('python')`` returns 1 in vim, you should be good to go. +``:echo has('python')`` returns 1 in Vim, you should be good to go. ----------------- Quickstart Guide: @@ -119,7 +119,7 @@ IPython's object? Functionality ------------------------------- If you're using gvim, mouse-over a variable to see IPython's ``?`` equivalent. -If you're using vim from a terminal, or want to copy something from the +If you're using Vim from a terminal, or want to copy something from the docstring, type ``d``. ```` is usually ``\`` (the backslash key). This will open a quickpreview window, which can be closed by hitting ``q`` or ````. @@ -128,7 +128,7 @@ key). This will open a quickpreview window, which can be closed by hitting IPython's tab-completion Functionality -------------------------------------- vim-ipython activates a 'completefunc' that queries IPython. -A completefunc is activated using ``Ctrl-X Ctrl-U`` in Insert Mode (vim +A completefunc is activated using ``Ctrl-X Ctrl-U`` in Insert Mode (Vim default). You can combine this functionality with SuperTab to get tab completion. @@ -146,7 +146,7 @@ If at any later time you wish to bring this shell up, including if you've set ``monitor_subchannel=False``, hit ``s``. **NEW since IPython 0.12** -For local kernels (kernels running on the same machine as vim), `Ctrl-C` in +For local kernels (kernels running on the same machine as Vim), `Ctrl-C` in the vim-ipython 'shell' sends an keyboard interrupt. (Note: this feature may not work on Windows, please report the issue to ). @@ -205,10 +205,10 @@ editor and REPL combination. --------------- Known issues: --------------- -- For now, vim-ipython only connects to an ipython session in progress. -- The standard ipython clients (console, qtconsole, notebook) do not currently +- For now, vim-ipython only connects to an IPython session in progress. +- The standard IPython clients (console, qtconsole, notebook) do not currently display the result of computation which they did not initialize. This means - that if you send print statements for execution from within vim, they will + that if you send print statements for execution from within Vim, they will only be shown inside the vim-ipython shell buffer, but **not** within any of the standard clients. This is not a limitation of vim-ipython, but a limitation of those built-in clients, see `ipython/ipython#1873 @@ -217,16 +217,16 @@ Known issues: [IPython PR #3089](https://github.com/ipython/ipython/pull/3089) - If ```` does not work inside your terminal, but you are able to run some of the other commands successfully (````, for example), try running - this command before launching vim in the terminal (add it to your + this command before launching Vim in the terminal (add it to your ``.bashrc`` if it fixes the issue):: stty stop undef # to unmap ctrl-s -- In vim, if you're getting ``ImportError: No module named +- In Vim, if you're getting ``ImportError: No module named IPython.zmq.blockingkernelmanager`` but are able to import it in regular - python, **either** + Python, **either** - 1. your ``sys.path`` in vim differs from the ``sys.path`` in regular python. + 1. your ``sys.path`` in Vim differs from the ``sys.path`` in regular Python. Try running these two lines, and comparing their output files:: $ vim -c 'py import vim, sys; vim.current.buffer.append(sys.path)' -c ':wq vim_syspath' @@ -234,13 +234,13 @@ Known issues: **or** - 2. your vim is compiled against a different python than you are launching. See + 2. your Vim is compiled against a different Python than you are launching. See if there's a difference between :: $ vim -c ':py import os; print os.__file__' -c ':q' $ python -c 'import os; print os.__file__' -- For vim inside a terminal, using the arrow keys won't work inside a +- For Vim inside a terminal, using the arrow keys won't work inside a documentation buffer, because the mapping for ```` overlaps with ``^[OA`` and so on, and we use ```` as a quick way of closing the documentation preview window. If you want go without this quick close @@ -269,12 +269,12 @@ pull request with your attribution. * @minrk for guiding me through the IPython kernel manager protocol, and support of connection_file-based IPython connection (#13), and keeping vim-ipython working across IPython API changes. -* @nakamuray and @tcheneau for reporting and providing a fix for when vim is +* @nakamuray and @tcheneau for reporting and providing a fix for when Vim is compiled without a gui (#1) * @unpingco for reporting Windows bugs (#3,#4), providing better multiline dedenting (#15), and suggesting that a resized vim-ipython shell stays resized (#16). -* @simon-b for terminal vim arrow key issue (#5) +* @simon-b for terminal Vim arrow key issue (#5) * @jorgesca and @kwgoodman for shell update problems (#6) * @xowlinx and @vladimiroff for Ctrl-S issues in Konsole (#8) * @zeekay for easily allowing custom mappings (#9) @@ -282,7 +282,7 @@ pull request with your attribution. only open updating 'shell' if it is open (#29) * @enzbang for removing mapping that's not currently functional (#17) * @ogrisel for fixing documentation typo (#19) -* @koepsell for gracefully exiting in case python is not available (#23) +* @koepsell for gracefully exiting in case Python is not available (#23) * @mrterry for activating completefunc only after a connection is made (#25), Ctrl-C implementation in vim-ipython 'shell' (#28) * @nonameentername for completion on import statements (#26) @@ -301,7 +301,7 @@ Similar Projects * `screen.vba`_ - Simulate a split shell, using GNU Screen / tmux, that you can send commands to (Eric Van Dewoestine) * `vimux`_ - vim plugin to interact with tmux (Ben Mills) -* `vimux-pyutils`_ - send code to tmux ipython session (Julien Rebetez) +* `vimux-pyutils`_ - send code to tmux IPython session (Julien Rebetez) * conque_ - terminal emulator which uses a Vim buffer to display the program output (Nico Raffo) * `ipyqtmacvim`_ - plugin to send commands from MacVim to IPython Qt console diff --git a/ftplugin/python/vim_ipython.py b/ftplugin/python/vim_ipython.py index 202925c..3c99238 100644 --- a/ftplugin/python/vim_ipython.py +++ b/ftplugin/python/vim_ipython.py @@ -1,14 +1,31 @@ -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 = '' +import inspect +import os +import re +import signal +import sys +import textwrap try: - from queue import Empty # python3 convention + # Python3 compatibility. + from queue import Empty except ImportError: from Queue import Empty +try: + import IPython +except ImportError: + install_instructions = textwrap.dedent("""\ + You *must* install IPython into the Python that your Vim is linked + against. If you are seeing this message, this usually means either (1) + installing IPython using the system Python that Vim is using, or (2) + recompiling Vim against the Python where you already have IPython + installed. This is only a requirement to allow Vim to speak with an + IPython instance using IPython's own machinery. It does *not* mean that + the IPython instance with which you communicate via vim-ipython needs to + be running the same version of Python. + """).strip() + raise ImportError("Could not find IPython.\n" + install_instructions) + try: import vim except ImportError: @@ -16,23 +33,28 @@ class NoOp(object): def __getattribute__(self, key): return lambda *args: '0' vim = NoOp() - print("uh oh, not running inside vim") + print("Uh oh, not running inside Vim.") -import sys +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 = '' -# get around unicode problems when interfacing with vim +# Get around Unicode problems when interfacing with Vim. vim_encoding=vim.eval('&encoding') or 'utf-8' -try: - sys.stdout.flush -except AttributeError: - # IPython complains if stderr and stdout don't have flush - # this is fixed in newer version of Vim +# IPython complains if stderr and stdout don't have flush. This is fixed in +# newer versions of Vim. +if not hasattr(sys.stdout, 'flush'): class WithFlush(object): def __init__(self,noflush): - self.write=noflush.write - self.writelines=noflush.writelines - def flush(self):pass + self.write = noflush.write + self.writelines = noflush.writelines + + def flush(self): + pass + sys.stdout = WithFlush(sys.stdout) sys.stderr = WithFlush(sys.stderr) @@ -60,9 +82,8 @@ def vim_regex_escape(x): status_blank_lines = int(vim_variable('g:ipy_status_blank_lines', '1')) - ip = '127.0.0.1' -# this allows us to load vim_ipython multiple times +# This allows us to load vim_ipython multiple times. try: km kc @@ -72,15 +93,21 @@ def vim_regex_escape(x): kc = None pid = None -_install_instructions = """You *must* install IPython into the Python that -your vim is linked against. If you are seeing this message, this usually means -either (1) installing IPython using the system Python that vim is using, or -(2) recompiling Vim against the Python where you already have IPython -installed. This is only a requirement to allow Vim to speak with an IPython -instance using IPython's own machinery. It does *not* mean that the IPython -instance with which you communicate via vim-ipython needs to be running the -same version of Python. -""" + +def echo(arg,style="Question"): + try: + vim.command("echohl %s" % style) + vim.command("echom \"%s\"" % arg.replace('\"','\\\"')) + vim.command("echohl None") + except vim.error: + print("-- %s" % arg) + + +def debug(s, style='Info'): + debug_enabled = vim_variable('g:ipy_enable_debug_output', False) + if bool(debug_enabled): + echo(s, style) + def new_ipy(s=''): """Create a new IPython kernel (optionally with extra arguments) @@ -98,15 +125,12 @@ def new_ipy(s=''): km.start_kernel() return km_from_string(km.connection_file) + def km_from_string(s=''): """create kernel manager from IPKernelApp string such as '--shell=47378 --iopub=39859 --stdin=36778 --hb=52668' for IPython 0.11 or just 'kernel-12345.json' for IPython 0.12 """ - try: - import IPython - except ImportError: - raise ImportError("Could not find IPython. " + _install_instructions) from IPython.config.loader import KeyValueConfigLoader try: from IPython.kernel import ( @@ -137,16 +161,15 @@ def km_from_string(s=''): # whether or not they are allowed to have spaces. I'll have to sync # up with the IPython team to address these issues -pi if '--profile' in s: - k,p = s.split('--profile') - k = k.lstrip().rstrip() # kernel part of the string - p = p.lstrip().rstrip() # profile part of the string - fullpath = find_connection_file(k,p) + k, p = s.split('--profile', 1) + fullpath = find_connection_file(k.strip(), p.strip()) else: - fullpath = find_connection_file(s.lstrip().rstrip()) + fullpath = find_connection_file(s.strip()) except IOError as e: echo(":IPython " + s + " failed", "Info") echo("^-- failed '" + s + "' not found", "Error") return + debug("Using connection File: {0}".format(fullpath)) km = KernelManager(connection_file = fullpath) km.load_connection_file() else: @@ -174,21 +197,22 @@ def km_from_string(s=''): kc.start_channels() 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) - + + # XXX: backwards compatibility for IPython < 0.13 + if hasattr(sc, 'object_info'): + 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) + #XXX: backwards compatibility for IPython < 1.0 if not hasattr(kc, 'iopub_channel'): kc.iopub_channel = kc.sub_channel - # now that we're connect to an ipython kernel, activate completion + # Now that we're connected to an IPython kernel, activate the completion # machinery, but do so only for the local buffer if the user added the # following line the vimrc: # let g:ipy_completefunc = 'local' @@ -199,7 +223,8 @@ def km_from_string(s=''): setl completefunc=CompleteIPython endif """) - # also activate GUI doc balloons if in gvim + + # Also activate GUI doc balloons if in gvim. vim.command(""" if has('balloon_eval') set bexpr=IPythonBalloonExpr() @@ -208,14 +233,6 @@ def km_from_string(s=''): set_pid() return km -def echo(arg,style="Question"): - try: - vim.command("echohl %s" % style) - vim.command("echom \"%s\"" % arg.replace('\"','\\\"')) - vim.command("echohl None") - except vim.error: - print("-- %s" % arg) - def disconnect(): "disconnect kernel manager" # XXX: make a prompt here if this km owns the kernel @@ -226,10 +243,9 @@ def get_doc(word, level=0): return ["Not connected to IPython, cannot query: %s" % word] msg_id = kc.shell_channel.object_info(word, level) doc = get_doc_msg(msg_id) - # get around unicode problems when interfacing with vim + # Get around Unicode problems when interfacing with Vim. return [d.encode(vim_encoding) for d in doc] -import re # from http://serverfault.com/questions/71285/in-centos-4-4-how-can-i-strip-escape-sequences-from-a-text-file strip = re.compile('\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]') def strip_color_escapes(s): @@ -293,10 +309,10 @@ def get_doc_buffer(level=0): #vim.command('pedit doc') #vim.command('normal! ') # go to previous window if level == 0: - # use the ReST formatting that ships with stock vim + # Use the ReST formatting that ships with stock Vim vim.command('setlocal syntax=rst') else: - # use Python syntax highlighting + # Use Python syntax highlighting. vim.command('setlocal syntax=python') def ipy_complete(base, current_line, pos): @@ -305,12 +321,12 @@ def ipy_complete(base, current_line, pos): m = get_child_msg(msg_id) matches = m['content']['matches'] matches.insert(0,base) # the "no completion" version - # we need to be careful with unicode, because we can have unicode + # We need to be careful with Unicode, because we can have Unicode # completions for filenames (for the %run magic, for example). So the next # line will fail on those: #completions= [str(u) for u in matches] # because str() won't work for non-ascii characters - # and we also have problems with unicode in vim, hence the following: + # we also have problems with Unicode in Vim, hence the following: return matches except Empty: echo("no reply from IPython kernel") @@ -326,7 +342,7 @@ def vim_ipython_is_open(): return True return False -def update_subchannel_msgs(debug=False, force=False): +def update_subchannel_msgs(force=False): """ Grab any pending messages and place them inside the vim-ipython shell. This function will do nothing if the vim-ipython shell is not visible, @@ -388,29 +404,34 @@ def update_subchannel_msgs(debug=False, force=False): for m in msgs: s = '' if 'msg_type' not in m['header']: - # debug information - #echo('skipping a message on sub_channel','WarningMsg') - #echo(str(m)) + debug('skipping a message on sub_channel', 'WarningMsg') + debug(str(m)) continue - header = m['header']['msg_type'] - if header == 'status': + + msg_type = m['header']['msg_type'] + + if msg_type == 'status': continue - elif header == 'stream': - # TODO: alllow for distinguishing between stdout and stderr (using + elif msg_type == 'stream': + # TODO: allow for distinguishing between stdout and stderr (using # custom syntax markers in the vim-ipython buffer perhaps), or by # also echoing the message to the status bar - s = strip_color_escapes(m['content']['data']) - elif header == 'pyout': + field_name = 'data' + if field_name not in m['content']: + field_name = 'text' + field_name = 'data' if 'data' in m['content'] else 'text' + s = strip_color_escapes(m['content'][field_name]) + elif msg_type in ['pyout', 'execute_result']: s = status_prompt_out % {'line': m['content']['execution_count']} s += m['content']['data']['text/plain'] - elif header == 'display_data': - # TODO: handle other display data types (HMTL? images?) + elif msg_type == 'display_data': + # TODO: handle other display data types (HTML? images?) s += m['content']['data']['text/plain'] - elif header == 'pyin': - # TODO: the next line allows us to resend a line to ipython if + elif msg_type in ['pyin', 'execute_input']: + # TODO: the next line allows us to resend a line to IPython if # %doctest_mode is on. In the future, IPython will send the # execution_count on subchannel, so this will need to be updated - # once that happens + # once that happens. line_number = m['content'].get('execution_count', 0) prompt = status_prompt_in % {'line': line_number} s = prompt @@ -418,13 +439,16 @@ def update_subchannel_msgs(debug=False, force=False): dots = '.' * len(prompt.rstrip()) dots += prompt[len(prompt.rstrip()):] s += m['content']['code'].rstrip().replace('\n', '\n' + dots) - elif header == 'pyerr': + elif msg_type in ['pyerr', 'error']: c = m['content'] s = "\n".join(map(strip_color_escapes,c['traceback'])) s += c['ename'] + ":" + c['evalue'] + else: + debug('Unexpected message type {0}'.format(msg_type)) + debug(str(m)) if s.find('\n') == -1: - # somewhat ugly unicode workaround from + # somewhat ugly Unicode workaround from # http://vim.1045645.n5.nabble.com/Limitations-of-vim-python-interface-with-respect-to-character-encodings-td1223881.html if isinstance(s,unicode): s=s.encode(vim_encoding) @@ -541,7 +565,7 @@ def run_these_lines(dedent=False): if not reselect: vim.command("normal! ") - #vim lines start with 1 + # Vim line numbering starts at 1. #print("lines %d-%d sent to ipython"% (r.start+1,r.end+1)) prompt = "lines %d-%d "% (r.start+1,r.end+1) print_prompt(prompt,msg_id) @@ -553,7 +577,7 @@ def set_pid(): """ global pid lines = '\n'.join(['import os', '_pid = os.getpid()']) - msg_id = send(lines, silent=True, user_variables=['_pid']) + msg_id = send(lines, silent=True, user_expressions={'_pid': '_pid'}) # wait to get message back from kernel try: @@ -562,9 +586,9 @@ def set_pid(): echo("no reply from IPython kernel") return try: - pid = int(child['content']['user_variables']['_pid']) + pid = int(child['content']['user_expressions']['_pid']) except TypeError: # change in IPython 1.0.dev moved this out - pid = int(child['content']['user_variables']['_pid']['data']['text/plain']) + pid = int(child['content']['user_expressions']['_pid']['data']['text/plain']) except KeyError: # change in IPython 1.0.dev moved this out echo("Could not get PID information, kernel not running Python?") return pid @@ -572,7 +596,6 @@ def set_pid(): def terminate_kernel_hack(): "Send SIGTERM to our the IPython kernel" - import signal interrupt_kernel_hack(signal.SIGTERM) def interrupt_kernel_hack(signal_to_send=None): @@ -582,8 +605,6 @@ def interrupt_kernel_hack(signal_to_send=None): Only works on posix. """ global pid - import signal - import os if pid is None: # Avoid errors if we couldn't get pid originally, # by trying to obtain it now diff --git a/test/simple.vader b/test/simple.vader index dfc831f..e08e7a2 100644 --- a/test/simple.vader +++ b/test/simple.vader @@ -1,12 +1,29 @@ Given (): Hello -Execute python ( check Python support ): - import os - import vim - from vim import current, vars - vim.command('normal! yy2p') - current.buffer.append(os.path.basename(vars['vader_file'])) +Execute (Make sure Vim actually works.): + Log "Run simple 'put' test." + let t="Vim is working." + put =t + Log "Done." + +Expect: + Hello + Vim is working. + + +Execute (FIXME: Check Python support.): + " Note: vim.vars didn't make it into Vim until version 7.4. The commands + " below might not work. + Assert has('python') == 1, 'FAIL: Vim must have Python support.' + Log 'Import modules.' + python import os + python import vim + Log 'Yank/paste current line.' + python vim.command('normal! yy2p') + Log 'Add name of vader script to file' + python vim.current.buffer.append(os.path.basename(vim.vars['vader_file'])) + Log 'Done.' Expect: Hello @@ -15,23 +32,73 @@ Expect: simple.vader -Execute python (check IPython import): - import vim - from vim import current, vars - import IPython - current.buffer.append(str(IPython.version_info[0] >= 1)) +Execute (Check IPython.): + Assert has('python') == 1, 'FAIL: Vim must have Python support.' + python import vim + Log 'Import IPython.' + python import IPython + Log 'Check IPython version >= 1.' + python vim.current.buffer.append(str(IPython.version_info[0] >= 1)) + Log 'Done.' Expect: Hello True -Execute python (check vim_ipython import): - import vim - vim.command("set ft=python") - vim.command("IPython") - import vim_ipython - current.buffer.append("vim_ipython loaded") + +Before (set filetype to Python): + Log 'Enable filetype plugin.' + filetype plugin on + Log 'Set filetype=python.' + set ft=python + Log '[before] done.' + +Execute (Ensure vim_python ftplugin loads): + Assert has('python') == 1, 'FAIL: Vim must have Python support.' + python import vim + python vim.current.buffer.append("filetype is " + vim.eval("&ft")) + AssertEqual 'python', &filetype + python vim.current.buffer.append("IPython command exists: " + vim.eval("exists(':IPython') != 0")) + python vim.command("IPython") + python import vim_ipython + python vim.current.buffer.append("vim_ipython loaded") + Log 'Done.' Expect: Hello + filetype is python + IPython command exists: 1 vim_ipython loaded + + +Given (): + " ".join(["Vim", "+", "IPython", "==", "nifty"]) + +Before (set filetype to Python): + filetype plugin on + set ft=python + IPython + +Execute (Run a command and get its result from the preview window.): + python import vim_ipython + Log 'Restarting kernel to reset execution counter.' + python vim_ipython.kc.shutdown(True) + Log 'Running current line: `' . getline('.') .'`' + python vim_ipython.run_this_line() + Log 'Copying results from preview window.' + " Switch to the preview window, move to the line with the "In []" command, + " then delete all the output + wincmd P + normal gg + /^In \+\[ + normal VGd + " Switch back to the previous window, and paste the output. + wincmd p + normal p + Log 'Done.' + +Expect (): + " ".join(["Vim", "+", "IPython", "==", "nifty"]) + In [1]: " ".join(["Vim", "+", "IPython", "==", "nifty"]) + Out[1]: 'Vim + IPython == nifty' +