├── .gitignore ├── README.rst ├── examples ├── example1.vim ├── example2.vim ├── example3.vim ├── example4.vim ├── example5.vim ├── example6.vim └── example7.vim ├── setup.py ├── tests ├── mocks │ └── vim.py └── test_vim_bridge.py └── vim_bridge ├── __init__.py └── registry.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg 2 | *.egg-info 3 | *.pyc 4 | /build 5 | /dist 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ===================================== 2 | vim_bridge - a Python-to-Vim bridge 3 | ===================================== 4 | 5 | What is it? 6 | ----------- 7 | vim_bridge_ is a Python-to-Vim bridge decorator that allows transparent calls 8 | to Python functions in native Vim scripts. 9 | 10 | 11 | Installation 12 | ------------ 13 | Simply install the vim_bridge_ Python package, using setuptools, 14 | ``easy_install``, or ``pip``. 15 | 16 | 17 | .. _vim_bridge: http://pypi.python.org/pypi/vim_bridge/ 18 | 19 | 20 | Usage 21 | ----- 22 | In a Vim script, decorate your Python functions as follows to expose them as 23 | native Vim callables. Both arguments and return values are casted so it should 24 | be transparent:: 25 | 26 | python << endpython 27 | from vim_bridge import bridged 28 | 29 | @bridged 30 | def SayHello(first, last): 31 | return "Hello, %s %s!" % (first, last) 32 | 33 | endpython 34 | 35 | " Now call directly into the Python function! 36 | echo SayHello("John", "Doe") 37 | " prints "Hello, John Doe!" 38 | 39 | 40 | Supported 41 | --------- 42 | The following data types have proven to work: 43 | 44 | * Strings 45 | * Integers 46 | * Lists 47 | * Exceptions 48 | 49 | 50 | More examples 51 | ------------- 52 | Passing in a list:: 53 | 54 | python << endpython 55 | from vim_bridge import bridged 56 | 57 | @bridged 58 | def GetLongest(list): 59 | return max(map(lambda s: len(s), list)) 60 | 61 | endpython 62 | 63 | echo GetLongest(['one', 'two', 'three', 'four']) 64 | " returns 5 (because "three" is 5 chars long) 65 | 66 | 67 | Catching exceptions:: 68 | 69 | python << endpython 70 | from vim_bridge import bridged 71 | 72 | @bridged 73 | def WillCauseException(): 74 | raise Exception("Oops") 75 | 76 | endpython 77 | 78 | " This will throw an error to the user... 79 | echo WillCauseException() 80 | 81 | " But here's how you can catch that in Vim 82 | try 83 | echo WillCauseException() 84 | catch 85 | echo "Something went wrong. Aborting." 86 | finally 87 | echo "Cleaning up." 88 | endtry 89 | 90 | 91 | Using Python stdlib functions to do work that would be more difficult using 92 | pure Vim scripting:: 93 | 94 | python << END 95 | import os.path 96 | from vim_bridge import bridged 97 | 98 | @bridged 99 | def NormalizePath(path): 100 | return os.path.realpath(path) 101 | END 102 | 103 | echo NormalizePath("/this/../or/./.././that/is/./a/.//very/../obscure/..//././long/./../path/name") 104 | echo NormalizePath("..") 105 | 106 | 107 | You can use the bridged function definitions within a Python block itself, or 108 | from inside Vim, it does not matter. In this example, NormalizePath is called 109 | from both Python and Vim:: 110 | 111 | python << END 112 | import os.path 113 | from vim_bridge import bridged 114 | 115 | @bridged 116 | def NormalizePath(path): 117 | return os.path.realpath(path) 118 | 119 | @bridged 120 | def RealPath(path): 121 | # It does not matter if you call NormalizePath from here... 122 | return NormalizePath(path) 123 | END 124 | 125 | " ...or from here 126 | echo NormalizePath("/this/../or/./.././that/is/./a/.//very/../obscure/..//././long/./../path/name") 127 | echo RealPath("..") 128 | 129 | 130 | Since vim_bridge 0.4, the function name casing convention is automatically 131 | converted to match Vim's conventions (and *requirement* even, since function 132 | names **must** start with a capital letter). Besides casing, prefixing the 133 | Python function with an underscore will lead to the function being defined in 134 | the Vim context as a ````-prefixed function (i.e. a "private" function 135 | that cannot be called from outside the script):: 136 | 137 | python << eop 138 | import os 139 | import vim 140 | from vim_bridge import bridged 141 | 142 | @bridged 143 | def public(): 144 | return "I am public." 145 | 146 | @bridged 147 | def _private(): 148 | return "I am private (available in the current script only)." 149 | 150 | @bridged 151 | def my_name_is_auto_converted(): 152 | return "In Python, I'm called my_name_is_auto_converted, " + \ 153 | "but in Vim, I'm called MyNameIsAutoConverted :)" 154 | 155 | @bridged 156 | def _long_private_name(): 157 | return "I'm private, and my case is converted automatically." 158 | eop 159 | 160 | echo Public() 161 | echo s:Private() 162 | echo MyNameIsAutoConverted() 163 | echo s:LongPrivateName() 164 | 165 | -------------------------------------------------------------------------------- /examples/example1.vim: -------------------------------------------------------------------------------- 1 | python << endpython 2 | from vim_bridge import bridged 3 | 4 | @bridged 5 | def SayHello(first, last): 6 | return "Hello, %s %s!" % (first, last) 7 | 8 | endpython 9 | 10 | " Now call directly into the Python function! 11 | echo SayHello("John", "Doe") 12 | " prints "Hello, John Doe!" 13 | -------------------------------------------------------------------------------- /examples/example2.vim: -------------------------------------------------------------------------------- 1 | python << endpython 2 | from vim_bridge import bridged 3 | 4 | @bridged 5 | def GetLongest(list): 6 | return max(map(lambda s: len(s), list)) 7 | 8 | endpython 9 | 10 | echo GetLongest(['one', 'two', 'three', 'four']) 11 | " returns 5 (because "three" is 5 chars long) 12 | -------------------------------------------------------------------------------- /examples/example3.vim: -------------------------------------------------------------------------------- 1 | python << endpython 2 | from vim_bridge import bridged 3 | 4 | @bridged 5 | def WillCauseException(): 6 | raise Exception("Oops") 7 | 8 | endpython 9 | 10 | " This will throw an error to the user... 11 | echo WillCauseException() 12 | 13 | " But here's how you can catch that in Vim 14 | try 15 | echo WillCauseException() 16 | catch 17 | echo "Something went wrong. Aborting." 18 | finally 19 | echo "Cleaning up." 20 | endtry 21 | -------------------------------------------------------------------------------- /examples/example4.vim: -------------------------------------------------------------------------------- 1 | python << END 2 | import os.path 3 | from vim_bridge import bridged 4 | 5 | @bridged 6 | def NormalizePath(path): 7 | return os.path.realpath(path) 8 | END 9 | 10 | echo NormalizePath("/this/../or/./.././that/is/./a/.//very/../obscure/..//././long/./../path/name") 11 | echo NormalizePath("..") 12 | -------------------------------------------------------------------------------- /examples/example5.vim: -------------------------------------------------------------------------------- 1 | python << END 2 | import os.path 3 | from vim_bridge import bridged 4 | 5 | @bridged 6 | def NormalizePath(path): 7 | return os.path.realpath(path) 8 | 9 | @bridged 10 | def RealPath(path): 11 | # It does not matter if you call NormalizePath from here... 12 | return NormalizePath(path) 13 | END 14 | 15 | " ...or from here 16 | echo NormalizePath("/this/../or/./.././that/is/./a/.//very/../obscure/..//././long/./../path/name") 17 | echo RealPath("..") 18 | -------------------------------------------------------------------------------- /examples/example6.vim: -------------------------------------------------------------------------------- 1 | python << eop 2 | import os 3 | import vim 4 | from vim_bridge import bridged 5 | 6 | @bridged 7 | def public(): 8 | return "I am public." 9 | 10 | @bridged 11 | def _private(): 12 | return "I am private (available in the current script only)." 13 | 14 | @bridged 15 | def my_name_is_auto_converted(): 16 | return "In Python, I'm called my_name_is_auto_converted, but in Vim, I'm called MyNameIsAutoConverted :)" 17 | 18 | @bridged 19 | def _long_private_name(): 20 | return "I'm private, and my case is converted automatically." 21 | eop 22 | 23 | echo Public() 24 | echo s:Private() 25 | echo MyNameIsAutoConverted() 26 | echo s:LongPrivateName() 27 | -------------------------------------------------------------------------------- /examples/example7.vim: -------------------------------------------------------------------------------- 1 | python << END 2 | import os.path 3 | from vim_bridge import bridged 4 | 5 | @bridged 6 | def is_cool(name): 7 | return name == 'Mr. Freeze' 8 | 9 | @bridged 10 | def dont_return_anything(): 11 | print "I'm a function without return value." 12 | END 13 | 14 | echo IsCool("Mr. Freeze") 15 | echo DontReturnAnything() 16 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import codecs 4 | try: 5 | from setuptools import setup, find_packages 6 | except ImportError: 7 | from ez_setup import use_setuptools 8 | use_setuptools() 9 | from setuptools import setup, find_packages 10 | 11 | if os.path.exists("README.rst"): 12 | long_description = codecs.open('README.rst', "r", "utf-8").read() 13 | else: 14 | long_description = "See http://github.com/nvie/vim_bridge/tree/master" 15 | 16 | from vim_bridge import __version__ as version 17 | 18 | setup( 19 | name="vim_bridge", 20 | version=version, 21 | description="A Python-to-Vim bridge decorator that allows transparent calls to Python functions in native Vim scripts.", 22 | author="Vincent Driessen", 23 | author_email="vincent@datafox.nl", 24 | url="http://github.com/nvie/vim_bridge", 25 | platforms=["any"], 26 | license="BSD", 27 | packages=find_packages(), 28 | install_requires=[], 29 | tests_require=['nose'], 30 | test_suite="nose.collector", 31 | zip_safe=False, 32 | classifiers=[ 33 | # Picked from 34 | # http://pypi.python.org/pypi?:action=list_classifiers 35 | #"Development Status :: 1 - Planning", 36 | #"Development Status :: 2 - Pre-Alpha", 37 | #"Development Status :: 3 - Alpha", 38 | "Development Status :: 4 - Beta", 39 | #"Development Status :: 5 - Production/Stable", 40 | #"Development Status :: 6 - Mature", 41 | #"Development Status :: 7 - Inactive", 42 | "Operating System :: OS Independent", 43 | "Programming Language :: Python", 44 | "Programming Language :: Python :: 2.4", 45 | "Programming Language :: Python :: 2.5", 46 | "Programming Language :: Python :: 2.6", 47 | "Programming Language :: Python :: 2.7", 48 | "Intended Audience :: Developers", 49 | "License :: OSI Approved :: BSD License", 50 | "Operating System :: POSIX", 51 | "Topic :: Text Editors", 52 | ], 53 | long_description=long_description, 54 | ) 55 | -------------------------------------------------------------------------------- /tests/mocks/vim.py: -------------------------------------------------------------------------------- 1 | # Mock of the vim module, for testing outside of vim 2 | from mock import Mock 3 | 4 | command = Mock() 5 | -------------------------------------------------------------------------------- /tests/test_vim_bridge.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | # Fake the system path to direct "import vim" calls to our mock module 4 | import sys 5 | sys.path = ['tests/mocks'] + sys.path 6 | 7 | # Stub out the random function 8 | import vim_bridge 9 | vim_bridge._rand = lambda: 3 10 | 11 | from vim_bridge import bridged 12 | 13 | 14 | class TestHelperFunctions(unittest.TestCase): 15 | 16 | def test_upcase_first(self): 17 | from vim_bridge import _upcase_first as ucf 18 | self.assertEquals(ucf(''), '') 19 | self.assertEquals(ucf('a'), 'A') 20 | self.assertEquals(ucf('A'), 'A') 21 | self.assertEquals(ucf('_foo'), '_foo') 22 | 23 | def test_convert_function(self): 24 | from vim_bridge import _convert_function_name as cfn 25 | self.assertEquals(cfn('foo'), (False, 'Foo')) 26 | self.assertEquals(cfn('_foo'), (True, 'Foo')) 27 | self.assertEquals(cfn('_ThiS_is_A_MiXed__case_'), \ 28 | (True, 'ThiSIsAMiXedCase')) 29 | 30 | def test_get_arguments(self): 31 | from vim_bridge import _get_arguments as ga 32 | 33 | def foo(a,b,c): pass 34 | def bar(x,y): 35 | z = 3 36 | z = z + x + y 37 | return z 38 | self.assertEquals(ga(foo), ('a','b','c')) 39 | self.assertEquals(ga(bar), ('x','y')) 40 | 41 | def test_cast_to_vimsafe_result(self): 42 | from vim_bridge import _cast_to_vimsafe_result as cast 43 | 44 | self.assertEquals(cast(3), "3") 45 | self.assertEquals(cast('3'), "'3'") 46 | self.assertEquals(cast(''), "''") 47 | self.assertEquals(cast(None), "") 48 | self.assertEquals(cast(True), "1") 49 | self.assertEquals(cast(False), "0") 50 | self.assertEquals(cast([1,2,3]), "[1, 2, 3]") 51 | 52 | 53 | class TestBridgedDecorator(unittest.TestCase): 54 | 55 | def _strip_whitespace(self, x): 56 | lines = x.split("\n") 57 | lines = [line.strip() for line in lines] 58 | x = "\n".join([s for s in lines if s]) 59 | return x 60 | 61 | def assertCodeEquals(self, x, y): 62 | self.assertEquals(self._strip_whitespace(x), self._strip_whitespace(y)) 63 | 64 | def test_no_bridges_yet(self): 65 | from vim_bridge.registry import func_register 66 | self.assertEquals(func_register, {}) 67 | 68 | def test_simple_bridge(self): 69 | from vim_bridge.registry import func_register 70 | import vim 71 | 72 | self.assertFalse('foo' in func_register) 73 | self.assertFalse(vim.command.called) 74 | 75 | @bridged 76 | def foo(x,y): pass 77 | 78 | self.assertTrue('foo' in func_register) 79 | self.assertTrue(vim.command.called) 80 | self.assertCodeEquals(vim.command.call_args[0][0], \ 81 | """ 82 | fun! Foo(x, y) 83 | python << endp 84 | __vim_brdg_3_x = vim.eval("a:x") 85 | __vim_brdg_3_y = vim.eval("a:y") 86 | 87 | from vim_bridge.registry import func_register as fr 88 | from vim_bridge import _cast_to_vimsafe_result as c2v 89 | 90 | __vim_brdg_3_result = c2v(fr["foo"](__vim_brdg_3_x, __vim_brdg_3_y)) 91 | vim.command("return %s" % repr(__vim_brdg_3_result)) 92 | 93 | del __vim_brdg_3_x 94 | del __vim_brdg_3_y 95 | del __vim_brdg_3_result 96 | endp 97 | endf 98 | """) 99 | 100 | -------------------------------------------------------------------------------- /vim_bridge/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from vim_bridge.registry import func_register 4 | 5 | __all__ = ['bridged', '_cast_to_vimsafe_result', '__version__'] 6 | 7 | VERSION = (0, 6) 8 | __version__ = ".".join(map(str, VERSION[0:2])) 9 | 10 | 11 | def _rand(): 12 | import random 13 | random.seed() 14 | return random.randint(1000, 9999) 15 | 16 | 17 | def _upcase_first(s): 18 | if len(s) == 0: 19 | return s 20 | else: 21 | return s[0].upper() + s[1:] 22 | 23 | 24 | def _convert_function_name(fname): 25 | private = fname.startswith('_') 26 | if private: 27 | fname = fname[1:] 28 | fname = "".join([_upcase_first(part) for part in fname.split('_')]) 29 | return (private, fname) 30 | 31 | 32 | def _get_arguments(func): 33 | return func.__code__.co_varnames[:func.__code__.co_argcount] 34 | 35 | 36 | def _cast_to_vimsafe_result(value): 37 | if value is None: 38 | # The string 'None' means nothing in Vim 39 | return '' 40 | elif type(value) == bool: 41 | return str(int(value)) 42 | else: 43 | # Default fallback is the Python representation as a string 44 | # The representation looks very similar to Vim's, so this should be 45 | # safe for most results 46 | return repr(value) 47 | 48 | 49 | def bridged(fin): 50 | import vim 51 | func_register[fin.__name__] = fin 52 | 53 | func_args = _get_arguments(fin) 54 | 55 | private, vimname = _convert_function_name(fin.__name__) 56 | private = private and "s:" or "" 57 | 58 | prefix = '__vim_brdg_%d_' % _rand() 59 | 60 | lines = ['fun! %s%s(%s)' % (private, vimname, ", ".join(func_args))] 61 | # Execute with same Python that we are running. 62 | python = 'python' if sys.version_info == 2 else 'python3' 63 | lines.append(python + ' << endp') 64 | for arg in func_args: 65 | lines.append('%s%s = vim.eval("a:%s")' % (prefix, arg, arg)) 66 | lines.append('from vim_bridge.registry import func_register as fr') 67 | lines.append('from vim_bridge import _cast_to_vimsafe_result as c2v') 68 | lines.append('%sresult = c2v(fr["%s"](%s))' % (prefix, fin.__name__, 69 | ", ".join([prefix + s for s in func_args]))) 70 | lines.append('vim.command("return %%s" %% repr(%sresult))' % prefix) 71 | for arg in func_args: 72 | #lines.append('try:') 73 | lines.append('del %s%s' % (prefix, arg)) 74 | #lines.append('except NameError: pass') 75 | lines.append('del %sresult' % prefix) 76 | lines.append('endp') 77 | lines.append('endf') 78 | vim.command("\n".join(lines)) 79 | 80 | return fin 81 | -------------------------------------------------------------------------------- /vim_bridge/registry.py: -------------------------------------------------------------------------------- 1 | func_register = {} 2 | --------------------------------------------------------------------------------