#@+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