diff --git a/docs/examples/vim/README.rst b/docs/examples/vim/README.rst index fd0980d..4eef20d 100644 --- a/docs/examples/vim/README.rst +++ b/docs/examples/vim/README.rst @@ -16,7 +16,7 @@ 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. should work +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+ with zeromq capabilities, vim compiled with +python. @@ -45,15 +45,28 @@ and for IPython 0.12, like this:: :IPython --existing kernel-85997.json The ``:IPythonClipboard`` command just uses the ``+`` register to get the -connection string, whereas ``:IPythonXSelection`` uses the ``*`` register +connection string, whereas ``:IPythonXSelection`` uses the ``*`` register. + +**NEW in IPython 0.12**! +Since IPython 0.12, you can simply use:: + + :IPython + +without arguments to connect to the most recent IPython session (this is the +same as passing just the ``--existing`` flag to ``ipython qtconsole`` and +``ipython console``. .. [*] Though the demos above use ``qtconsole``, it is not required for this workflow, it's just that it was the easiest way to show how to - make use of the new functionality in 0.11 release. In the current git - trunk of IPython, you can use ``ipython kernel`` to create a kernel and - get the connection string to use for any frontend (including vim-ipython). - If you are still using 0.11, you can launch a regular kernel using - ``python -c "from IPython.zmq.ipkernel import main; main()"`` + make use of the new functionality in 0.11 release. Since IPython 0.12, you + can use ``ipython kernel`` to create a kernel and get the connection + string to use for any frontend (including vim-ipython), or use ``ipython + console`` to create a kernel and immediately connect to it using a + terminal-based client. You can even connect to an active IPython Notebook + kernel - just watch for the connection string that gets printed when you + open the notebook, or use the ``%connect_info`` magic to get the + connection string. If you are still using 0.11, you can launch a regular + kernel using ``python -c "from IPython.zmq.ipkernel import main; main()"`` ------------------------ Sending lines to IPython @@ -87,12 +100,17 @@ Then, go to the qtconsole and run this line:: You can also send whole files to IPython's ``%run`` magic using ````. +**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 +whitespace of the first line from all lines in a visual mode selection. + ------------------------------- 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 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 docstring, type ``d``. ```` is usually ``\`` (the backslash key). This will open a quickpreview window, which can be closed by hitting ``q`` or ````. @@ -108,7 +126,6 @@ completion. ------------------- vim-ipython 'shell' ------------------- -**NEW since IPython 0.11**! By monitoring km.sub_channel, we can recreate what messages were sent to IPython, and what IPython sends back in response. @@ -119,6 +136,11 @@ updated on every sent command (default: True). 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 +the vim-ipython 'shell' sends an keyboard interrupt. (Note: this feature may +not work on Windows, please report the issue to ). + ------- Options ------- @@ -134,6 +156,14 @@ In your own ``.vimrc``, if you don't like the mappings provided by default, you can define a variable ``let g:ipy_perform_mappings=0`` which will prevent vim-ipython from defining any of the default mappings. +**NEW since IPython 0.12** +**Making completefunc local to a buffer, or disabling it** +By default, vim-ipython activates the custom completefunc globally. +Sometimes, having a completefunc breaks other plugins' completions. Putting +the line ``let g:ipy_completefunc = 'local'`` in one's vimrc will activate the +IPython-based completion only for current buffer. Setting `g:ipy_completefunc` +to anything other than `'local'` or `'global'` disables it altogether. + --------------- Current issues: --------------- @@ -161,7 +191,7 @@ Current issues: if there's a difference between :: $ vim -c ':py import os; print os.__file__' -c ':q' - $ python -c ':py import os; print os.__file__' + $ python -c 'import os; print os.__file__' - For vim inside a terminal, using the arrow keys won't work inside a documentation buffer, because the mapping for ```` overlaps with @@ -179,11 +209,45 @@ Current issues: ---------------------------- Thanks and Bug Participation ---------------------------- -* @MinRK for guiding me through the IPython kernel manager protocol. -* @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) +Here's a brief acknowledgment of the folks who have graciously pitched in. If +you've been missed, don't hesitate to contact me, or better yet, submit a +pull request with your attribution. + +* @minrk for guiding me through the IPython kernel manager protocol, and + support of connection_file-based IPython connection (#13) +* @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) -* @jorgesca and @kwgoodman for shell (#6) +* @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) -* @minrk for support of connection_file-based IPython connection (#13) -* @jorgesca for reporting the lack of profile handling capability (#14) +* @jorgesca for reporting the lack of profile handling capability (#14), + 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) +* @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) +* @dstahlke for setting syntax of doc window to ReST +* @jtratner for docs with quotes (#30) +* @pielgrzym for setting completefunc locally to a buffer (#32) + +Similar Projects +---------------- +* `vim-slime`_ - Grab some text and "send" it to a GNU Screen / tmux session + (Jonathan Palardy) +* `screen.vba`_ - Simulate a split shell, using GNU Screen / tmux, that you + can send commands to (Eric Van Dewoestine) +* 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 + (Justin Kitzes) + +.. _vim-slime: https://github.com/jpalardy/vim-slime +.. _screen.vba: https://github.com/ervandew/screen +.. _conque: http://code.google.com/p/conque/ +.. _ipyqtmacvim: https://github.com/jkitzes/ipyqtmacvim/ diff --git a/docs/examples/vim/ipy.vim b/docs/examples/vim/ipy.vim index acfee62..3953902 100644 --- a/docs/examples/vim/ipy.vim +++ b/docs/examples/vim/ipy.vim @@ -1,6 +1,6 @@ " Vim integration with IPython 0.11+ " -" A two-way integration between Vim and IPython. +" A two-way integration between Vim and IPython. " " Using this plugin, you can send lines or whole files for IPython to execute, " and also get back object introspection and word completions in Vim, like @@ -11,16 +11,40 @@ " ----------------- " Start ipython qtconsole and copy the connection string. " Source this file, which provides new IPython command -" :source ipy.vim -" :IPythonClipboard +" :source ipy.vim +" :IPythonClipboard " (or :IPythonXSelection if you're using X11 without having to copy) " " written by Paul Ivanov (http://pirsquared.org) +" +if !has('python') + " exit if python is not available. + finish +endif + +" Allow custom mappings. +if !exists('g:ipy_perform_mappings') + let g:ipy_perform_mappings = 1 +endif + +" Register IPython completefunc +" 'global' -- for all of vim (default). +" 'local' -- only for the current buffer. +" otherwise -- don't register it at all. +" +" you can later set it using ':set completefunc=CompleteIPython', which will +" correspond to the 'global' behavior, or with ':setl ...' to get the 'local' +" behavior +if !exists('g:ipy_completefunc') + let g:ipy_completefunc = 'global' +endif + python << EOF 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 vim import sys @@ -48,6 +72,10 @@ try: km except NameError: km = None +try: + pid +except NameError: + pid = None def km_from_string(s=''): """create kernel manager from IPKernelApp string @@ -103,6 +131,26 @@ def km_from_string(s=''): return km.start_channels() send = km.shell_channel.execute + + # now that we're connect to an ipython kernel, activate completion + # machinery, but do so only for the local buffer if the user added the + # following line the vimrc: + # let g:ipy_completefunc = 'local' + vim.command(""" + if g:ipy_completefunc == 'global' + set completefunc=CompleteIPython + elseif g:ipy_completefunc == 'local' + setl completefunc=CompleteIPython + endif + """) + # also activate GUI doc balloons if in gvim + vim.command(""" + if has('balloon_eval') + set bexpr=IPythonBalloonExpr() + set ballooneval + endif + """) + set_pid() return km def echo(arg,style="Question"): @@ -120,7 +168,7 @@ def disconnect(): def get_doc(word): if km is None: - return ["Not connected to IPython, cannot query \"%s\"" %word] + return ["Not connected to IPython, cannot query: %s" % word] msg_id = km.shell_channel.object_info(word) doc = get_doc_msg(msg_id) # get around unicode problems when interfacing with vim @@ -131,7 +179,7 @@ import re strip = re.compile('\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]') def strip_color_escapes(s): return strip.sub('',s) - + def get_doc_msg(msg_id): n = 13 # longest field name (empirically) b=[] @@ -164,13 +212,11 @@ def get_doc_buffer(level=0): word = vim.eval('expand("")') or '' doc = get_doc(word) if len(doc) ==0: - echo(word+" not found","Error") + echo(repr(word)+" not found","Error") return - # close any currently open preview windows - vim.command('pcl') # documentation buffer name is same as the query made to ipython vim.command('new '+word) - vim.command('setlocal pvw modifiable noro') + vim.command('setlocal modifiable noro') # doc window quick quit keys: 'q' and 'escape' vim.command('map q :q') # Known issue: to enable the use of arrow keys inside the terminal when @@ -188,8 +234,27 @@ def get_doc_buffer(level=0): #vim.command('pcl') #vim.command('pedit doc') #vim.command('normal ') # go to previous window + # use the ReST formatting that ships with stock vim + vim.command('setlocal syntax=rst') + +def vim_ipython_is_open(): + """ + Helper function to let us know if the vim-ipython shell is currently + visible + """ + for w in vim.windows: + if w.buffer.name is not None and w.buffer.name.endswith("vim-ipython"): + return True + return False -def update_subchannel_msgs(debug=False): +def update_subchannel_msgs(debug=False, 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, + unless force=True argument is passed. + """ + if km is None or (not vim_ipython_is_open() and not force): + return False msgs = km.sub_channel.get_msgs() if debug: #try: @@ -223,6 +288,15 @@ def update_subchannel_msgs(debug=False): # subchannel window quick quit key 'q' vim.command('map q :q') vim.command("set bufhidden=hide buftype=nofile ft=python") + # make shift-enter and control-enter in insert mode behave same as in ipython notebook + # shift-enter send the current line, control-enter send the line + # but keeps it around for further editing. + vim.command("imap dd:python run_command('''\"''')i") + # pkddA: paste, go up one line which is blank after run_command, + # delete it, and then back to insert mode + vim.command("imap dd:python run_command('''\"''')pkddA") + # ctrl-C gets sent to the IPython process as a signal on POSIX + vim.command("map  :IPythonInterrupt") #syntax highlighting for python prompt # QtConsole In[] is blue, but I prefer the oldschool green @@ -234,6 +308,7 @@ def update_subchannel_msgs(debug=False): vim.command("syn match Green /^In \[[0-9]*\]\:/") vim.command("syn match Red /^Out\[[0-9]*\]\:/") b = vim.current.buffer + update_occured = False for m in msgs: #db.append(str(m).splitlines()) s = '' @@ -274,12 +349,14 @@ def update_subchannel_msgs(debug=False): b.append(s.splitlines()) except: b.append([l.encode(vim_encoding) for l in s.splitlines()]) + update_occured = True # make a newline so we can just start typing there if b[-1] != '': b.append(['']) vim.command('normal G') # go to the end of the file if not startedin_vimipython: vim.command('normal p') # go back to where you were + return update_occured def get_child_msg(msg_id): # XXX: message handling should be split into its own process in the future @@ -292,7 +369,7 @@ def get_child_msg(msg_id): #got a message, but not the one we were looking for echo('skipping a message on shell_channel','WarningMsg') return m - + def print_prompt(prompt,msg_id=None): """Print In[] or In[42] style messages""" global show_execution_count @@ -332,8 +409,6 @@ def run_this_line(): def run_command(cmd): msg_id = send(cmd) print_prompt(cmd, msg_id) - if monitor_subchannel: - update_subchannel_msgs() @with_subchannel def run_these_lines(): @@ -354,6 +429,51 @@ def run_these_lines(): prompt = "lines %d-%d "% (r.start+1,r.end+1) print_prompt(prompt,msg_id) + +def set_pid(): + """ + Explicitly ask the ipython kernel for its pid + """ + global km, pid + lines = '\n'.join(['import os', '_pid = os.getpid()']) + msg_id = send(lines, silent=True, user_variables=['_pid']) + + # wait to get message back from kernel + try: + child = get_child_msg(msg_id) + except Empty: + echo("no reply from IPython kernel") + return + + pid = int(child['content']['user_variables']['_pid']) + return pid + + +def interrupt_kernel_hack(): + """ + Sends the interrupt signal to the remote kernel. This side steps the + (non-functional) ipython interrupt mechanisms. + 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 + pid = set_pid() + + if pid is None: + echo("cannot get kernel PID, Ctrl-C will not be supported") + return + echo("KeyboardInterrupt (sent to ipython: pid " + + "%i with signal %i)" % (pid, signal.SIGINT),"Operator") + try: + os.kill(pid, signal.SIGINT) + except OSError: + echo("unable to kill pid %d" % pid) + pid = None + def dedent_run_this_line(): vim.command("left") run_this_line() @@ -366,7 +486,7 @@ def dedent_run_these_lines(): vim.command("'<,'>" + "<"*count) run_these_lines() 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("\'","\\\'")) @@ -382,9 +502,9 @@ def toggle_reselect(): #def set_breakpoint(): # send("__IP.InteractiveTB.pdb.set_break('%s',%d)" % (vim.current.buffer.name, # vim.current.window.cursor[0])) -# print "set breakpoint in %s:%d"% (vim.current.buffer.name, +# print "set breakpoint in %s:%d"% (vim.current.buffer.name, # vim.current.window.cursor[0]) -# +# #def clear_breakpoint(): # send("__IP.InteractiveTB.pdb.clear_break('%s',%d)" % (vim.current.buffer.name, # vim.current.window.cursor[0])) @@ -414,16 +534,41 @@ fun! toggle_send_on_save() endif endfun -" Allow custom mappings -if !exists('g:ipy_perform_mappings') - let g:ipy_perform_mappings = 1 -endif +" Update the vim-ipython shell when the cursor is not moving. +" You can change how quickly this happens after you stop moving the cursor by +" setting 'updatetime' (in milliseconds). For example, to have this event +" trigger after 1 second: +" +" :set updatetime 1000 +" +" NOTE: This will only be triggered once, after the first 'updatetime' +" milliseconds, *not* every 'updatetime' milliseconds. see :help CursorHold +" for more info. +" +" TODO: Make this easily configurable on the fly, so that an introspection +" buffer we may have opened up doesn't get closed just because of an idle +" event (i.e. user pressed \d and then left the buffer that popped up, but +" expects it to stay there). +au CursorHold *.*,vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on idle)",'Operator') + +" XXX: broken - cursor hold update for insert mode moves the cursor one +" character to the left of the last character (update_subchannel_msgs must be +" doing this) +"au CursorHoldI *.* :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on idle)",'Operator') + +" Same as above, but on regaining window focus (mostly for GUIs) +au FocusGained *.*,vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on input focus)",'Operator') + +" Update vim-ipython buffer when we move the cursor there. A message is only +" displayed if vim-ipython buffer has been updated. +au BufEnter vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on buffer enter)",'Operator') + if g:ipy_perform_mappings != 0 map :python run_this_file() map :python run_this_line() map :python run_these_lines() map d :py get_doc_buffer() - map s :py update_subchannel_msgs(); echo("vim-ipython shell updated",'Operator') + map s :py if update_subchannel_msgs(force=True): echo("vim-ipython shell updated",'Operator') map :python toggle_reselect() "map :python send('%pdb') "map :python set_breakpoint() @@ -435,9 +580,9 @@ if g:ipy_perform_mappings != 0 imap map :call toggle_send_on_save() "" Example of how to quickly clear the current plot with a keystroke - map :python run_command("plt.clf()") + "map :python run_command("plt.clf()") "" Example of how to quickly close all figures with a keystroke - map :python run_command("plt.close('all')") + "map :python run_command("plt.close('all')") "pi custom map :python run_this_file() @@ -455,6 +600,7 @@ endif command! -nargs=* IPython :py km_from_string("") command! -nargs=0 IPythonClipboard :py km_from_string(vim.eval('@+')) command! -nargs=0 IPythonXSelection :py km_from_string(vim.eval('@*')) +command! -nargs=0 IPythonInterrupt :py interrupt_kernel_hack() function! IPythonBalloonExpr() python << endpython @@ -464,28 +610,27 @@ vim.command("let l:doc = %s"% reply) endpython return l:doc endfunction -if has('balloon_eval') - set bexpr=IPythonBalloonExpr() - set ballooneval -endif fun! CompleteIPython(findstart, base) - if a:findstart - " locate the start of the word - let line = getline('.') - let start = col('.') - 1 - while start > 0 && line[start-1] =~ '\k\|\.' "keyword - let start -= 1 - endwhile + if a:findstart + " locate the start of the word + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start-1] =~ '\k\|\.' "keyword + let start -= 1 + endwhile echo start - return start - else - " find months matching with "a:base" - let res = [] + python << endpython +current_line = vim.current.line +endpython + return start + else + " find months matching with "a:base" + let res = [] python << endpython base = vim.eval("a:base") findstart = vim.eval("a:findstart") -msg_id = km.shell_channel.complete(base, vim.current.line, vim.eval("col('.')")) +msg_id = km.shell_channel.complete(base, current_line, vim.eval("col('.')")) try: m = get_child_msg(msg_id) matches = m['content']['matches'] @@ -515,7 +660,6 @@ for c in completions: vim.command('call add(res,"'+c+'")') endpython "call extend(res,completions) - return res - endif - endfun -set completefunc=CompleteIPython + return res + endif + endfun