├── .gitignore ├── LICENSE ├── Makefile ├── Package.swift ├── PluginGenerator ├── Makefile.tpl ├── Package.tpl.swift ├── PluginMain.tpl.swift ├── plugin.tpl.vim └── plugin_generator.sh ├── README.md ├── Sources ├── Example │ └── Example.swift ├── Vim │ ├── Vim.swift │ ├── VimBuffer.swift │ ├── VimCurrent.swift │ ├── VimExtensions.swift │ ├── VimPlugin.swift │ ├── VimValue.swift │ └── VimWindow.swift ├── VimAsync │ ├── VimTask.swift │ └── VimTimer.swift ├── VimInterface │ ├── include │ │ └── VimInterface │ │ │ ├── module.modulemap │ │ │ └── swiftvim.h │ └── swiftvim_lib.c └── VimPluginBootstrap │ ├── include │ └── VimPluginBootstrap │ │ └── module.modulemap │ └── swiftvim.c ├── Tests ├── LinuxMain.swift ├── VimAsyncTests │ └── VimAsyncTest.swift ├── VimInterfaceTests │ ├── CallingTests.swift │ └── MockVimRuntime │ │ └── vim.py └── VimTests │ └── VimTests.swift └── VimUtils └── make_lib.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | compile_commands.json 6 | *.pyc 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, swift-vim contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | The views and conclusions contained in the software and documentation are those 25 | of the authors and should not be interpreted as representing official policies, 26 | either expressed or implied, of the swift-vim project. 27 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=bash 2 | 3 | LAST_LOG=.build/last_build.log 4 | PWD=$(shell pwd) 5 | 6 | PLUGIN_NAME=Example 7 | TRIPPLE=x86_64-apple-macosx10.12 8 | BUILD_DIR=$(PWD)/.build/$(CONFIG) 9 | 10 | # Generate a plugin setup to work with Vim 11 | .PHONY: generate 12 | generate: 13 | @./PluginGenerator/plugin_generator.sh 14 | 15 | .PHONY: debug 16 | debug: CONFIG=debug 17 | debug: plugin_so 18 | 19 | .PHONY: release 20 | release: CONFIG=release 21 | release: vim_lib plugin_so 22 | 23 | BASE_OPTS=-Xcc -I$(PYTHON_INCLUDE) \ 24 | -Xcc -DVIM_PLUGIN_NAME=$(PLUGIN_NAME) \ 25 | -Xlinker $(PYTHON_LINKED_LIB) \ 26 | -Xcc -fvisibility=hidden \ 27 | -Xlinker -undefined -Xlinker dynamic_lookup \ 28 | -Xlinker -all_load 29 | 30 | 31 | # Build namespaced versions of Vim and VimAsync libs. 32 | # The modules have a prefix of the plugin name, to avoid conflicts 33 | # when the code is linked into the Vim process. 34 | # The module is imported as "import $(PLUGIN_NAME)Vim" 35 | # FIXME: Consider other ways to do this that work transitively and 36 | # doesn't trigger rebuilds 37 | 38 | 39 | .PHONY: vim_lib, renamed_vim_lib 40 | vim_lib: SWIFT_OPTS=--product Vim \ 41 | -Xswiftc -module-name=$(PLUGIN_NAME)Vim \ 42 | -Xswiftc -module-link-name=$(PLUGIN_NAME)Vim \ 43 | $(BASE_OPTS) 44 | renamed_vim_lib: vim_lib 45 | @ditto $(BUILD_DIR)/Vim.swiftmodule \ 46 | $(BUILD_DIR)/$(PLUGIN_NAME)Vim.swiftmodule 47 | @ditto $(BUILD_DIR)/Vim.swiftdoc \ 48 | $(BUILD_DIR)/$(PLUGIN_NAME)Vim.swiftdoc 49 | @ditto $(BUILD_DIR)/libVim.dylib \ 50 | $(BUILD_DIR)/lib$(PLUGIN_NAME)Vim.dylib 51 | 52 | .PHONY: vim_async_lib, renamed_vim_async_lib 53 | vim_async_lib: SWIFT_OPTS=--product VimAsync \ 54 | -Xswiftc -module-name=$(PLUGIN_NAME)VimAsync \ 55 | -Xswiftc -module-link-name=$(PLUGIN_NAME)VimAsync \ 56 | $(BASE_OPTS) 57 | renamed_vim_async_lib: vim_async_lib 58 | @ditto $(BUILD_DIR)/VimAsync.swiftmodule \ 59 | $(BUILD_DIR)/$(PLUGIN_NAME)VimAsync.swiftmodule 60 | @ditto $(BUILD_DIR)/VimAsync.swiftdoc \ 61 | $(BUILD_DIR)/$(PLUGIN_NAME)VimAsync.swiftdoc 62 | @ditto $(BUILD_DIR)/libVimAsync.dylib \ 63 | $(BUILD_DIR)/lib$(PLUGIN_NAME)VimAsync.dylib 64 | 65 | # Main plugin lib 66 | .PHONY: plugin_lib 67 | plugin_lib: SWIFT_OPTS=--product $(PLUGIN_NAME) \ 68 | $(BASE_OPTS) 69 | plugin_lib: renamed_vim_lib renamed_vim_async_lib 70 | 71 | # Build the .so, which Vim dynamically links. 72 | .PHONY: plugin_so 73 | plugin_so: plugin_lib 74 | @clang -g \ 75 | -Xlinker $(PYTHON_LINKED_LIB) \ 76 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME).dylib \ 77 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME)VimAsync.dylib \ 78 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME)Vim.dylib \ 79 | -shared -o .build/$(PLUGIN_NAME).so 80 | 81 | # Build for the python dylib vim links 82 | .PHONY: py_vars 83 | py_vars: 84 | @source VimUtils/make_lib.sh; python_info 85 | $(eval PYTHON_LINKED_LIB=$(shell source VimUtils/make_lib.sh; linked_python)) 86 | $(eval PYTHON_INCLUDE=$(shell source VimUtils/make_lib.sh; python_inc_dir)) 87 | 88 | # SPM Build 89 | vim_lib vim_async_lib plugin_lib test_b: py_vars 90 | @echo "Building.." 91 | @mkdir -p .build 92 | @swift build -c $(CONFIG) \ 93 | $(BASE_OPTS) $(SWIFT_OPTS) $(EXTRA_OPTS) \ 94 | -Xswiftc -target -Xswiftc $(TRIPPLE) \ 95 | | tee $(LAST_LOG) 96 | 97 | # Mark - Internal Utils: 98 | 99 | # Overriding Python: 100 | # USE_PYTHON=/usr/local/Cellar/python/3.6.4_4/Frameworks/Python.framework/Versions/3.6/Python make test 101 | .PHONY: test 102 | test: CONFIG=debug 103 | test: EXTRA_OPTS= \ 104 | -Xcc -DSPMVIM_LOADSTUB_RUNTIME 105 | test: debug 106 | @echo "Testing.." 107 | @mkdir -p .build 108 | @swift build --product VimPackageTests \ 109 | $(BASE_OPTS) $(SWIFT_OPTS) $(EXTRA_OPTS) \ 110 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME).dylib \ 111 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME)VimAsync.dylib \ 112 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME)Vim.dylib \ 113 | -Xswiftc -target -Xswiftc $(TRIPPLE) | tee $(LAST_LOG) 114 | @swift test --skip-build 2>&1 | tee -a $(LAST_LOG) 115 | 116 | 117 | .PHONY: test_generate 118 | test_generate: 119 | # We use the HEAD ref in the test 120 | #git diff --quiet || (echo 'Dirty tree' && exit 1) 121 | rm -rf ~/Desktop/Swiftvimexample || true 122 | GIT_REPO=$(PWD)/.git \ 123 | plugin_path=~/Desktop/Swiftvimexample make generate 124 | cd ~/Desktop/Swiftvimexample && make 125 | 126 | clean: 127 | rm -rf .build/debug/* 128 | rm -rf .build/release/* 129 | 130 | # Generate the example 131 | PluginGenerator/PluginMain.tpl.swift: Sources/Example/Example.swift 132 | sed "s,Example,__VIM_PLUGIN_NAME__,g" $< > $@ 133 | 134 | # Build compile_commands.json 135 | # Unfortunately, we need to clean. 136 | # Use the last installed product incase we messed something up during 137 | # coding. 138 | compile_commands.json: SWIFT_OPTS=-Xswiftc -parseable-output \ 139 | -Xcc -I$(PYTHON_INCLUDE) \ 140 | -Xlinker $(PYTHON_LINKED_LIB) 141 | compile_commands.json: CONFIG=debug 142 | compile_commands.json: clean build_impl 143 | cat $(LAST_LOG) | /usr/local/bin/spm-vim compile_commands 144 | 145 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "Vim", 8 | products: [ 9 | .library( 10 | name: "VimInterface", 11 | type: .dynamic, 12 | targets: ["VimInterface"]), 13 | 14 | .library( 15 | name: "Vim", 16 | type: .dynamic, 17 | targets: ["Vim"]), 18 | 19 | .library( 20 | name: "VimPluginBootstrap", 21 | type: .dynamic, 22 | targets: ["VimPluginBootstrap"]), 23 | 24 | .library( 25 | name: "VimAsync", 26 | type: .dynamic, 27 | targets: ["VimAsync"]), 28 | 29 | .library( 30 | name: "Example", 31 | type: .dynamic, 32 | targets: ["Example"]), 33 | ], 34 | 35 | targets: [ 36 | .target(name: "Vim", 37 | dependencies: ["VimInterface", "VimPluginBootstrap"]), 38 | 39 | .target(name: "VimInterface", 40 | dependencies: []), 41 | 42 | .target(name: "VimPluginBootstrap", 43 | dependencies: []), 44 | 45 | // Async Support for Vim. Note, that this is OSX only and 46 | // depends on Foundation 47 | .target(name: "VimAsync", 48 | dependencies: []), 49 | 50 | // Tests 51 | .testTarget( 52 | name: "VimInterfaceTests", 53 | dependencies: ["VimInterface"]), 54 | .testTarget( 55 | name: "VimTests", 56 | dependencies: []), 57 | .testTarget( 58 | name: "VimAsyncTests", 59 | dependencies: []), 60 | // Example: 61 | .target(name: "Example", 62 | dependencies: []), 63 | ] 64 | ) 65 | -------------------------------------------------------------------------------- /PluginGenerator/Makefile.tpl: -------------------------------------------------------------------------------- 1 | SHELL=bash 2 | 3 | LAST_LOG=.build/last_build.log 4 | PWD=$(shell pwd) 5 | 6 | PLUGIN_NAME=__VIM_PLUGIN_NAME__ 7 | TRIPPLE=x86_64-apple-macosx10.12 8 | BUILD_DIR=.build/$(CONFIG) 9 | 10 | .PHONY: debug 11 | debug: CONFIG=debug 12 | debug: plugin_so 13 | 14 | .PHONY: release 15 | release: CONFIG=release 16 | release: vim_lib plugin_so 17 | 18 | BASE_OPTS=-Xcc -I$(PYTHON_INCLUDE) \ 19 | -Xcc -DVIM_PLUGIN_NAME=$(PLUGIN_NAME) \ 20 | -Xlinker $(PYTHON_LINKED_LIB) \ 21 | -Xcc -fvisibility=hidden \ 22 | -Xlinker -undefined -Xlinker dynamic_lookup \ 23 | -Xlinker -all_load 24 | 25 | # Build namespaced versions of Vim and VimAsync libs. 26 | # The modules have a prefix of the plugin name, to avoid conflicts 27 | # when the code is linked into the Vim process. 28 | # The module is imported as "import $(PLUGIN_NAME)Vim" 29 | # FIXME: Consider other ways to do this that work transitively and 30 | # doesn't trigger rebuilds 31 | 32 | .PHONY: vim_lib, renamed_vim_lib 33 | vim_lib: SWIFT_OPTS=--product Vim \ 34 | -Xswiftc -module-name=$(PLUGIN_NAME)Vim \ 35 | -Xswiftc -module-link-name=$(PLUGIN_NAME)Vim \ 36 | $(BASE_OPTS) 37 | renamed_vim_lib: vim_lib 38 | @ditto $(BUILD_DIR)/Vim.swiftmodule \ 39 | $(BUILD_DIR)/$(PLUGIN_NAME)Vim.swiftmodule 40 | @ditto $(BUILD_DIR)/Vim.swiftdoc \ 41 | $(BUILD_DIR)/$(PLUGIN_NAME)Vim.swiftdoc 42 | @ditto $(BUILD_DIR)/libVim.dylib \ 43 | $(BUILD_DIR)/lib$(PLUGIN_NAME)Vim.dylib 44 | 45 | .PHONY: vim_async_lib, renamed_vim_lib_async 46 | vim_async_lib: SWIFT_OPTS=--product VimAsync \ 47 | -Xswiftc -module-name=$(PLUGIN_NAME)VimAsync \ 48 | -Xswiftc -module-link-name=$(PLUGIN_NAME)VimAsync \ 49 | $(BASE_OPTS) 50 | renamed_vim_async_lib: vim_async_lib 51 | ditto $(BUILD_DIR)/VimAsync.swiftmodule \ 52 | $(BUILD_DIR)/$(PLUGIN_NAME)VimAsync.swiftmodule 53 | ditto $(BUILD_DIR)/VimAsync.swiftdoc \ 54 | $(BUILD_DIR)/$(PLUGIN_NAME)VimAsync.swiftdoc 55 | @ditto $(BUILD_DIR)/libVimAsync.dylib \ 56 | $(BUILD_DIR)/lib$(PLUGIN_NAME)VimAsync.dylib 57 | 58 | # Main plugin lib 59 | .PHONY: plugin_lib 60 | plugin_lib: SWIFT_OPTS=--product $(PLUGIN_NAME) \ 61 | $(BASE_OPTS) \ 62 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME)VimAsync.dylib \ 63 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME)Vim.dylib 64 | plugin_lib: renamed_vim_lib renamed_vim_async_lib 65 | 66 | # Build the .so, which Vim dynamically links. 67 | .PHONY: plugin_so 68 | plugin_so: plugin_lib 69 | @clang -g \ 70 | -Xlinker $(PYTHON_LINKED_LIB) \ 71 | -Xlinker $(BUILD_DIR)/lib$(PLUGIN_NAME).dylib \ 72 | -shared -o .build/$(PLUGIN_NAME).so 73 | 74 | # Build for the python dylib vim links 75 | .PHONY: py_vars 76 | py_vars: 77 | @source VimUtils/make_lib.sh; python_info 78 | $(eval PYTHON_LINKED_LIB=$(shell source VimUtils/make_lib.sh; linked_python)) 79 | $(eval PYTHON_INCLUDE=$(shell source VimUtils/make_lib.sh; python_inc_dir)) 80 | 81 | # SPM Build 82 | vim_lib vim_async_lib plugin_lib test_b: py_vars 83 | @mkdir -p .build 84 | @echo "Building.." 85 | swift build -c $(CONFIG) \ 86 | $(BASE_OPTS) $(SWIFT_OPTS) $(EXTRA_OPTS) \ 87 | -Xswiftc -target -Xswiftc $(TRIPPLE) \ 88 | | tee $(LAST_LOG) 89 | -------------------------------------------------------------------------------- /PluginGenerator/Package.tpl.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:4.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "__VIM_PLUGIN_NAME__", 8 | products: [ 9 | .library( 10 | name: "__VIM_PLUGIN_NAME__", 11 | type: .dynamic, 12 | targets: ["__VIM_PLUGIN_NAME__"]), 13 | ], 14 | dependencies: [ 15 | .package(url: "__GIT_REPO__", 16 | .revision("__GIT_REVISION__")) 17 | ], 18 | targets: [ 19 | // Currently, it uses SPM, in a somewhat unconventional way due to 20 | // namespacing: It isn't possible to build Vim plugins with SPM 21 | // naievely. 22 | // 23 | // Consider SPM an implementation detail of the Makefile. See the 24 | // Makefile for more info. 25 | .target( 26 | name: "__VIM_PLUGIN_NAME__", 27 | // The dependencies of the target __VIM_PLUGIN_NAME__ 28 | // are added in the Maekfile. Don't add here. 29 | dependencies: []), 30 | .testTarget( 31 | name: "__VIM_PLUGIN_NAME__Tests", 32 | dependencies: ["__VIM_PLUGIN_NAME__"]), 33 | // We cant depend on "Vim" due to namespacing issues 34 | // and SPM. This makes "Vim" available as a target. 35 | .target(name: "StubVimImport", 36 | dependencies: ["Vim", "VimAsync"]) 37 | ] 38 | ) 39 | -------------------------------------------------------------------------------- /PluginGenerator/PluginMain.tpl.swift: -------------------------------------------------------------------------------- 1 | // VimPlugin Plugin initialization 2 | import __VIM_PLUGIN_NAME__Vim 3 | import __VIM_PLUGIN_NAME__VimAsync 4 | 5 | /// plugin_load 6 | /// Core bootstrap for the plugin. 7 | /// This is called from Vimscript, when the plugin loads. 8 | /// Non 0 return value indicates failure. 9 | @_cdecl("__VIM_PLUGIN_NAME___plugin_load") 10 | func plugin_load(context: UnsafePointer) -> Int { 11 | // Obligatory Hello World 12 | _ = try? Vim.command("echo 'Hello world!'") 13 | 14 | // Set a callable 15 | // Vimscript can call such as: 16 | // call s:SwiftVimEval("Swiftvimexample.invoke('helloSwift')") 17 | VimPlugin.setCallable("helloSwift") { 18 | _ in 19 | _ = try? Vim.command("echo 'Hello Vim!'") 20 | return nil 21 | } 22 | return 0 23 | } 24 | 25 | // Mark - Boilerplate 26 | 27 | /// plugin_runloop_callback 28 | /// This func is called from Vim to wakeup the main runloop 29 | /// It isn't necessary for single threaded plugins 30 | @_cdecl("__VIM_PLUGIN_NAME___plugin_runloop_callback") 31 | func plugin_runloop_callback() { 32 | // Make sure to add VimAsync to the Makefile 33 | // and remove the comment. 34 | VimTaskRunLoopCallback() 35 | } 36 | 37 | /// plugin_runloop_invoke 38 | /// This is called from Vim: 39 | /// __VIM_PLUGIN_NAME__.invoke("Func", 1, 2, 3) 40 | /// The fact that this is here now is a current implementation 41 | /// detail, and will likely go away in the future. 42 | @_cdecl("__VIM_PLUGIN_NAME___plugin_invoke") 43 | func plugin_invoke_callback(_ args: UnsafeMutableRawPointer) -> UnsafePointer? { 44 | return VimPlugin.invokeCallback(args) 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /PluginGenerator/plugin.tpl.vim: -------------------------------------------------------------------------------- 1 | " This is basic vim plugin boilerplate 2 | let s:save_cpo = &cpo 3 | set cpo&vim 4 | 5 | " BEGIN_SWIFTVIM 6 | " COMPILED_FOR_SWIFTVIM_VERSION 0.1 7 | 8 | " The API uses Python internally 9 | function! s:UsingPython3() 10 | if has('python3') 11 | return 1 12 | endif 13 | return 0 14 | endfunction 15 | 16 | " Prefer py3 17 | let s:using_python3 = s:UsingPython3() 18 | let s:python_until_eof = s:using_python3 ? "python3 << EOF" : "python << EOF" 19 | let s:python_command = s:using_python3 ? "py3 " : "py " 20 | 21 | let s:path = fnamemodify(expand(':p:h'), ':h') 22 | 23 | " SwiftVimEval: 24 | " Eval commands against the VimPlugin instance 25 | " 26 | " The Vim API exposes a single method: 27 | " pluginName.event(Int, String) 28 | " 29 | " This corresponds to the Swift protocol 30 | " protocol VimPlugin { 31 | " func event(id: Int, context: String) -> String 32 | " } 33 | " 34 | " ex: 35 | " myvimplugin.event(42, 'MeaningOfLife') 36 | " 37 | function! s:SwiftVimEval( eval_string ) 38 | " Run some python 39 | if s:using_python3 40 | return py3eval( a:eval_string ) 41 | endif 42 | return pyeval( a:eval_string ) 43 | endfunction 44 | 45 | function! s:SwiftVimSetupPlugin() abort 46 | exec s:python_until_eof 47 | import vim 48 | import os 49 | import sys 50 | 51 | # Directory of the plugin 52 | plugin_dir = vim.eval('s:path') 53 | 54 | # Bootstrap Swift Plugin 55 | sys.path.insert(0, os.path.join(plugin_dir, '.build')) 56 | import __VIM_PLUGIN_NAME__ 57 | __VIM_PLUGIN_NAME__.load() 58 | 59 | vim.command('return 1') 60 | EOF 61 | endfunction 62 | 63 | if s:SwiftVimSetupPlugin() != 1 64 | echom "Setting up python failed..." . s:path 65 | endif 66 | 67 | " Internal, VimRunLoop integration 68 | fun s:SwiftVimRunLoopTimer(timer) 69 | call s:SwiftVimEval("__VIM_PLUGIN_NAME__.runloop_callback()") 70 | endf 71 | 72 | let s:SwiftVimRunLoopTimer = timer_start(100, function('s:SwiftVimRunLoopTimer'), {'repeat':-1}) 73 | 74 | " END_SWIFTVIM 75 | 76 | 77 | " Example of calling swift 78 | call s:SwiftVimEval("Swiftvimexample.invoke('helloSwift')") 79 | 80 | " This is basic vim plugin boilerplate 81 | let &cpo = s:save_cpo 82 | unlet s:save_cpo 83 | 84 | -------------------------------------------------------------------------------- /PluginGenerator/plugin_generator.sh: -------------------------------------------------------------------------------- 1 | set +e 2 | 3 | if [[ $plugin_path == "" ]]; then 4 | echo "Usage plugin_path=/path/to/output" 5 | exit 1 6 | fi 7 | 8 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 9 | 10 | REV=$(cd $SCRIPTPATH/../ && echo $(git rev-parse HEAD)) 11 | echo "Hardcoding to revision $REV" 12 | 13 | OUT_DIR=$plugin_path 14 | 15 | PLUGIN=$(basename $OUT_DIR) 16 | echo "Generating $PLUGIN in $OUT_DIR" 17 | 18 | mkdir -p $OUT_DIR 19 | cd $OUT_DIR 20 | 21 | swift package init 22 | 23 | mkdir -p plugin 24 | sed "s,__VIM_PLUGIN_NAME__,$PLUGIN,g" $SCRIPTPATH/plugin.tpl.vim \ 25 | > plugin/$PLUGIN.vim 26 | sed "s,__VIM_PLUGIN_NAME__,$PLUGIN,g" $SCRIPTPATH/PluginMain.tpl.swift \ 27 | > Sources/$PLUGIN/$PLUGIN.swift 28 | 29 | sed "s,__VIM_PLUGIN_NAME__,$PLUGIN,g" $SCRIPTPATH/Package.tpl.swift \ 30 | > Package.swift 31 | 32 | # For testing, we'll set this to PWD 33 | if [[ ! $GIT_REPO ]]; then 34 | GIT_REPO="https://github.com/swift-vim/SwiftForVim.git" 35 | fi 36 | 37 | sed -i "" "s,__GIT_REPO__,$GIT_REPO,g" \ 38 | Package.swift 39 | sed -i "" "s,__GIT_REVISION__,$REV,g" \ 40 | Package.swift 41 | 42 | mkdir -p VimUtils 43 | ditto $SCRIPTPATH/../VimUtils/make_lib.sh VimUtils/ 44 | 45 | sed "s,__VIM_PLUGIN_NAME__,$PLUGIN,g" $SCRIPTPATH/Makefile.tpl \ 46 | > Makefile 47 | 48 | mkdir -p Sources/StubVimImport 49 | touch Sources/StubVimImport/Dummy.swift 50 | 51 | 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftForVim 2 | 3 | Fast, typesafe, Vim plugins with the power of Swift! 4 | 5 | SwiftForVim integrates the Swift Programming Language into Vim. 6 | 7 | ## Vim API 8 | 9 | ### Vim 10 | 11 | Vimscript <-> Swift 12 | 13 | Calling Vim commands from Swift 14 | ```swift 15 | Vim.command("echo 'Hello World!'") 16 | ``` 17 | 18 | Evaluating Vim expressions from Swift 19 | ```swift 20 | let path = String(Vim.eval("expand('%:p')")) 21 | ``` 22 | 23 | Call Swift functions from Vim 24 | 25 | ```swift 26 | VimPlugin.setCallable("cursorMoved") { 27 | _ in 28 | print("The cursor moved") 29 | } 30 | 31 | // Off in VimScript 32 | call s:SwiftVimEval("MyAwesomePlugin.invoke('cursorMoved')") 33 | ``` 34 | 35 | ### VimAsync 36 | 37 | Threading and Async support for Vim 38 | 39 | ```swift 40 | DispatchQueue.async { 41 | // Do some work 42 | VimTask.onMain { 43 | Vim.command("echo 'Hello World! on the main thread.'") 44 | } 45 | } 46 | ``` 47 | 48 | _Note: VimAsync depends on Foundation. Its not needed for basic, single threaded plugins._ 49 | 50 | ## Usage 51 | 52 | First, generate a Vim Plugin setup to use Swift. 53 | ```bash 54 | git clone https://github.com/swift-vim/SwiftForVim.git 55 | cd SwiftForVim 56 | plugin_path=/path/to/MyAwesomePlugin make generate 57 | ``` 58 | 59 | Then, build the plugin. 60 | ```bash 61 | cd /path/to/MyAwesomePlugin 62 | make 63 | ``` 64 | 65 | Last, setup the plugin ( VimPlug, Pathogen, etc ). 66 | 67 | ## Design Goals 68 | 69 | Portable, fast, and simple to use. 70 | 71 | It doesn't require recompiling Vim or a custom fork of the Swift language. 72 | 73 | ## Why? 74 | 75 | Swift makes it easy to build fast, type safe programs, that are easy to debug 76 | and deploy. 77 | 78 | ## Examples 79 | 80 | The source tree contains a very basic example and test case. 81 | 82 | [SwiftPackageManger.vim](https://github.com/swift-vim/SwiftPackageManager.vim) is the canonical use case and uses [VimAsync](#VimAsync) to run a custom RPC service inside of vim. 83 | 84 | -------------------------------------------------------------------------------- /Sources/Example/Example.swift: -------------------------------------------------------------------------------- 1 | // VimPlugin Plugin initialization 2 | import ExampleVim 3 | import ExampleVimAsync 4 | 5 | /// plugin_load 6 | /// Core bootstrap for the plugin. 7 | /// This is called from Vimscript, when the plugin loads. 8 | /// Non 0 return value indicates failure. 9 | @_cdecl("Example_plugin_load") 10 | func plugin_load(context: UnsafePointer) -> Int { 11 | // Obligatory Hello World 12 | _ = try? Vim.command("echo 'Hello world!'") 13 | 14 | // Set a callable 15 | // Vimscript can call such as: 16 | // call s:SwiftVimEval("Swiftvimexample.invoke('helloSwift')") 17 | VimPlugin.setCallable("helloSwift") { 18 | _ in 19 | _ = try? Vim.command("echo 'Hello Vim!'") 20 | return nil 21 | } 22 | return 0 23 | } 24 | 25 | // Mark - Boilerplate 26 | 27 | /// plugin_runloop_callback 28 | /// This func is called from Vim to wakeup the main runloop 29 | /// It isn't necessary for single threaded plugins 30 | @_cdecl("Example_plugin_runloop_callback") 31 | func plugin_runloop_callback() { 32 | // Make sure to add VimAsync to the Makefile 33 | // and remove the comment. 34 | VimTaskRunLoopCallback() 35 | } 36 | 37 | /// plugin_runloop_invoke 38 | /// This is called from Vim: 39 | /// Example.invoke("Func", 1, 2, 3) 40 | /// The fact that this is here now is a current implementation 41 | /// detail, and will likely go away in the future. 42 | @_cdecl("Example_plugin_invoke") 43 | func plugin_invoke_callback(_ args: UnsafeMutableRawPointer) -> UnsafePointer? { 44 | return VimPlugin.invokeCallback(args) 45 | } 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /Sources/Vim/Vim.swift: -------------------------------------------------------------------------------- 1 | import VimInterface 2 | 3 | enum VimError: Error { 4 | case invalidCall(String) 5 | } 6 | 7 | public struct Vim { 8 | public static var current: Current { 9 | return Current() 10 | } 11 | 12 | /// Run a Vim command. 13 | /// :help command 14 | @discardableResult public static func command(_ cmd: String) throws -> VimValue { 15 | var value: VimValue? 16 | cmd.withCString { cStr in 17 | if let result = swiftvim_command( 18 | UnsafeMutablePointer(mutating: cStr)) { 19 | value = VimValue(result) 20 | } 21 | } 22 | if let value = value { 23 | return value 24 | } 25 | throw getCallError(context: cmd) 26 | } 27 | 28 | /// Evaluate an expression 29 | /// :help eval 30 | @discardableResult public static func eval(_ cmd: String) throws -> VimValue { 31 | var value: VimValue? 32 | cmd.withCString { cStr in 33 | if let result = swiftvim_eval( 34 | UnsafeMutablePointer(mutating: cStr)) { 35 | value = VimValue(result) 36 | } 37 | } 38 | if let value = value { 39 | return value 40 | } 41 | throw getCallError(context: cmd) 42 | } 43 | 44 | private static func getCallError(context: String) -> VimError { 45 | if let error = swiftvim_get_error() { 46 | if let base = swiftvim_asstring(error) { 47 | return VimError.invalidCall(String(cString: base) + " - " + context) 48 | } 49 | } 50 | return VimError.invalidCall(context) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /Sources/Vim/VimBuffer.swift: -------------------------------------------------------------------------------- 1 | import VimInterface 2 | 3 | public class VimBuffer { 4 | private let value: VimValue 5 | 6 | init(_ value: VimValue) { 7 | self.value = value 8 | } 9 | 10 | public lazy var number: Int = { 11 | return self.value.reference.attr("number") 12 | }() 13 | 14 | public lazy var name: String = { 15 | return self.value.reference.attr("name") 16 | }() 17 | 18 | public func asList() -> VimList { 19 | return VimList(self.value)! 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/Vim/VimCurrent.swift: -------------------------------------------------------------------------------- 1 | import VimInterface 2 | 3 | public class Current { 4 | private let value: UnsafeVimValue 5 | 6 | public lazy var buffer: VimBuffer = { 7 | return VimBuffer(VimValue(self.value.attrp("buffer")!)) 8 | }() 9 | 10 | public lazy var window: VimWindow = { 11 | return VimWindow(VimValue(self.value.attrp("window")!)) 12 | }() 13 | 14 | init() { 15 | let module = "vim".withCString { moduleCStr in 16 | return swiftvim_get_module(moduleCStr) 17 | } 18 | guard let value = module?.attrp("current") else { 19 | fatalError("missing current") 20 | } 21 | self.value = value 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Vim/VimExtensions.swift: -------------------------------------------------------------------------------- 1 | #if os(Linux) 2 | import Glibc 3 | let sysRealpath = Glibc.realpath 4 | #else 5 | import Darwin.C 6 | let sysRealpath = Darwin.realpath 7 | #endif 8 | 9 | /// Convert to a vimscript string 10 | public protocol VimScriptConvertible { 11 | func toVimScript() -> String 12 | } 13 | 14 | extension Int: VimScriptConvertible{ 15 | public func toVimScript() -> String { 16 | return String(self) 17 | } 18 | } 19 | 20 | extension String: VimScriptConvertible { 21 | public func toVimScript() -> String { 22 | return self 23 | } 24 | } 25 | 26 | extension Vim { 27 | public static func escapeForVim(_ value: String) -> String { 28 | return value.reduce(into: "", { acc, x in 29 | if x == "'" { acc += "''" } 30 | else { acc += String(x) } 31 | }) 32 | } 33 | 34 | /// Mark - Eval Helpers 35 | public static func exists(variable: String) -> Bool { 36 | return get("exists('\(escapeForVim(variable))'") 37 | } 38 | 39 | public static func set(variable: String, value: VimScriptConvertible) { 40 | _ = try? command("let \(variable) = \(value.toVimScript())") 41 | } 42 | 43 | public static func get(variable: String) -> VimValue? { 44 | return try? eval(variable) 45 | } 46 | 47 | public static func get(_ variable: String) -> VimValue? { 48 | return try? eval(variable) 49 | } 50 | 51 | public static func get(_ variable: String) -> Bool { 52 | return Bool(try? eval(variable)) ?? false 53 | } 54 | 55 | public static func get(_ variable: String) -> Int { 56 | return Int(try? eval(variable)) ?? 0 57 | } 58 | 59 | public static func get(_ variable: String) -> String { 60 | return String(try? eval(variable)) ?? "" 61 | } 62 | 63 | /// Returns the 0-based current line and 0-based current column 64 | public static func currentLineAndColumn() -> (Int, Int) { 65 | return current.window.cursor 66 | } 67 | 68 | public static func realpath(_ path: String) -> String? { 69 | guard let p = sysRealpath(path, nil) else { return nil } 70 | defer { free(p) } 71 | return String(validatingUTF8: p) 72 | } 73 | 74 | // MARK - Buffers 75 | 76 | public static func getBufferNumber(for filename: String, openFileIfNeeded: Bool=false) -> Int { 77 | guard let rpath = realpath(filename) else { return -1 } 78 | let path = escapeForVim(rpath) 79 | let create = openFileIfNeeded == true ? "1" : "0" 80 | return get("bufnr('\(path)', \(create))") 81 | } 82 | 83 | public static func bufferIsVisible(bufferNumber: Int) -> Bool { 84 | guard bufferNumber > 0 else { 85 | return false 86 | } 87 | let windowNumber: Int = get("bufwinnr(\(bufferNumber))") 88 | return windowNumber != -1 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /Sources/Vim/VimPlugin.swift: -------------------------------------------------------------------------------- 1 | import VimInterface 2 | 3 | /// VimCallable: 4 | /// VarArgs style calling from Vim 5 | /// May return nil if needed 6 | public typealias VimCallable = (([VimValue]) -> VimScriptConvertible?) 7 | 8 | /// This is not thread safe and has no reason to be. 9 | private var CallablePluginMethods: [String: VimCallable] = [:] 10 | 11 | /// VimPlugin represents the namespaced module which VimScript interacts with. 12 | /// All calls run on Vim's main thread. 13 | public struct VimPlugin { 14 | 15 | /// Set a callable 16 | /// This may be called from Vim: 17 | /// Example.invoke("Func", 1, 2, 3) 18 | /// 19 | /// @recommendation these functions should return fast. Don't block the event 20 | /// loop, and use `VimAsync` for slow tasks 21 | public static func setCallable(_ name: String, 22 | callable: @escaping VimCallable) { 23 | CallablePluginMethods[name] = callable 24 | } 25 | 26 | /// Here the callback is actually invoked 27 | public static func invokeCallback(_ args: UnsafeMutableRawPointer) -> UnsafePointer? { 28 | guard let ref = swiftvim_tuple_get(args, 0), 29 | let name = String(VimValue(reference: ref)) else { 30 | return nil 31 | } 32 | return call(name: name, args: args) 33 | } 34 | 35 | static func call(name: String, args: UnsafeMutableRawPointer) -> UnsafePointer? { 36 | guard let callable = CallablePluginMethods[name] else { 37 | fatalError("error: tried to call unregistered plugin func: " + name) 38 | } 39 | let varArgs: [VimValue] = (1.. VimValue in 42 | guard let ref = swiftvim_tuple_get(args, i) else { 43 | fatalError("error: incoherent call usage") 44 | } 45 | let value = VimValue(borrowedReference: ref, own: true) 46 | return value 47 | } 48 | 49 | /// FIXME: should this return `None` for Nil? 50 | let ret = callable(varArgs)?.toVimScript() ?? "" 51 | return UnsafePointer(ret) 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Sources/Vim/VimValue.swift: -------------------------------------------------------------------------------- 1 | import VimInterface 2 | 3 | // Converting values from Vim. 4 | // VimValue is a `View` into the state of Vim. 5 | // Design: 6 | // - Be fast 7 | // - Be as idomatic as possible 8 | // 9 | // In the standard library, types do not dynamically cast to Any. 10 | // 11 | 12 | extension Int { 13 | public init?(_ value: VimValue) { 14 | // Generally, eval results are returned as strings 15 | // Perhaps there is a better way to express this. 16 | if let strValue = String(value), 17 | let intValue = Int(strValue) { 18 | self.init(intValue) 19 | } else { 20 | self.init(swiftvim_asnum(value.reference)) 21 | } 22 | } 23 | 24 | public init?(_ value: VimValue?) { 25 | guard let value = value else { 26 | return nil 27 | } 28 | self.init(value) 29 | } 30 | } 31 | 32 | extension String { 33 | public init?(_ value: VimValue) { 34 | guard let cStr = swiftvim_asstring(value.reference) else { 35 | return nil 36 | } 37 | self.init(cString: cStr) 38 | } 39 | 40 | public init?(_ value: VimValue?) { 41 | guard let value = value else { 42 | return nil 43 | } 44 | self.init(value) 45 | } 46 | } 47 | 48 | extension Bool { 49 | init?(_ value: VimValue) { 50 | self.init((Int(value) ?? 0) != 0) 51 | } 52 | 53 | init?(_ value: VimValue?) { 54 | guard let value = value else { 55 | return nil 56 | } 57 | self.init(value) 58 | } 59 | } 60 | 61 | /// Vim Value represents a value in Vim 62 | /// 63 | /// This value is generally created from vimscript function calls. It provides 64 | /// a "readonly" view of Vim's state. 65 | public final class VimValue { 66 | let reference: UnsafeVimValue 67 | private let doDeInit: Bool 68 | 69 | init(_ value: UnsafeVimValue, doDeInit: Bool = false) { 70 | self.reference = value 71 | self.doDeInit = doDeInit 72 | } 73 | 74 | /// Borrowed reference 75 | init(borrowedReference: UnsafeVimValue, own: Bool = false) { 76 | self.reference = borrowedReference 77 | self.doDeInit = own 78 | if own { 79 | swiftvim_incref(borrowedReference) 80 | } 81 | } 82 | 83 | /// New reference 84 | public init(reference: UnsafeVimValue) { 85 | // FIXME: Audit spmvim_lib.c for cases of this 86 | self.reference = reference 87 | self.doDeInit = true 88 | } 89 | 90 | deinit { 91 | /// Correctly decrement when this value is done. 92 | if doDeInit { 93 | swiftvim_decref(reference) 94 | } 95 | } 96 | } 97 | 98 | 99 | // A Dictionary 100 | public final class VimDictionary { 101 | private let value: VimValue 102 | 103 | public init?(_ value: VimValue) { 104 | self.value = value 105 | } 106 | 107 | public var count: Int { 108 | return Int(swiftvim_dict_size(value.reference)) 109 | } 110 | 111 | public var keys: VimList { 112 | guard let list = VimList(VimValue(reference: swiftvim_dict_keys(value.reference))) else { 113 | fatalError("Can't get keys") 114 | } 115 | return list 116 | } 117 | 118 | public var values: VimList { 119 | guard let list = VimList(VimValue(reference: swiftvim_dict_values(value.reference))) else { 120 | fatalError("Can't get values") 121 | } 122 | return list 123 | } 124 | 125 | public subscript(index: VimValue) -> VimValue? { 126 | get { 127 | guard let v = swiftvim_dict_get(value.reference, index.reference) else { 128 | return nil 129 | } 130 | return VimValue(v) 131 | } 132 | set { 133 | swiftvim_dict_set(value.reference, index.reference, newValue?.reference) 134 | } 135 | } 136 | 137 | public subscript(index: String) -> VimValue? { 138 | get { 139 | return index.withCString { cStrIdx in 140 | guard let v = swiftvim_dict_getstr(value.reference, cStrIdx) else { 141 | return nil 142 | } 143 | return VimValue(v) 144 | } 145 | } 146 | set { 147 | index.withCString { cStrIdx in 148 | swiftvim_dict_setstr(value.reference, cStrIdx, newValue?.reference) 149 | } 150 | } 151 | } 152 | } 153 | 154 | 155 | /// A List of VimValues 156 | public final class VimList: Collection { 157 | private let value: VimValue 158 | 159 | public init?(_ value: VimValue) { 160 | self.value = value 161 | } 162 | 163 | public var startIndex: Int { 164 | return 0 165 | } 166 | 167 | public var endIndex: Int { 168 | return Int(swiftvim_list_size(value.reference)) 169 | } 170 | 171 | public var isEmpty: Bool { 172 | return swiftvim_list_size(value.reference) == 0 173 | } 174 | 175 | public var count: Int { 176 | return Int(swiftvim_list_size(value.reference)) 177 | } 178 | 179 | public subscript(index: Int) -> VimValue { 180 | get { 181 | return VimValue(swiftvim_list_get(value.reference, Int32(index))) 182 | } 183 | set { 184 | swiftvim_list_set(value.reference, Int32(index), newValue.reference) 185 | } 186 | } 187 | 188 | public func index(after i: Int) -> Int { 189 | precondition(i < endIndex, "Can't advance beyond endIndex") 190 | return i + 1 191 | } 192 | } 193 | 194 | // MARK - Internal 195 | 196 | /// This is a helper for internal usage 197 | public typealias UnsafeVimValue = UnsafeMutableRawPointer 198 | 199 | extension UnsafeVimValue { 200 | func attrp(_ key: String) -> UnsafeVimValue? { 201 | let value = key.withCString { fCStr in 202 | return swiftvim_get_attr( 203 | self, 204 | UnsafeMutablePointer(mutating: fCStr)) 205 | } 206 | return value 207 | } 208 | 209 | func attr(_ key: String) -> String { 210 | let value = key.withCString { fCStr in 211 | return swiftvim_get_attr( 212 | self, 213 | UnsafeMutablePointer(mutating: fCStr)) 214 | } 215 | return String(cString: swiftvim_asstring(value)!) 216 | } 217 | 218 | func attr(_ key: String) -> Int { 219 | let value = key.withCString { fCStr in 220 | return swiftvim_get_attr( 221 | self, 222 | UnsafeMutablePointer(mutating: fCStr)) 223 | } 224 | return Int(swiftvim_asnum(value)) 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /Sources/Vim/VimWindow.swift: -------------------------------------------------------------------------------- 1 | import VimInterface 2 | 3 | public class VimWindow { 4 | private let value: VimValue 5 | 6 | init(_ value: VimValue) { 7 | self.value = value 8 | } 9 | 10 | public var cursor: (Int, Int) { 11 | guard let cursor = self.value.reference.attrp("cursor") else { 12 | return (0, 0) 13 | } 14 | let first = swiftvim_tuple_get(cursor, 0) 15 | let second = swiftvim_tuple_get(cursor, 1) 16 | return (Int(swiftvim_asnum(first)), Int(swiftvim_asnum(second))) 17 | } 18 | 19 | public var height: Int { 20 | return value.reference.attr("height") 21 | } 22 | 23 | public var col: Int { 24 | return value.reference.attr("col") 25 | } 26 | 27 | public var row: Int { 28 | return value.reference.attr("row") 29 | } 30 | 31 | public var valid: Bool { 32 | return value.reference.attr("valid") != 0 33 | } 34 | 35 | public var buffer: VimBuffer { 36 | return VimBuffer(VimValue(reference: value.reference.attrp("buffer")!)) 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Sources/VimAsync/VimTask.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | private var rlLock = os_unfair_lock_s() 4 | 5 | fileprivate final class VimRunLoop { 6 | private let source: CFRunLoopSource 7 | private let runLoopRef: CFRunLoop 8 | private let runLoop: RunLoop 9 | 10 | private init() { 11 | let runLoop = RunLoop.current 12 | let runLoopRef = RunLoop.current.getCFRunLoop() 13 | var ctx = CFRunLoopSourceContext() 14 | let source = CFRunLoopSourceCreate(nil, 0, UnsafeMutablePointer(&ctx))! 15 | CFRunLoopAddSource(runLoopRef, source, CFRunLoopMode.commonModes) 16 | self.runLoopRef = runLoopRef 17 | self.runLoop = runLoop 18 | self.source = source 19 | CFRunLoopSourceSignal(source); 20 | CFRunLoopWakeUp(runLoopRef); 21 | } 22 | 23 | public func runOnce() { 24 | os_unfair_lock_lock(&rlLock) 25 | CFRunLoopSourceSignal(source); 26 | CFRunLoopWakeUp(runLoopRef); 27 | CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0, true) 28 | os_unfair_lock_unlock(&rlLock) 29 | } 30 | 31 | /// Schedule the block to run 32 | public func perform(_ bl: @escaping (() -> Void)) { 33 | os_unfair_lock_lock(&rlLock) 34 | runLoop.perform(bl) 35 | CFRunLoopRunInMode(CFRunLoopMode.defaultMode, 0, true) 36 | os_unfair_lock_unlock(&rlLock) 37 | } 38 | 39 | public static var main: VimRunLoop = { 40 | var rl: VimRunLoop! 41 | os_unfair_lock_lock(&rlLock) 42 | rl = VimRunLoop() 43 | os_unfair_lock_unlock(&rlLock) 44 | return rl 45 | }() 46 | } 47 | 48 | /// Create tasks for new threads or use existing ones 49 | /// The program should be as synchronous as possible 50 | public final class VimTask : NSObject { 51 | public typealias VimTaskBlock = () -> T 52 | 53 | public init(main: Bool? = false, bl: @escaping VimTaskBlock) { 54 | self.bl = bl 55 | self.main = main ?? false 56 | super.init() 57 | } 58 | 59 | /// Schedule on Vim's main thread in a thread safe way. 60 | /// Vim is a "single threaded", thread unsafe program, so any code that touches 61 | /// vim must run on the main thread. 62 | /// 63 | /// This includes any function calls and associated data. 64 | /// Beware, that not adhereing to this will case several issues and thread 65 | /// safety is generally not validated. 66 | public static func onMain(_ bl: @escaping VimTaskBlock) { 67 | if Thread.current == Thread.main { 68 | _ = bl() 69 | return 70 | } 71 | VimTask(main: true, bl: bl).run() 72 | } 73 | 74 | public var isDone: Bool { 75 | var x = false 76 | mutQueue.sync { 77 | x = self.done 78 | } 79 | return x 80 | } 81 | 82 | public func run() { 83 | if main { 84 | VimRunLoop.main.perform { 85 | () -> Void in 86 | self.start(sender: nil) 87 | } 88 | } else { 89 | Thread.detachNewThreadSelector(#selector(start), toTarget:self, with: nil) 90 | } 91 | } 92 | 93 | private let bl: VimTaskBlock 94 | private let main: Bool 95 | private let mutQueue = DispatchQueue(label: "com.bs.threadMut") 96 | private var done = false 97 | private var running = false 98 | 99 | @objc 100 | private func start(sender: Any?) { 101 | mutQueue.sync { 102 | self.running = true 103 | } 104 | let _ = bl() 105 | mutQueue.sync { 106 | self.done = true 107 | self.running = false 108 | } 109 | } 110 | 111 | } 112 | 113 | /// Callback for the main run loop 114 | public func VimTaskRunLoopCallback() { 115 | VimRunLoop.main.runOnce() 116 | } 117 | 118 | /// Check in code if thread is on the main 119 | public func VimTaskMainThreadGuard() { 120 | guard Thread.current == Thread.main else { 121 | fatalError("error: main thread check failed") 122 | } 123 | } 124 | 125 | 126 | -------------------------------------------------------------------------------- /Sources/VimAsync/VimTimer.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | // Dispatch Timer for Vim 4 | public final class VimTimer { 5 | let timeInterval: TimeInterval 6 | private var eventHandler: (() -> Void)? 7 | 8 | private lazy var timer: DispatchSourceTimer = { 9 | let t = DispatchSource.makeTimerSource() 10 | t.schedule(deadline:.now() + self.timeInterval, 11 | repeating: self.timeInterval) 12 | t.setEventHandler(handler: { [weak self] in 13 | self?.eventHandler?() 14 | }) 15 | return t 16 | }() 17 | 18 | private enum State { 19 | case suspended 20 | case resumed 21 | } 22 | 23 | private var state: State = .suspended 24 | 25 | public init(timeInterval: TimeInterval, eventHandler: @escaping (() -> Void)) { 26 | self.timeInterval = timeInterval 27 | self.eventHandler = eventHandler 28 | } 29 | 30 | public func resume() { 31 | if state == .resumed { 32 | return 33 | } 34 | state = .resumed 35 | timer.resume() 36 | } 37 | 38 | public func suspend() { 39 | if state == .suspended { 40 | return 41 | } 42 | state = .suspended 43 | timer.suspend() 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Sources/VimInterface/include/VimInterface/module.modulemap: -------------------------------------------------------------------------------- 1 | module VimInterface { 2 | export * 3 | } 4 | -------------------------------------------------------------------------------- /Sources/VimInterface/include/VimInterface/swiftvim.h: -------------------------------------------------------------------------------- 1 | // Main vimscript methods 2 | 3 | void *_Nullable swiftvim_command(const char *_Nonnull command); 4 | void *_Nullable swiftvim_eval(const char *_Nonnull eval); 5 | 6 | // Internally, the API uses reference counting 7 | void *_Nullable swiftvim_decref(void *_Nullable value); 8 | void *_Nullable swiftvim_incref(void *_Nullable value); 9 | 10 | // Value extraction 11 | const char *_Nullable swiftvim_asstring(void *_Nullable value); 12 | long swiftvim_asnum(void *_Nullable value); 13 | 14 | // List 15 | int swiftvim_list_size(void *_Nonnull list); 16 | void swiftvim_list_set(void *_Nonnull list, int i, void *_Nullable value); 17 | 18 | void *_Nonnull swiftvim_list_get(void *_Nonnull list, int i); 19 | void swiftvim_list_append(void *_Nonnull list, void *_Nullable value); 20 | 21 | 22 | // Dict 23 | void *_Nullable swiftvim_dict_get(void *_Nonnull dict, void *_Nullable key); 24 | void swiftvim_dict_set(void *_Nonnull dict, void *_Nonnull key, void *_Nullable value); 25 | 26 | void *_Nullable swiftvim_dict_getstr(void *_Nonnull dict, const char *_Nonnull key); 27 | void swiftvim_dict_setstr(void *_Nonnull dict, const char *_Nonnull key, void *_Nullable value); 28 | 29 | void *_Nonnull swiftvim_dict_values(void *_Nonnull dict); 30 | void *_Nonnull swiftvim_dict_values(void *_Nonnull dict); 31 | void *_Nonnull swiftvim_dict_keys(void *_Nonnull dict); 32 | int swiftvim_dict_size(void *_Nonnull dict); 33 | 34 | // Tuples 35 | void *_Nullable swiftvim_tuple_get(void *_Nonnull tuple, int idx); 36 | int swiftvim_tuple_size(void *_Nonnull tuple); 37 | 38 | 39 | void *_Nullable swiftvim_call(const char *_Nonnull module, const char *_Nonnull method, const char *_Nullable str); 40 | 41 | void *_Nullable swiftvim_get_module(const char *_Nonnull module); 42 | void *_Nullable swiftvim_get_attr(void *_Nonnull target, const char *_Nonnull attr); 43 | 44 | void *_Nullable swiftvim_call_impl(void *_Nonnull func, void *_Nullable arg1, void *_Nullable arg2); 45 | 46 | // Bootstrapping 47 | // Note: These methods are only for testing purposes 48 | void swiftvim_initialize(); 49 | void swiftvim_finalize(); 50 | 51 | void *_Nullable swiftvim_get_error(); 52 | 53 | -------------------------------------------------------------------------------- /Sources/VimInterface/swiftvim_lib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // Do not export any symbols - we don't want collisions 7 | #define VIM_INTERN __attribute__ ((visibility("hidden"))) 8 | 9 | static FILE *_plugin_error_f = NULL; 10 | 11 | static FILE *plugin_error_f() { 12 | if (_plugin_error_f != NULL) { 13 | return _plugin_error_f; 14 | } 15 | 16 | static char template[] = "/private/tmp/swiftvim_stderr.logXXXXXX"; 17 | char fname[PATH_MAX]; 18 | char buf[BUFSIZ]; 19 | strcpy(fname, template); 20 | mkstemp(fname); 21 | 22 | printf("logged errors to %s\n", fname); 23 | _plugin_error_f = fopen(fname, "w+"); 24 | return _plugin_error_f; 25 | } 26 | 27 | /// Bridge PyString_AsString to both runtimes 28 | static const char *SPyString_AsString(PyObject *input) { 29 | #if PY_MAJOR_VERSION == 3 30 | return PyUnicode_AsUTF8(input); 31 | #else 32 | return PyString_AsString(input); 33 | #endif 34 | } 35 | 36 | /// Bridge PyString_FromString to both runtimes 37 | static PyObject *SPyString_FromString(const char *input) { 38 | #if PY_MAJOR_VERSION == 3 39 | return PyUnicode_FromString(input); 40 | #else 41 | return PyString_FromString(input); 42 | #endif 43 | } 44 | void *swiftvim_call_impl(void *func, void *arg1, void *arg2); 45 | 46 | // module=vim, method=command|exec, str = value 47 | VIM_INTERN void *swiftvim_call(const char *module, const char *method, const char *textArg) { 48 | PyObject *pName = SPyString_FromString(module); 49 | PyObject *pModule = PyImport_Import(pName); 50 | Py_DECREF(pName); 51 | if (pModule == NULL) { 52 | PyErr_Print(); 53 | fprintf(plugin_error_f(), "swiftvim error: failed to load \"%s\"\n", module); 54 | return NULL; 55 | } 56 | 57 | PyObject *arg = SPyString_FromString(textArg); 58 | if (!arg) { 59 | fprintf(plugin_error_f(), "swiftvim error: Cannot convert argument\n"); 60 | return NULL; 61 | } 62 | PyObject *pFunc = PyObject_GetAttrString(pModule, method); 63 | void *v = swiftvim_call_impl(pFunc, arg, NULL); 64 | Py_DECREF(pModule); 65 | Py_XDECREF(pFunc); 66 | return v; 67 | } 68 | 69 | VIM_INTERN void *swiftvim_get_module(const char *module) { 70 | PyObject *pName = SPyString_FromString(module); 71 | PyObject *pModule = PyImport_Import(pName); 72 | Py_DECREF(pName); 73 | if (pModule == NULL) { 74 | PyErr_Print(); 75 | fprintf(plugin_error_f(), "swiftvim error: failed to load \"%s\"\n", module); 76 | return NULL; 77 | } 78 | return pModule; 79 | } 80 | 81 | VIM_INTERN void *swiftvim_get_attr(void *target, const char *method) { 82 | void *v = PyObject_GetAttrString(target, method); 83 | return v; 84 | } 85 | 86 | static void print_basic_error_desc() { 87 | // This goes to stderr, which vim can parse 88 | PyErr_Print(); 89 | 90 | fprintf(plugin_error_f(), "\n=== startCallStack == \n"); 91 | void *callstack[128]; 92 | int frames = backtrace(callstack, 128); 93 | char **strs = backtrace_symbols(callstack, frames); 94 | for (int i = 0; i < frames; ++i) { 95 | fprintf(plugin_error_f(), "%s\n", strs[i]); 96 | } 97 | free(strs); 98 | fprintf(plugin_error_f(), "\n=== endCallStack == \n"); 99 | } 100 | 101 | VIM_INTERN void *swiftvim_call_impl(void *pFunc, void *arg1, void *arg2) { 102 | void *outValue = NULL; 103 | // pFunc is a new reference 104 | if (pFunc && PyCallable_Check(pFunc)) { 105 | int argCt = 0; 106 | if (arg1) { 107 | argCt++; 108 | } 109 | if (arg2) { 110 | argCt++; 111 | } 112 | 113 | PyObject *pArgs = PyTuple_New(argCt); 114 | /// Add args if needed 115 | if (arg1) { 116 | PyTuple_SetItem(pArgs, 0, arg1); 117 | } 118 | if (arg2) { 119 | PyTuple_SetItem(pArgs, 1, arg2); 120 | } 121 | PyObject *pValue = PyObject_CallObject(pFunc, pArgs); 122 | if (pValue != NULL) { 123 | outValue = pValue; 124 | } else { 125 | print_basic_error_desc(); 126 | PyObject *funcObj = PyObject_Repr(pFunc); 127 | PyObject *arg1Obj = PyObject_Repr(arg1); 128 | PyObject *arg2Obj = PyObject_Repr(arg2); 129 | fprintf(plugin_error_f(), 130 | "swiftvim error: call failed %s %s %s \n", 131 | SPyString_AsString(funcObj), 132 | SPyString_AsString(arg1Obj), 133 | SPyString_AsString(arg2Obj)); 134 | } 135 | Py_DECREF(pArgs); 136 | } else { 137 | print_basic_error_desc(); 138 | PyObject *funcObj = PyObject_Repr(pFunc); 139 | fprintf(plugin_error_f(), "swiftvim error: cannot find function \"(%s)\"\n", SPyString_AsString(funcObj)); 140 | } 141 | 142 | return outValue; 143 | } 144 | 145 | VIM_INTERN void *swiftvim_command(const char *command) { 146 | return swiftvim_call("vim", "command", command); 147 | } 148 | 149 | VIM_INTERN void *swiftvim_eval(const char *eval) { 150 | return swiftvim_call("vim", "eval", eval); 151 | } 152 | 153 | // TODO: Do these need GIL locks? 154 | VIM_INTERN void *swiftvim_decref(void *value) { 155 | if (value == NULL) { 156 | return NULL; 157 | } 158 | 159 | Py_DECREF(value); 160 | return NULL; 161 | } 162 | 163 | VIM_INTERN void *swiftvim_incref(void *value) { 164 | if (value == NULL) { 165 | return NULL; 166 | } 167 | 168 | Py_INCREF(value); 169 | return NULL; 170 | } 171 | 172 | VIM_INTERN const char *swiftvim_asstring(void *value) { 173 | if (value == NULL) { 174 | return ""; 175 | } 176 | const char *v = SPyString_AsString(value); 177 | return v; 178 | } 179 | 180 | VIM_INTERN long swiftvim_asnum(void *value) { 181 | long v = PyLong_AsLong(value); 182 | return v; 183 | } 184 | 185 | VIM_INTERN int swiftvim_list_size(void *list) { 186 | int v = PySequence_Size(list); 187 | return v; 188 | } 189 | 190 | VIM_INTERN void swiftvim_list_set(void *list, size_t i, void *value) { 191 | PySequence_SetItem(list, i, value); 192 | } 193 | 194 | VIM_INTERN void *swiftvim_list_get(void *list, size_t i) { 195 | /// Return a borrowed reference 196 | void *v = PySequence_GetItem(list, i); 197 | return v; 198 | } 199 | 200 | VIM_INTERN void swiftvim_list_append(void *list, void *value) { 201 | PyList_Append(list, value); 202 | } 203 | 204 | // MARK - Dict 205 | 206 | VIM_INTERN int swiftvim_dict_size(void *dict) { 207 | int v = PyDict_Size(dict); 208 | return v; 209 | } 210 | 211 | VIM_INTERN void *swiftvim_dict_keys(void *dict) { 212 | // Return value: New reference 213 | void *v = PyDict_Keys(dict); 214 | return v; 215 | } 216 | 217 | VIM_INTERN void *swiftvim_dict_values(void *dict) { 218 | // Return value: New reference 219 | void *v = PyDict_Items(dict); 220 | return v; 221 | } 222 | 223 | VIM_INTERN void swiftvim_dict_set(void *dict, void *key, void *value) { 224 | PyDict_SetItem(dict, key, value); 225 | } 226 | 227 | VIM_INTERN void *swiftvim_dict_get(void *dict, void *key) { 228 | /// Return a borrowed reference 229 | void *v = PyDict_GetItem(dict, key); 230 | return v; 231 | } 232 | 233 | VIM_INTERN void swiftvim_dict_setstr(void *dict, const char *key, void *value) { 234 | PyDict_SetItemString(dict, key, value); 235 | } 236 | 237 | VIM_INTERN void *swiftvim_dict_getstr(void *dict, const char *key) { 238 | /// Return a borrowed reference 239 | void *v = PyDict_GetItemString(dict, key); 240 | return v; 241 | } 242 | 243 | // MARK - Tuples 244 | 245 | VIM_INTERN void *_Nonnull swiftvim_tuple_get(void *_Nonnull tuple, int idx) { 246 | /// Return a borrowed reference 247 | void *v = PyTuple_GetItem(tuple, idx); 248 | swiftvim_incref(v); 249 | return v; 250 | } 251 | 252 | int swiftvim_tuple_size(void *_Nonnull tuple) { 253 | PyGILState_STATE gstate = PyGILState_Ensure(); 254 | /// Return a borrowed reference 255 | int v = (int)PyTuple_Size(tuple); 256 | PyGILState_Release(gstate); 257 | return v; 258 | } 259 | 260 | VIM_INTERN void swiftvim_initialize() { 261 | Py_Initialize(); 262 | if(!PyEval_ThreadsInitialized()) { 263 | PyEval_InitThreads(); 264 | } 265 | 266 | // FIXME: Move this to the Makefile or something 267 | #ifdef SPMVIM_LOADSTUB_RUNTIME 268 | // For unit tests, we fake out the vim module 269 | // to make the tests as pure as possible. 270 | // Assume that tests are running from the source root 271 | // We could do something better. 272 | char cwd[1024]; 273 | if (getcwd(cwd, sizeof(cwd)) == NULL) { 274 | fprintf(stderr, "can't load testing directory"); 275 | exit(1); 276 | } 277 | strcat(cwd, "/Tests/VimInterfaceTests/MockVimRuntime/"); 278 | fprintf(stderr, "Adding test import path: %s \n", cwd); 279 | PyObject* sysPath = PySys_GetObject((char*)"path"); 280 | PyObject* programName = SPyString_FromString(cwd); 281 | PyList_Append(sysPath, programName); 282 | Py_DECREF(programName); 283 | #endif 284 | } 285 | 286 | VIM_INTERN void swiftvim_finalize() { 287 | Py_Finalize(); 288 | } 289 | 290 | VIM_INTERN void *_Nullable swiftvim_get_error() { 291 | if (PyErr_Occurred()) { 292 | PyObject *type, *value, *traceback; 293 | PyErr_Fetch(&type, &value, &traceback); 294 | if (value) { 295 | return value; 296 | } else { 297 | return type; 298 | } 299 | } 300 | return NULL; 301 | } 302 | 303 | -------------------------------------------------------------------------------- /Sources/VimPluginBootstrap/include/VimPluginBootstrap/module.modulemap: -------------------------------------------------------------------------------- 1 | module VimPluginBootstrap { 2 | export * 3 | } 4 | -------------------------------------------------------------------------------- /Sources/VimPluginBootstrap/swiftvim.c: -------------------------------------------------------------------------------- 1 | // Core Python -> Swift bootstrap 2 | // The vim plugin is expected to call swiftvim_load when 3 | // it's time to initialize the plugin 4 | #include 5 | #include 6 | 7 | // Namespace the plugin names 8 | #define _MAKE_FN_NAME_P_2(y, x) y ## x 9 | #define _MAKE_FN_NAME_P(y, x) _MAKE_FN_NAME_P_2(y, x) 10 | #define PLUGIN_FUNC(s, p) _MAKE_FN_NAME_P(s, p) 11 | 12 | #define _PLUGIN_NAME_STR_2(s) #s 13 | #define _PLUGIN_NAME_STR(s) _PLUGIN_NAME_STR_2(s) 14 | #define PLUGIN_NAME_STR _PLUGIN_NAME_STR(VIM_PLUGIN_NAME) 15 | 16 | // Symbols within this program are exported 17 | // Mantra: 18 | // The symbols exported are the ones necessary. 19 | // Any exported symbols *MUST* be namespaced. 20 | #define VIM_EXTERN extern __attribute__((visibility("default"))) 21 | 22 | 23 | // Plugin load is called to bootstrap the plugin in vim 24 | // These methods are defined within the user provided library 25 | // The VimInteface doesn't actually define these. 26 | extern int PLUGIN_FUNC(VIM_PLUGIN_NAME, _plugin_load)(const char *) __attribute__((weak)); 27 | extern const char *PLUGIN_FUNC(VIM_PLUGIN_NAME, _plugin_runloop_callback)(void) __attribute__((weak)); 28 | extern const char *PLUGIN_FUNC(VIM_PLUGIN_NAME, _plugin_invoke)(void*) __attribute__((weak)); 29 | 30 | static PyObject *swiftvimError; 31 | 32 | // Python methods 33 | static PyObject *PLUGIN_FUNC(VIM_PLUGIN_NAME, _load)(PyObject *self, PyObject *args); 34 | static PyObject *PLUGIN_FUNC(VIM_PLUGIN_NAME, _invoke)(PyObject *self, PyObject *args); 35 | static PyObject *PLUGIN_FUNC(VIM_PLUGIN_NAME, _runloop_callback)(PyObject *self, PyObject *args); 36 | 37 | static PyMethodDef swiftvimMethods[] = { 38 | {"load", PLUGIN_FUNC(VIM_PLUGIN_NAME, _load), METH_VARARGS, 39 | "Load the plugin."}, 40 | 41 | {"invoke", PLUGIN_FUNC(VIM_PLUGIN_NAME, _invoke), METH_VARARGS, 42 | "Invoke a user callback"}, 43 | 44 | {"runloop_callback", PLUGIN_FUNC(VIM_PLUGIN_NAME, _runloop_callback), METH_VARARGS, 45 | "RunLoop callback"}, 46 | 47 | {NULL, NULL, 0, NULL} /* Sentinel */ 48 | }; 49 | 50 | #if PY_MAJOR_VERSION == 3 51 | 52 | static struct PyModuleDef swiftvimmodule = { 53 | PyModuleDef_HEAD_INIT, 54 | PLUGIN_NAME_STR, /* name of module */ 55 | NULL, /* module documentation, may be NULL */ 56 | -1, /* size of per-interpreter state of the module, 57 | or -1 if the module keeps state in global variables. */ 58 | swiftvimMethods 59 | }; 60 | 61 | VIM_EXTERN PyMODINIT_FUNC PLUGIN_FUNC(PyInit_, VIM_PLUGIN_NAME)(void) { 62 | PyObject *m; 63 | 64 | m = PyModule_Create(&swiftvimmodule); 65 | if (m == NULL) 66 | return NULL; 67 | 68 | swiftvimError = PyErr_NewException("swiftvim.error", NULL, NULL); 69 | Py_INCREF(swiftvimError); 70 | PyModule_AddObject(m, "error", swiftvimError); 71 | return m; 72 | } 73 | 74 | #else 75 | 76 | VIM_EXTERN PyMODINIT_FUNC PLUGIN_FUNC(init, VIM_PLUGIN_NAME)(void) { 77 | PyObject *m; 78 | 79 | m = Py_InitModule(PLUGIN_NAME_STR, swiftvimMethods); 80 | if (m == NULL) 81 | return; 82 | 83 | swiftvimError = PyErr_NewException("swiftvim.error", NULL, NULL); 84 | Py_INCREF(swiftvimError); 85 | PyModule_AddObject(m, "error", swiftvimError); 86 | } 87 | #endif 88 | 89 | // Mark - Method Implementations 90 | 91 | static int calledPluginInit = 0; 92 | 93 | static PyObject *PLUGIN_FUNC(VIM_PLUGIN_NAME, _load)(PyObject *self, PyObject *args) 94 | { 95 | int status = 1; 96 | if (calledPluginInit == 0) { 97 | status = PLUGIN_FUNC(VIM_PLUGIN_NAME, _plugin_load)("load"); 98 | calledPluginInit = 1; 99 | } else { 100 | fprintf(stderr, "warning: called swiftvim.plugin_load more than once"); 101 | } 102 | return Py_BuildValue("i", status); 103 | } 104 | 105 | static PyObject *PLUGIN_FUNC(VIM_PLUGIN_NAME, _runloop_callback)(PyObject *self, PyObject *args) 106 | { 107 | PLUGIN_FUNC(VIM_PLUGIN_NAME, _plugin_runloop_callback)(); 108 | return Py_BuildValue("i", 0); 109 | } 110 | 111 | static PyObject *PLUGIN_FUNC(VIM_PLUGIN_NAME, _invoke)(PyObject *self, PyObject *args) 112 | { 113 | void *result = (void *)PLUGIN_FUNC(VIM_PLUGIN_NAME, _plugin_invoke)(args); 114 | return Py_BuildValue("s", result); 115 | } 116 | 117 | 118 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import SPMVimTests 3 | 4 | XCTMain([ 5 | testCase(SPMVimTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/VimAsyncTests/VimAsyncTest.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import VimInterface 3 | import ExampleVim 4 | import ExampleVimAsync 5 | import Foundation 6 | 7 | class VimAsyncTests: XCTestCase { 8 | static var allTests = [ 9 | ("testCommandNone", testCommandNone) 10 | ] 11 | 12 | func testCommandNone() { 13 | swiftvim_initialize() 14 | var result: VimValue! 15 | let semaphore = DispatchSemaphore(value: 0) 16 | let serial = DispatchQueue(label: "Queuename") 17 | serial.async { 18 | VimTask.onMain { 19 | result = try! Vim.command("VALUE") 20 | semaphore.signal() 21 | } 22 | } 23 | 24 | let timeout = DispatchTime.now() + .seconds(120) 25 | guard semaphore.wait(timeout: timeout) != .timedOut else { 26 | fatalError("Fail.") 27 | } 28 | XCTAssertNotNil(result) 29 | XCTAssertNil(String(result)) 30 | swiftvim_finalize() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/VimInterfaceTests/CallingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import VimInterface 3 | 4 | /// These tests make assertions about "vim.py" 5 | class VimInterfaceTests: XCTestCase { 6 | static var allTests = [ 7 | ("testEvalString", testEvalString), 8 | ("testEvalInt", testEvalInt), 9 | ("testCommandNone", testCommandNone), 10 | ("testPyEval", testPyEval), 11 | ] 12 | 13 | func testEvalString() { 14 | swiftvim_initialize() 15 | "VALUE".withCString { cStr in 16 | let result = swiftvim_eval( 17 | UnsafeMutablePointer(mutating: cStr)) 18 | let str = swiftvim_asstring(result) 19 | 20 | let value = String(cString: str!) 21 | XCTAssertEqual(value, "VALUE") 22 | } 23 | swiftvim_finalize() 24 | } 25 | 26 | func testCommandNone() { 27 | swiftvim_initialize() 28 | "VALUE".withCString { cStr in 29 | // This command returns a None 30 | let result = swiftvim_command( 31 | UnsafeMutablePointer(mutating: cStr)) 32 | // This should return a null 33 | let str = swiftvim_asstring(result) 34 | XCTAssertNil(str) 35 | } 36 | swiftvim_finalize() 37 | } 38 | 39 | // Low level testing 40 | func testEvalInt() { 41 | swiftvim_initialize() 42 | // eval_int is a function that returns an int 43 | "vim".withCString { moduleCStr in 44 | "eval_int".withCString { fCStr in 45 | "1".withCString { argCStr in 46 | let result = swiftvim_call( 47 | UnsafeMutablePointer(mutating: moduleCStr), 48 | UnsafeMutablePointer(mutating: fCStr), 49 | UnsafeMutablePointer(mutating: argCStr)) 50 | let value = swiftvim_asnum(result) 51 | XCTAssertEqual(value, 1) 52 | } 53 | } 54 | } 55 | swiftvim_finalize() 56 | } 57 | 58 | // Low level testing 59 | func testPyEval() { 60 | swiftvim_initialize() 61 | // Swap out the runtime 62 | let setEvalAsInt = """ 63 | runtime.eval = lambda value : int(value) 64 | """ 65 | "vim".withCString { moduleCStr in 66 | "py_exec".withCString { fCStr in 67 | setEvalAsInt.withCString { argCStr in 68 | swiftvim_call( 69 | UnsafeMutablePointer(mutating: moduleCStr), 70 | UnsafeMutablePointer(mutating: fCStr), 71 | UnsafeMutablePointer(mutating: argCStr)) 72 | } 73 | } 74 | } 75 | 76 | // Verify that above eval was correct 77 | "vim".withCString { moduleCStr in 78 | "eval".withCString { fCStr in 79 | "1".withCString { argCStr in 80 | let result = swiftvim_call( 81 | UnsafeMutablePointer(mutating: moduleCStr), 82 | UnsafeMutablePointer(mutating: fCStr), 83 | UnsafeMutablePointer(mutating: argCStr)) 84 | let value = swiftvim_asnum(result) 85 | XCTAssertEqual(value, 1) 86 | } 87 | } 88 | } 89 | swiftvim_finalize() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Tests/VimInterfaceTests/MockVimRuntime/vim.py: -------------------------------------------------------------------------------- 1 | import types 2 | import sys 3 | import os 4 | 5 | # Setup the build dir like the vim plugin does. 6 | src_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 7 | "../../../") 8 | sys.path.insert(0, os.path.join(src_dir, '.build')) 9 | import Example 10 | 11 | class MockBuffer(): 12 | def __init__(self): 13 | self.number = 1 14 | self.name = "mock" 15 | 16 | class MockWindow(): 17 | def __init__(self): 18 | self.cursor = (1, 2) 19 | 20 | # actual vim apis 21 | 22 | class Current(): 23 | def __init__(self): 24 | self.buffer = MockBuffer() 25 | self.window = MockWindow() 26 | 27 | current = Current() 28 | 29 | class MockRuntime(): 30 | def __init__(self): 31 | self.command = lambda value: None 32 | self.eval = lambda value: value 33 | 34 | runtime = MockRuntime() 35 | 36 | def command(value): 37 | return runtime.command(value) 38 | 39 | def eval(value): 40 | return runtime.eval(value) 41 | 42 | def eval_int(value): 43 | return int(value) 44 | 45 | def eval_bool(value): 46 | return True 47 | 48 | def py_exec(value): 49 | exec(value) 50 | 51 | -------------------------------------------------------------------------------- /Tests/VimTests/VimTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import VimInterface 3 | import ExampleVim 4 | 5 | /// Here we mutate the runtime to do things we want 6 | /// Note, that this relies on the fact it uses python internally 7 | /// to make testing easier. 8 | func mutateRuntime(_ f: String, lambda: String) { 9 | let evalStr = "runtime.\(f) = \(lambda)" 10 | _ = "vim".withCString { moduleCStr in 11 | "py_exec".withCString { fCStr in 12 | evalStr.withCString { argCStr in 13 | swiftvim_call( 14 | UnsafeMutablePointer(mutating: moduleCStr), 15 | UnsafeMutablePointer(mutating: fCStr), 16 | UnsafeMutablePointer(mutating: argCStr)) 17 | } 18 | } 19 | } 20 | } 21 | 22 | class VimValueTests: XCTestCase { 23 | static var allTests = [ 24 | ("testEvalString", testEvalString), 25 | ("testEvalInt", testEvalInt), 26 | ("testEvalList", testEvalList), 27 | ("testEvalDict", testEvalDict), 28 | ("testCommandNone", testCommandNone), 29 | ("testListCollectionUsage", testListCollectionUsage), 30 | ("testDictCollectionUsage", testDictCollectionUsage), 31 | ("testBufferAttrs", testBufferAttrs), 32 | ] 33 | 34 | func testEvalString() { 35 | swiftvim_initialize() 36 | let result = try! Vim.eval("VALUE") 37 | XCTAssertEqual(String(result), "VALUE") 38 | swiftvim_finalize() 39 | } 40 | 41 | func testCommandNone() { 42 | swiftvim_initialize() 43 | let result = try! Vim.command("VALUE") 44 | XCTAssertNil(String(result)) 45 | swiftvim_finalize() 46 | } 47 | 48 | func testEvalInt() { 49 | swiftvim_initialize() 50 | mutateRuntime("eval", lambda: "lambda value : int(value)") 51 | let result = try! Vim.eval("2") 52 | XCTAssertEqual(Int(result), 2) 53 | swiftvim_finalize() 54 | } 55 | 56 | func testEvalList() { 57 | swiftvim_initialize() 58 | mutateRuntime("eval", lambda: "lambda value : [1, 2]") 59 | let result = try! Vim.eval("") 60 | let list = VimList(result)! 61 | XCTAssertEqual(list.count, 2) 62 | XCTAssertEqual(Int(list[0]), 1) 63 | XCTAssertEqual(Int(list[1]), 2) 64 | list[1] = list[0] 65 | XCTAssertEqual(Int(list[1]), 1) 66 | swiftvim_finalize() 67 | } 68 | 69 | func testListCollectionUsage() { 70 | swiftvim_initialize() 71 | mutateRuntime("eval", lambda: "lambda value : [1, 2]") 72 | let result = try! Vim.eval("") 73 | let list = VimList(result)! 74 | /// Smoke test we can do collectiony things. 75 | let incremented = list.map { Int($0)! + 1 } 76 | XCTAssertEqual(incremented[1], 3) 77 | swiftvim_finalize() 78 | } 79 | 80 | func testEvalDict() { 81 | swiftvim_initialize() 82 | mutateRuntime("eval", lambda: "lambda value : dict(a=42, b='a')") 83 | let result = try! Vim.eval("") 84 | let dict = VimDictionary(result)! 85 | let aVal = dict["a"]! 86 | XCTAssertEqual(Int(aVal)!, 42) 87 | let nonVal = dict["s"] 88 | XCTAssertNil(Int(nonVal)) 89 | 90 | swiftvim_finalize() 91 | } 92 | 93 | func testDictCollectionUsage() { 94 | swiftvim_initialize() 95 | mutateRuntime("eval", lambda: "lambda value : dict(a=42, b='a')") 96 | let result = try! Vim.eval("") 97 | let dict = VimDictionary(result)! 98 | XCTAssertEqual(dict.keys.count, 2) 99 | XCTAssertEqual(dict.values.count, 2) 100 | swiftvim_finalize() 101 | } 102 | 103 | func testBufferAttrs() { 104 | swiftvim_initialize() 105 | let buffer = Vim.current.buffer 106 | XCTAssertEqual(buffer.number, 1) 107 | XCTAssertEqual(buffer.name, "mock") 108 | swiftvim_finalize() 109 | } 110 | 111 | func testWindowAttrs() { 112 | swiftvim_initialize() 113 | let window = Vim.current.window 114 | XCTAssertEqual(window.cursor.0, 1) 115 | XCTAssertEqual(window.cursor.1, 2) 116 | swiftvim_finalize() 117 | } 118 | 119 | func testCallback() { 120 | var called = false 121 | var calledArgs: [VimValue]? 122 | let callback: (([VimValue]) -> VimScriptConvertible?) = { 123 | args in 124 | called = true 125 | calledArgs = args 126 | print("test-info: DidCallback", args) 127 | return nil 128 | } 129 | 130 | swiftvim_initialize() 131 | let erasedFunc = callback as AnyObject 132 | let address = unsafeBitCast(erasedFunc, to: Int.self) 133 | VimPlugin.setCallable(String(address), callable: callback) 134 | let cb = "Example.invoke('\(address)', 'arga')" 135 | print("test-info: Callback:", cb) 136 | mutateRuntime("eval", lambda: "lambda value : \(cb)") 137 | _ = try? Vim.eval("") 138 | print("test-info: Callback Args:", calledArgs ?? "") 139 | XCTAssertTrue(called) 140 | XCTAssertEqual(calledArgs?.count, 1) 141 | XCTAssertEqual(String(calledArgs?.first), "arga") 142 | swiftvim_finalize() 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /VimUtils/make_lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Find vims and pythons 4 | # Tested under: 5 | # - brew vim8 / python 2.7 6 | # - brew vim8 / python 3.6.5 7 | # - brew macvim / python 3.6.5 8 | # - custom built vims against OSX python 9 | 10 | function realpath() { 11 | echo $(python -c "import os; print(os.path.realpath('$1'))") 12 | } 13 | 14 | function find_py_from_vim() { 15 | VIM_PATH="" 16 | WHICH_VIM=$(realpath $(which vim)) 17 | 18 | if [[ $(echo $(file $WHICH_VIM) | grep -q shell; echo $?) -eq 0 ]]; then 19 | # Assumptions about vim inside of MacVim.app which is a script 20 | if [[ $(cat $WHICH_VIM | grep -q MacVim; echo $?) -eq 0 ]]; then 21 | VIM_PATH=$(dirname $(dirname $WHICH_VIM))/MacOS/Vim 22 | fi 23 | else 24 | VIM_PATH=$WHICH_VIM 25 | fi 26 | 27 | if [[ $(test -x "$VIM_PATH") -ne 0 ]]; then 28 | >&2 echo "error: can't find vim" 29 | exit 1 30 | fi 31 | 32 | _PYTHON_LINKED=$(otool -l $VIM_PATH | grep Python | awk '{ print $2 }') 33 | PYTHON_F=$(realpath $_PYTHON_LINKED) 34 | } 35 | 36 | # If the user specifies, we'll use a python. 37 | # This must point to the actual version: 38 | # /System/Library/Frameworks/Python.framework/Python 39 | # and is mainly for testing only. 40 | if [[ "$USE_PYTHON" ]]; then 41 | >&2 echo "using specified python $USE_PYTHON" 42 | PYTHON_F=$(realpath "$USE_PYTHON") 43 | else 44 | find_py_from_vim 45 | fi 46 | 47 | # Test if we've got a dylib on our hands. 48 | if [[ $(echo $(file "$PYTHON_F") | grep -q dynamic; echo $?) -ne 0 ]]; then 49 | # Fall back to the default python 50 | DEFAULT_PY=$(realpath "/System/Library/Frameworks/Python.framework/Python") 51 | >&2 echo "warning: can't find linked python. Falling back to $DEFAULT_PY." 52 | >&2 echo "this may not work out so well for many reasons" 53 | PYTHON_F=$DEFAULT_PY 54 | fi 55 | 56 | function python_info() { 57 | echo "Found vim executable $VIM_PATH" 58 | echo "Found python executable $PYTHON_F" 59 | } 60 | 61 | function linked_python() { 62 | echo $PYTHON_F 63 | } 64 | 65 | function python_inc_dir() { 66 | ROOT=$(dirname $PYTHON_F) 67 | if [[ -d $ROOT/Headers ]]; then 68 | echo $ROOT/Headers 69 | return 70 | fi 71 | 72 | # Handle builds of python like Apple system pythons 73 | # i.e. 74 | # /System/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7 75 | VERSION_INC=$(ls $ROOT/include | head -1) 76 | if [[ -d $ROOT/include/$VERSION_INC ]]; then 77 | echo $ROOT/include/$VERSION_INC 78 | return 79 | fi 80 | } 81 | 82 | --------------------------------------------------------------------------------