Source code for leo.plugins.vim

#@+leo-ver=5-thin
#@+node:EKR.20040517075715.10: * @file vim.py
#@+<< docstring >>
#@+node:ekr.20050226184411: ** << docstring >>
'''
#@@language rest

Enables two-way communication with gVim (recommended) or Vim.

Commands
--------

``vim-open-file``
    Opens the nearest ancestor @file or @clean node in vim. Leo will update the
    file in the outline when you save the file in vim.

``vim-open-node``
    Opens the selected node in vim. Leo will update the node in the outline when
    you save the file in vim.

Installation
------------

Set the ``vim_cmd`` and ``vim_exe`` settings as shown below.

Alternatively, you can put gvim.exe is on your PATH.

Settings
--------

``@string vim_cmd``
    The command to execute to start gvim. Something like::

        <path-to-gvim>/gvim --servername LEO

``@string vim_exe``
    The path to the gvim executable.

``vim_plugin_uses_tab_feature``
    True: Leo will put the node or file in a Vim tab card.

'''
#@-<< docstring >>
#@+<< version history >>
#@+node:ekr.20050226184411.1: ** << version history >>
#@@killcolor
#@+at
# 
# Contributed by Andrea Galimberti.
# Edited by Felix Breuer.
# 
# 1.5 EKR:
#     - Added new sections.
#     - Move most comments into docstring.
#     - Added useDoubleClick variable.
#     - Added init function.
#     - Init _vim_cmd depending on sys.platform.
# 1.6 EKR:
#     - Use keywords to get c, not g.top().
#     - Don't use during unit testing: prefer xemacs instead.
#     - Added _vim_exe
#     - Use "os.spawnv" instead of os.system.
#     - Simplified the search of g.app.openWithFiles.
#     - Fixed bug in open_in_vim: hanged v.bodyString to v.bodyString()
# 1.7 EKR: Excellent new code by Jim Sizelove solves weird message on first open of vim.
# 1.8 EKR: Set subprocess = None if import fails.
# 1.9 EKR:
#     - Document how install subproces, and use g.importExtension to import subprocess.
#     - Import subprocess with g.importExtension.
# 1.10 EKR:
#     - Support 'vim_cmd' and 'vim_exe' settings.
#     - These override the default _vim_cmd and _vim_exe settings.
# 1.11 EKR: Emergency default for window is now the default location: c:\Program Files\vim\vim63
# 1.12 EKR:
#     - Added emergency default for 'darwin'.
#     - Corrected the call to openWith.  It must now use data=data due to a new event param.
# 1.13 EKR: The docstring now states that the open_with plugin must be enabled for this to work.
# 1.14 EKR: Emphasized that the open_with plugin must be enabled.
# 1.15 EKR: Don't open @url nodes in vim if @bool vim_plugin_opens_url_nodes setting is False.
# 1.16 TL: open_in_vim modifications
#     - support file open in gVim at same line number as Leo cursor location
#     - support file open in a gVim tab
# 1.17 EKR: Give a location message to help with settings.
# 1.18 VMV:
#     - Use gvim on Linux too, emergency default on Windows doesn't have explicit path
#     - Works when subprocess.Popen(shell=True)
# 2.0 EKR: Use *only* the vim-open-node command.  Do not pollute click handlers.
#@-<< version history >>
#@+<< documentation from Jim Sizelove >>
#@+node:ekr.20050909102921: ** << documentation from Jim Sizelove >>
#@+at
#@@language rest
#@@wrap
# 
# I was trying to get Leo to work more effectively with Vim, my editor of choice.
# To do so, I made several changes to Leo which (I believe) make it work better.
# 
# After much exploring and trying various things, I made a change to the os.spawnv
# section of the openWith function in leoCommands.py. This added line seems to
# prevent the "weird error message on first open of Vim." (vim.py, line 32) when
# opening Vim with os.spawnv.
# 
# os.spawnv needs the command it is calling as the first argument in the args list
# in addition, so the command actually shows twice in the total args to os.spawnv.
# For example::
# 
#     os.spawnv(os.P_NOWAIT, "C:/Program Files/Vim/vim63/gvim.exe",
#         ["gvim.exe", "--servername", "LEO", "--remote", "foo.txt"])
# 
# If the call is made without the command-name as the first item in the list of
# args, like so::
# 
#     os.spawnv(os.P_NOWAIT, "C:/Program Files/Vim/vim63/gvim.exe",
#         ["--servername", "LEO", "--remote", "foo.txt"])
# 
# an error message pops up::
# 
#     E247: no registered server named "GVIM": Send failed.  Trying to execute locally
# 
# This message means that gVim is not looking for a server named "LEO", which
# presumably the user has already opened with the command "gvim --servername LEO".
# Instead it is looking for a server named "GVIM", and not finding it, opens the
# files "foo.txt" and "LEO" (notice that it didn't catch the "--servername"
# argument and thinks that "LEO" is the name of a new file to create) in two
# buffers in a local copy of gVim. Now, if the command is::
# 
#     os.spawnv(
#         os.P_NOWAIT, "C:/Program Files/Vim/vim63/gvim.exe",
#         ["gvim.exe", "--servername", "LEO", "--remote", "foo.txt"])
# 
# Everything works great, as long as the user doesn't close the gVim window. If
# the user has closed the gVim window, then tries to open a node in Vim, they will
# see this error message::
# 
#     E247: no registered server named "LEO": Send failed.
# 
# Trying to execute locally If you use the ``--remote-silent`` argument, gVim will
# start the LEO server without the error message.
# 
# You can see which servers gVim has running by typing the following at the
# command prompt::
# 
#     vim --serverlist
# 
# The rest of my changes have to do with using the subprocess module instead of
# the os.system, and various os.spawn* calls. I find subprocess easier to
# understand, and it is fairly simple to use for the most common kinds of process
# calls, but is capable of all the variations you may need. It is designed to
# replace all the os.system, os.spawn, and popen calls. It is available in Python
# 2.4.
# 
# So I added some lines to use subprocess in the OpenWith plugin and the Vim
# plugin.
# 
# I also have added a table in the "create_open_with_menu" function that makes use
# of the various editors I have used at times. Most of those editors are called
# with subprocess.Popen.
#@-<< documentation from Jim Sizelove >>
#@+<< imports >>
#@+node:ekr.20050226184411.2: ** << imports >>
import leo.core.leoGlobals as g
import os
import subprocess
import sys
#@-<< imports >>
# This command is used to communicate with the vim server. If you use gvim
# you can leave the command as is, you do not need to change it to "gvim ..."
# New in version 1.10 of this plugin: these are emergency defaults only.
# They are typically overridden by the corresponding 'vim_cmd' and 'vim_exe' settings in
# leoSettings.leo or individual .leo files.
if sys.platform == 'win32':
    # Works on XP if you have gvim on PATH
    _vim_cmd = "gvim --servername LEO"
    _vim_exe = "gvim"
