├── .gitignore ├── README.md ├── autoload └── ncm2_phpactor.vim ├── ncm2-plugin └── ncm2_phpactor.vim └── pythonx └── ncm2_phpactor.py /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /test.py 3 | /test.php 4 | /composer.lock 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [phpactor](https://github.com/phpactor/phpactor) integration for 2 | [ncm2](https://github.com/ncm2/ncm2) 3 | 4 | ![phpactor](https://user-images.githubusercontent.com/4538941/30627852-67643a22-9e05-11e7-90d1-aa75c2d0654c.gif) 5 | 6 | ## Installation 7 | 8 | Assuming you're using [vim-plug](https://github.com/junegunn/vim-plug) 9 | 10 | ```vim 11 | " Include Phpactor 12 | Plug 'phpactor/phpactor' , {'do': 'composer install', 'for': 'php'} 13 | 14 | " Require ncm2 and this plugin 15 | Plug 'ncm2/ncm2' 16 | Plug 'roxma/nvim-yarp' 17 | Plug 'phpactor/ncm2-phpactor' 18 | ``` 19 | 20 | Additionally you will need to set the following options: 21 | 22 | ```vim 23 | autocmd BufEnter * call ncm2#enable_for_buffer() 24 | set completeopt=noinsert,menuone,noselect 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /autoload/ncm2_phpactor.vim: -------------------------------------------------------------------------------- 1 | if get(s:, 'loaded', 0) 2 | finish 3 | endif 4 | let s:loaded = 1 5 | 6 | let g:ncm2_phpactor_timeout = get(g:, 'ncm2_phpactor_timeout', 5) 7 | 8 | let g:ncm2_phpactor#proc = yarp#py3({ 9 | \ 'module': 'ncm2_phpactor', 10 | \ 'on_load': { -> ncm2#set_ready(g:ncm2_phpactor#source)} 11 | \ }) 12 | 13 | let g:ncm2_phpactor#source = extend(get(g:, 'ncm2_phpactor#source', {}), { 14 | \ 'name': 'phpactor', 15 | \ 'ready': 0, 16 | \ 'priority': 9, 17 | \ 'mark': 'b', 18 | \ 'scope': ['php'], 19 | \ 'word_pattern': '[\$\w][\w]*', 20 | \ 'complete_pattern': ['\$', '-\>', '::'], 21 | \ 'subscope_enable': 1, 22 | \ 'on_complete': 'ncm2_phpactor#on_complete', 23 | \ 'on_warmup': 'ncm2_phpactor#on_warmup', 24 | \ }, 'keep') 25 | 26 | func! ncm2_phpactor#init() 27 | call ncm2#register_source(g:ncm2_phpactor#source) 28 | endfunc 29 | 30 | func! ncm2_phpactor#on_warmup(ctx) 31 | call g:ncm2_phpactor#proc.jobstart() 32 | endfunc 33 | 34 | func! ncm2_phpactor#on_complete(ctx) 35 | " g:phpactorPhpBin and g:phpactorbinpath, g:phpactorInitialCwd is defined in 36 | " phpactor plugin 37 | call g:ncm2_phpactor#proc.try_notify('on_complete', 38 | \ a:ctx, 39 | \ getline(1, '$'), 40 | \ getcwd(), 41 | \ [g:phpactorPhpBin, 42 | \ g:phpactorbinpath, 43 | \ 'complete', '-d', g:phpactorInitialCwd, 44 | \ '--format=json', '--', 'stdin' 45 | \ ]) 46 | endfunc 47 | 48 | -------------------------------------------------------------------------------- /ncm2-plugin/ncm2_phpactor.vim: -------------------------------------------------------------------------------- 1 | call ncm2_phpactor#init() 2 | -------------------------------------------------------------------------------- /pythonx/ncm2_phpactor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import vim 4 | from ncm2 import Ncm2Source, getLogger 5 | import re 6 | 7 | import subprocess 8 | from ncm2 import Popen 9 | import json 10 | 11 | logger = getLogger(__name__) 12 | 13 | 14 | class Source(Ncm2Source): 15 | 16 | def __init__(self, nvim): 17 | super(Source, self).__init__(nvim) 18 | self.completion_timeout = self.nvim.eval('g:ncm2_phpactor_timeout') or 5 19 | 20 | def on_complete(self, ctx, lines, cwd, phpactor_complete): 21 | src = "\n".join(lines) 22 | src = self.get_src(src, ctx) 23 | 24 | lnum = ctx['lnum'] 25 | 26 | # use byte addressing 27 | bcol = ctx['bcol'] 28 | src = src.encode() 29 | 30 | pos = self.lccol2pos(lnum, bcol, src) 31 | args = phpactor_complete 32 | args += [str(pos)] 33 | 34 | proc = Popen(args=args, 35 | stdin=subprocess.PIPE, 36 | stdout=subprocess.PIPE, 37 | stderr=subprocess.DEVNULL) 38 | 39 | result, errs = proc.communicate(src, timeout=self.completion_timeout) 40 | 41 | result = result.decode() 42 | 43 | logger.debug("args: %s, result: [%s]", args, result) 44 | 45 | result = json.loads(result) 46 | 47 | if not result or not result.get('suggestions', None): 48 | return 49 | 50 | # { 51 | # "suggestions": [ 52 | # { 53 | # "type": "f", 54 | # "name": "setFormatter", 55 | # "info": "pub setFormatter(OutputFormatterInterface $formatter)" 56 | # } 57 | # ] 58 | # } 59 | 60 | matches = [] 61 | 62 | for e in result['suggestions']: 63 | menu = e['short_description'] 64 | word = e['name'] 65 | t = e['type'] 66 | 67 | item = dict(word=word, menu=menu, info=menu) 68 | 69 | # snippet support 70 | m = re.search(r'(\w+\s+)?\w+\((.*)\)', menu) 71 | 72 | if m and (t == 'function' or t == 'method'): 73 | 74 | params = m.group(2) 75 | 76 | placeholders = [] 77 | num = 1 78 | snip_args = '' 79 | 80 | if params != '': 81 | 82 | params = params.split(',') 83 | 84 | for param in params: 85 | 86 | if "=" in param: 87 | # skip params with default value 88 | break 89 | else: 90 | if not re.search(r'\$\w+', param): 91 | break 92 | param = re.search(r'\$\w+', param).group() 93 | ph = self.snippet_placeholder(num, param) 94 | placeholders.append(ph) 95 | num += 1 96 | 97 | snip_args = ', '.join(placeholders) 98 | 99 | if len(placeholders) == 0: 100 | # don't jump out of parentheses if function has 101 | # parameters 102 | snip_args = self.snippet_placeholder(1) 103 | 104 | ph0 = self.snippet_placeholder(0) 105 | snippet = '%s(%s)%s' % (word, snip_args, ph0) 106 | 107 | item['user_data'] = {'snippet': snippet, 'is_snippet': 1} 108 | 109 | matches.append(item) 110 | 111 | self.complete(ctx, ctx['startccol'], matches) 112 | 113 | def snippet_placeholder(self, num, txt=''): 114 | txt = txt.replace('\\', '\\\\') 115 | txt = txt.replace('$', r'\$') 116 | txt = txt.replace('}', r'\}') 117 | if txt == '': 118 | return '${%s}' % num 119 | return '${%s:%s}' % (num, txt) 120 | 121 | 122 | source = Source(vim) 123 | 124 | on_complete = source.on_complete 125 | --------------------------------------------------------------------------------