├── SourceKittenWrapper ├── Tests │ └── .gitkeep ├── .gitignore ├── Package.swift └── Sources │ └── main.swift ├── .gitignore ├── Makefile ├── README.md ├── ftplugin └── swift.vim └── pythonx └── completor_swift.py /SourceKittenWrapper/Tests/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .cache 3 | doc/tags 4 | .DS_Store 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /SourceKittenWrapper/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | Package.resolved 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | sourcekitten: 2 | cd ./SourceKittenWrapper && swift build -c release 3 | 4 | 5 | .PHONY: sourcekitten 6 | -------------------------------------------------------------------------------- /SourceKittenWrapper/Package.swift: -------------------------------------------------------------------------------- 1 | import PackageDescription 2 | 3 | let package = Package( 4 | name: "SourceKittenWrapper", 5 | dependencies: [ 6 | .Package(url: "https://github.com/jpsim/SourceKitten.git", Version(0, 18, 1)) 7 | ] 8 | ) 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | completor-swift 2 | =============== 3 | 4 | Swift code completion for [completor.vim](https://github.com/maralla/completor.vim.git). 5 | 6 | 7 | Install 8 | ------- 9 | 10 | [Install completor.vim](https://github.com/maralla/completor.vim#install) first. 11 | 12 | For [vim-plug](https://github.com/junegunn/vim-plug) 13 | 14 | ``` 15 | Plug 'maralla/completor-swift' 16 | ``` 17 | 18 | To enable swift completion, swift3 should be installed. Then go to the root directory of *completor-swift* and run: 19 | 20 | ```bash 21 | make 22 | ``` 23 | 24 | Tips 25 | ---- 26 | 27 | Use `` To jump to placeholder: 28 | 29 | ```vim 30 | imap CompletorSwiftJumpToPlaceholder 31 | map CompletorSwiftJumpToPlaceholder 32 | ``` 33 | -------------------------------------------------------------------------------- /ftplugin/swift.vim: -------------------------------------------------------------------------------- 1 | let s:pat = '<#[^#]\+#>' 2 | 3 | 4 | function! s:jump_to_placeholder() 5 | let [_, lnum, column, offset] = getpos('.') 6 | let place = search(s:pat, 'zn', lnum) 7 | if !place 8 | call cursor(lnum, 1, offset) 9 | endif 10 | let [_, start] = searchpos(s:pat, 'z', lnum) 11 | if start == 0 12 | call cursor(lnum, column, offset) 13 | return '' 14 | endif 15 | let [_, end] = searchpos(s:pat, 'enz', lnum) 16 | if start == end 17 | return '' 18 | endif 19 | 20 | let range_cmd = '' 21 | if mode() !=? 'n' 22 | let range_cmd .= "\" 23 | endif 24 | 25 | let screen_start = virtcol([lnum, start]) 26 | let screen_end = virtcol([lnum, end]) 27 | 28 | let range_cmd .= 'v'.lnum.'G'.screen_end.'|o'.lnum.'G'.screen_start."|o\" 29 | call feedkeys(range_cmd) 30 | return '' 31 | endfunction 32 | 33 | 34 | inoremap CompletorSwiftJumpToPlaceholder jump_to_placeholder() 35 | noremap CompletorSwiftJumpToPlaceholder jump_to_placeholder() 36 | -------------------------------------------------------------------------------- /SourceKittenWrapper/Sources/main.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SourceKittenFramework 3 | 4 | var spmModule = "" 5 | var compilerArgs = "" 6 | 7 | var iter = CommandLine.arguments[1 ..< CommandLine.arguments.endIndex].makeIterator() 8 | while let option = iter.next() { 9 | guard let value = iter.next() else { 10 | print("Invalid argument") 11 | exit(EXIT_FAILURE) 12 | } 13 | 14 | switch option { 15 | case "-spm-module": spmModule = value 16 | case "-compiler-args": compilerArgs = value 17 | default: 18 | print("Invalid argument: \(option)") 19 | exit(EXIT_FAILURE) 20 | } 21 | } 22 | 23 | let path = "\(NSUUID().uuidString).swift" 24 | var args: [String] 25 | 26 | if spmModule.isEmpty { 27 | args = ["-c", path] + compilerArgs.characters.split(separator: " ").map(String.init) 28 | if args.index(of: "-sdk") == nil { 29 | args.append(contentsOf: ["-sdk", sdkPath()]) 30 | } 31 | } else { 32 | guard let module = Module(spmName: spmModule) else { 33 | print("Bad module name: \(spmModule)") 34 | exit(EXIT_FAILURE) 35 | } 36 | args = module.compilerArguments 37 | } 38 | 39 | func parseInput(_ input: String) -> (String, Int64)? { 40 | guard let data = input.data(using: String.Encoding.utf8) else { 41 | return nil 42 | } 43 | 44 | do { 45 | guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] else { 46 | return nil 47 | } 48 | guard let content = json["content"] as? String else { 49 | return nil 50 | } 51 | guard let offset = json["offset"] as? Int64 else { 52 | return nil 53 | } 54 | return (content, offset) 55 | } catch { 56 | return nil 57 | } 58 | } 59 | 60 | func toJson(_ obj: Any) -> Data { 61 | do { 62 | return try JSONSerialization.data(withJSONObject: obj, options: []) 63 | } catch {} 64 | return Data() 65 | } 66 | 67 | while true { 68 | guard let input = readLine(strippingNewline: true) else { 69 | continue 70 | } 71 | guard let (content, offset) = parseInput(input) else { 72 | continue 73 | } 74 | let request = Request.codeCompletionRequest(file: path, contents: content, offset: offset, arguments: args) 75 | let items = CodeCompletionItem.parse(response: request.send()).map { 76 | ["abbr": $0.descriptionKey, "menu": $0.kind, "word": $0.sourcetext] 77 | } 78 | var data = toJson(items) 79 | data.append(10) 80 | FileHandle.standardOutput.write(data) 81 | } 82 | -------------------------------------------------------------------------------- /pythonx/completor_swift.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | import json 5 | import os.path 6 | 7 | from completor import Completor, vim 8 | from completor.compat import to_unicode 9 | 10 | path = os.path.dirname(__file__) 11 | 12 | wrapper = os.path.join(path, '..', 'SourceKittenWrapper', '.build', 13 | 'release', 'SourceKittenWrapper') 14 | 15 | pat = re.compile('\w+$|\.\s*\w*$', re.U) 16 | placeholder = re.compile('<#T##([^#]+:\s*[^#]+##)?(?P[^#]+)#>', re.U) 17 | 18 | 19 | def replace(match): 20 | matches = match.groupdict() 21 | return '<#{}#>'.format(matches['ty']) if 'ty' in matches else '' 22 | 23 | 24 | class SourceKitten(Completor): 25 | filetype = 'swift' 26 | daemon = True 27 | args_file = '.sourcekitten_complete' 28 | 29 | def format_cmd(self): 30 | binary = self.get_option('sourcekittenwrapper_binary') or wrapper 31 | spm_module = self.get_option('sourcekitten_spm_module') or '' 32 | extra_args = self.get_option('sourcekitten_extra_args') or '' 33 | 34 | args = [binary] 35 | if spm_module: 36 | args.extend(['-spm-module', spm_module]) 37 | if extra_args: 38 | args.extend(['-compiler-args', extra_args]) 39 | else: 40 | file_args = self.parse_config(self.args_file) 41 | if file_args: 42 | args.extend(['-compiler-args', ' '.join(file_args)]) 43 | return args 44 | 45 | def offset(self): 46 | line, col = vim.current.window.cursor 47 | line2byte = vim.Function('line2byte') 48 | return line2byte(line) + col - 1 49 | 50 | def start_column(self): 51 | col = super(SourceKitten, self).start_column() 52 | parts = self.input_data.rsplit() 53 | if parts and '.' in parts[-1]: 54 | col -= 1 55 | return col 56 | 57 | def request(self, action=None): 58 | offset = self.offset() - 1 59 | match = pat.search(self.input_data) 60 | if not match: 61 | return '' 62 | start, end = match.span() 63 | offset -= end - start - 1 64 | 65 | return json.dumps({ 66 | 'content': '\n'.join(vim.current.buffer[:]), 67 | 'offset': offset 68 | }) 69 | 70 | def parse(self, items): 71 | res = [] 72 | prefix = '' 73 | match = pat.search(self.input_data) 74 | if match: 75 | prefix = match.group() 76 | 77 | if prefix.startswith('.'): 78 | prefix = prefix.strip('.') 79 | 80 | try: 81 | data = to_unicode(items[0], 'utf-8') 82 | for item in json.loads(data): 83 | if not item['abbr'].startswith(prefix): 84 | continue 85 | item['word'] = placeholder.sub( 86 | replace, to_unicode(item['word'], 'utf-8')) 87 | item['menu'] = item['menu'].replace( 88 | 'source.lang.swift.decl.', '') 89 | res.append(item) 90 | return res 91 | except Exception: 92 | return [] 93 | --------------------------------------------------------------------------------