elif sys.platform == 'darwin':
    _vim_cmd = "/Applications/gvim.app/Contents/MacOS/gvim --servername LEO"
    _vim_exe = "gvim"
else:
    _vim_cmd = "gvim --servername LEO"
    _vim_exe = "gvim"
# Global message flags.
contextmenu_message_given = False
locationMessageGiven = False
#@+others
#@+node:ekr.20050226184624: ** init
[docs]def init(): '''Return True if the plugin has loaded successfully.''' ok = not g.app.unitTesting # Don't conflict with xemacs plugin. if ok: # print ('vim.py enabled') # Register the handlers... # event = 'open2' # g.registerHandler(event,on_open_window) # Enable the os.system call if you want to # start a (g)vim server when Leo starts. if 0: os.system(_vim_cmd) g.plugin_signon(__name__) return ok
#@+node:ekr.20150326150910.1: ** g.command('vim-open-file')
[docs]@g.command('vim-open-file') def vim_open_file_command(event): '''vim.py: Open the entire file in (g)vim.''' c = event.get('c') if c: VimCommander(c, entire_file=True)
#@+node:ekr.20120315101404.9745: ** g.command('vim-open-node')
[docs]@g.command('vim-open-node') def vim_open_node_command(event): '''vim.py: open the selected node in (g)vim.''' c = event.get('c') if c: VimCommander(c, entire_file=False)
#@+node:ekr.20150326153420.1: ** class VimCommander
[docs]class VimCommander(object): '''A class implementing the vim plugin.''' #@+others #@+node:ekr.20150326155343.1: *3* vim.ctor def __init__(self, c, entire_file): '''Ctor for the VimCommander class.''' self.c = c self.entire_file = entire_file # compute settings. getBool, getString = c.config.getBool, c.config.getString self.open_url_nodes = getBool('vim_plugin_opens_url_nodes') self.trace = False or getBool('vim_plugin_trace') self.uses_tab = getBool('vim_plugin_uses_tab_feature') self.vim_cmd = getString('vim_cmd') or _vim_cmd self.vim_exe = getString('vim_exe') or _vim_exe # Give messages. global locationMessageGiven if self.trace and not locationMessageGiven: locationMessageGiven = True print('vim_cmd: %s' % self.vim_cmd) print('vim_exe: %s' % self.vim_exe) self.open_in_vim() #@+node:ekr.20150326183310.1: *3* vim.error
[docs] def error(self, s): '''Report an error.''' g.es_print(s, color='red')
#@+node:ekr.20120315101404.9746: *3* vim.open_in_vim & helpers
[docs] def open_in_vim(self): '''Open p in vim, or the entire enclosing file if entire_file is True.''' p = self.c.p if not self.check_args(): return root = self.find_root(p) if self.entire_file else p if not root: return path = self.find_path_for_node(root) if path and self.should_open_old_file(path, root): cmd = self.vim_cmd + "--remote-send '<C-\\><C-N>:e " + path + "<CR>'" if self.trace: g.trace('os.system(%s)' % cmd) os.system(cmd) else: # Open a new temp file. if path: self.forget_path(path) self.open_file(root)
#@+node:ekr.20150326183613.1: *4* vim.check_args & helper
[docs] def check_args(self): '''Return True of basic checks pass.''' p = self.c.p contextMenu = self.load_context_menu() if not contextMenu: return False if not self.open_url_nodes and p.h.startswith('@url'): return False else: return True
#@+node:ekr.20150326154203.1: *5* vim.load_context_menu
[docs] def load_context_menu(self): '''Load the contextmenu plugin.''' global contextmenu_message_given contextMenu = g.loadOnePlugin('contextmenu.py', verbose=True) if not contextMenu and not contextmenu_message_given: contextmenu_message_given = True self.error('can not load contextmenu.py') return contextMenu
#@+node:ekr.20150326180515.1: *4* vim.find_path_for_node
[docs] def find_path_for_node(self, p): '''Search the open-files list for a file corresponding to p.''' efc = g.app.externalFilesController path = efc.find_path_for_node(p) return path
#@+node:ekr.20150326173414.1: *4* vim.find_root
[docs] def find_root(self, p): '''Return the nearest ancestor @auto or @clean node.''' assert self.entire_file for p2 in p.self_and_parents(): if p2.isAnyAtFileNode(): return p2 self.error('no parent @auto or @clean node: %s' % p.h) return None
#@+node:ekr.20150326173301.1: *4* vim.forget_path
[docs] def forget_path(self, path): ''' Stop handling the path: - Remove the path from the list of open-with files. - Send a command to vim telling it to close the path. ''' assert path # Don't do this: it prevents efc from reopening paths. # efc = g.app.externalFilesController # if efc: efc.forget_path(path) if 0: # Dubious. if g.os_path_exists(path): os.remove(path) cmd = self.vim_cmd + "--remote-send '<C-\\><C-N>:bd " + path + "<CR>'" os.system(cmd)
#@+node:ekr.20150326181247.1: *4* vim.get_cursor_arg
[docs] def get_cursor_arg(self): '''Compute the cursor argument for vim.''' wrapper = self.c.frame.body.wrapper s = wrapper.getAllText() ins = wrapper.getInsertPoint() row, col = g.convertPythonIndexToRowCol(s, ins) return "+" + str(row + 1)
# This is an Ex command, not a normal Vim command. See: # http://vimdoc.sourceforge.net/htmldoc/remote.html # and # http://pubs.opengroup.org/onlinepubs/9699919799/utilities/ex.html#tag_20_40_13_02 #@+node:ekr.20150326180928.1: *4* vim.open_file
[docs] def open_file(self, root): '''Open the the file in vim using c.openWith.''' c = self.c efc = g.app.externalFilesController # Common arguments. tab_arg = "-tab" if self.uses_tab else "" remote_arg = "--remote" + tab_arg + "-silent" args = [self.vim_exe, "--servername", "LEO", remote_arg] # No cursor arg. if self.entire_file: # vim-open-file args.append('+0') # Go to first line of the file. This is an Ex command. assert root.isAnyAtFileNode(), root dir_ = g.setDefaultDirectory(c, root) fn = c.os_path_finalize_join(dir_, root.anyAtFileNodeName()) else: # vim-open-node args.append(self.get_cursor_arg()) # Set the cursor position to the current line in the node. ext = 'txt' fn = efc.create_temp_file(c, ext, c.p) c_arg = '%s %s' % (' '.join(args), fn) command = 'subprocess.Popen(%s,shell=True)' % c_arg try: subprocess.Popen(c_arg, shell=True) except OSError: g.es_print(command) g.es_exception()
#@+node:ekr.20150326173000.1: *4* vim.should_open_old_file
[docs] def should_open_old_file(self, path, root): '''Return True if we should open the old temp file.''' v = root.v return ( path and g.os_path_exists(path) and hasattr(v.b, '_vim_old_body') and v.b == v._vim_old_body )
#@+node:ekr.20150326175258.1: *3* vim.write_root (not used)
[docs] def write_root(self, root): '''Return the concatenation of all bodies in p's tree.''' result = [] for p in root.self_and_subtree(): s = p.b result.append(s if s.endswith('\n') else s.rstrip() + '\n') return ''.join(result)
#@-others #@-others #@@language python #@@tabwidth -4 #@-leo