├── .gitignore
├── LICENSE
├── README.md
├── after
└── syntax
│ ├── c.vim
│ └── objc.vim
├── autoload
└── clang2.vim
├── plugin
└── clang2.vim
├── pythonx
└── clang2_warp.py
└── rplugin
├── clang2
├── __init__.py
├── scan.py
└── scan_test.py
└── python3
└── deoplete
└── sources
└── deoplete_clang2.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /frameworks
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 | Copyright (c) 2016 Tommy Allen
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of
5 | this software and associated documentation files (the "Software"), to deal in
6 | the Software without restriction, including without limitation the rights to
7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8 | of the Software, and to permit persons to whom the Software is furnished to do
9 | so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
deoplete-clang2
2 | Electric Boogaloo
3 |
4 | This is a `clang` completer for [deoplete.nvim][] that's faster than
5 | [deoplete-clang][]. Instead of using `libclang`, it just uses `clang -cc1`
6 | like most other `clang` plugins. Unlike other plugins, Objective-C was given a
7 | little more attention.
8 |
9 | 
10 |
11 | If you are like me, you:
12 |
13 | - want completions to be relatively easy to setup
14 | - are not entirely sure how to use `clang -cc1`
15 | - just want the damned completions
16 | - for Objective-C
17 | - with the ability to fill in method arguments without having an aneurysm
18 | - and type `]` to wrap method calls (within reason)
19 | - (you also think Xcode's method of doing this sucks)
20 | - also, magically get completions for the MacOSX SDKs
21 | - without Xcode
22 | - on Linux
23 |
24 | This was developed mainly to scratch an old itch. I'm currently not committed
25 | to continuing development beyond fixing obvious bugs. Pull requests to add
26 | useful features are welcome, though.
27 |
28 | With that said, you may want to keep an eye on [clang-server][] that @zchee is
29 | working on.
30 |
31 | ## Requirements
32 |
33 | - [Neovim][] or vim8 with if_python3
34 | - [deoplete.nvim][]
35 | - `clang`
36 |
37 | I'm using `clang 3.8.0`. Lower versions may work, but are untested.
38 |
39 | **Vim8 support:**
40 |
41 | - install [nvim-yarp](https://github.com/roxma/nvim-yarp) plugin for Vim8.
42 | - install neovim python client: `pip install neovim`
43 | - install [vim-hug-neovim-rpc](https://github.com/roxma/vim-hug-neovim-rpc) plugin for Vim8.
44 |
45 |
46 | ## Install
47 |
48 | Follow your package manager's instructions.
49 |
50 |
51 | ## Usage
52 |
53 | Completions will insert functions with argument placeholders in the form of
54 | `<#Type var#>`. While the cursor is on a line with one of these placeholders,
55 | pressing `` will enter select mode with the next placeholder selected.
56 | Pressing `` again will move to the next placeholder and pressing ``
57 | will cycle backwards.
58 |
59 | In Objective-C sources, pressing `]` will try to place a `[` in the appropriate
60 | place. While it isn't perfect, it's a whole lot better than how Xcode works.
61 | You will have the best results by avoiding nested multi-argument method calls.
62 |
63 |
64 | ## Config
65 |
66 | **Note:** For simple projects, you probably don't need to configure anything. You
67 | definitely shouldn't need to configure anything if your project uses a
68 | [compilation database][].
69 |
70 | Create a `.clang` file at your project root. You should be able to just paste
71 | most of your compile flags in there (the parts that make sense at least).
72 | Mainly, it should have the relevant `-I`, `-D`, `-F` flags. The plugin will
73 | try to fill in the blanks for system include paths and discard the flags that
74 | are causing completions to not work.
75 |
76 | You can also use `let g:deoplete#sources#clang#flags = ['-Iwhatever', ...]` in
77 | your nvim configs.
78 |
79 | `g:deoplete#sources#clang#executable` sets the path to the `clang` executable.
80 |
81 | `g:deoplete#sources#clang#autofill_neomake` is a boolean that tells this plugin
82 | to fill in the `g:neomake__clang_maker` variable with the `clang`
83 | executable path and flags. You will still need to enable it with
84 | `g:neomake__enabled_makers = ["clang"]`.
85 |
86 | `g:deoplete#sources#clang#std` is a dict containing the standards you want to
87 | use. It's not used if you already have `-std=whatever` in your flags. The
88 | defaults are:
89 |
90 | ```
91 | {
92 | 'c': 'c11',
93 | 'cpp': 'c++1z',
94 | 'objc': 'c11',
95 | 'objcpp': 'c++1z',
96 | }
97 | ```
98 |
99 | `g:deoplete#sources#clang#preproc_max_lines` sets the maximum number of lines to
100 | search for a `#ifdef` or `#endif` line. `#ifdef` lines are discarded to get
101 | completions within conditional preprocessor blocks. The default is `50`,
102 | setting it to `0` disables this feature.
103 |
104 | ### MacOSX10.`_` SDK completions
105 |
106 | (You may find it funny that I haven't tested this on macOS)
107 |
108 | Just add `-darwin=10.XX` to your flags (where `XX` is the release, e.g.
109 | `10.8`). It will be turned into the following flags:
110 |
111 | ```
112 | -D__MACH__
113 | -D__MAC_OS_X_VERSION_MAX_ALLOWED=10XX
114 | -D__APPLE_CPP__
115 | -DTARGET_CPU_X86_64
116 | -fblocks
117 | -fasm-blocks
118 | -fno-builtin
119 | -isysroot
120 | -iframework/System/Library/Frameworks
121 | -isystem/usr/include
122 | ```
123 |
124 | The above is the minimum flags to get SDK completions without clang spewing a
125 | litany of errors. If you're working on a simple project, `-darwin=10.XX`
126 | should be the only flag you need.
127 |
128 | On macOS, the following directories are searched for the SDK:
129 |
130 | - `/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs`
131 | - `/Developer/SDKs`
132 | - `~/Library/Developer/Frameworks` (download destination)
133 |
134 | On non-macOS:
135 |
136 | - `$XDG_DATA_HOME/SDKs` or `~/.local/share/SDKs` (download destination)
137 |
138 | If the SDK is not found on the system or SDK paths, it will be downloaded from
139 | [phracker/MacOSX-SDKs][] in the background.
140 |
141 |
142 | [deoplete.nvim]: https://github.com/Shougo/deoplete.nvim
143 | [deoplete-clang]: https://github.com/zchee/deoplete-clang
144 | [clang-server]: https://github.com/zchee/clang-server
145 | [Neovim]: https://github.com/neovim/neovim
146 | [compilation database]: http://clang.llvm.org/docs/JSONCompilationDatabase.html
147 | [phracker/MacOSX-SDKs]: https://github.com/phracker/MacOSX-SDKs
148 |
--------------------------------------------------------------------------------
/after/syntax/c.vim:
--------------------------------------------------------------------------------
1 | syntax region CompletePlaceHolder matchgroup=CompletePlaceHolderEnds start="<#" end="#>" concealends containedin=ALL
2 |
3 | highlight default link CompletePlaceHolder NonText
4 | highlight default link CompletePlaceHolderEnds NonText
5 |
--------------------------------------------------------------------------------
/after/syntax/objc.vim:
--------------------------------------------------------------------------------
1 | " Fixes the built-in highlighting.
2 | syn region objcMethodCall start=/\[/ end=/\]/ keepend contains=objcMethodCall,objcBlocks,@objcObjCEntities,@objcCEntities
3 |
--------------------------------------------------------------------------------
/autoload/clang2.vim:
--------------------------------------------------------------------------------
1 | let s:pl_prev = get(g:, 'clang2_placeholder_prev', '')
2 | let s:pl_next = get(g:, 'clang2_placeholder_next', '')
3 |
4 | function! clang2#status(msg) abort
5 | echo '[deoplete-clang2]' a:msg
6 | endfunction
7 |
8 | function! clang2#after_complete() abort
9 | if !exists('v:completed_item') || empty(v:completed_item)
10 | return
11 | endif
12 |
13 | if v:completed_item.word !~# '<#.*#>'
14 | return
15 | endif
16 | call cursor(line('.'), col('.') - strlen(v:completed_item.word))
17 | call feedkeys(s:select_placeholder('n', 0), 'n')
18 | endfunction
19 |
20 |
21 | function! s:noop_postprocess(msg) abort
22 | " Don't allow the whitespace to be compressed to preserve the alignment of
23 | " Fix-It hints.
24 | return a:msg
25 | endfunction
26 |
27 |
28 | function! clang2#set_neomake_cflags(flags) abort
29 | if exists(':Neomake') != 2
30 | return
31 | endif
32 |
33 | if !exists('g:neomake_'.&filetype.'_clang_maker')
34 | let g:neomake_{&filetype}_clang_maker = neomake#makers#ft#c#clang()
35 | endif
36 |
37 | let m = g:neomake_{&filetype}_clang_maker
38 | if !has_key(m, 'orig_args')
39 | let m.orig_args = copy(m.args)
40 | endif
41 |
42 | let m.postprocess = function('s:noop_postprocess')
43 |
44 | let m.args = ['-fsyntax-only']
45 | if &filetype =~# 'objc'
46 | " Pretty much a copy and paste of Xcode's warning flags.
47 | let m.args += [
48 | \ '-fmessage-length=0',
49 | \ '-Wno-objc-property-implementation',
50 | \ '-Wno-objc-missing-property-synthesis',
51 | \ '-Wnon-modular-include-in-framework-module',
52 | \ '-Werror=non-modular-include-in-framework-module',
53 | \ '-Wno-trigraphs',
54 | \ '-fpascal-strings',
55 | \ '-fno-common',
56 | \ '-Wno-missing-field-initializers',
57 | \ '-Wno-missing-prototypes',
58 | \ '-Werror=return-type',
59 | \ '-Wdocumentation',
60 | \ '-Wunreachable-code',
61 | \ '-Wno-implicit-atomic-properties',
62 | \ '-Werror=deprecated-objc-isa-usage',
63 | \ '-Werror=objc-root-class',
64 | \ '-Wno-arc-repeated-use-of-weak',
65 | \ '-Wduplicate-method-match',
66 | \ '-Wno-missing-braces',
67 | \ '-Wparentheses',
68 | \ '-Wswitch',
69 | \ '-Wunused-function',
70 | \ '-Wno-unused-label',
71 | \ '-Wno-unused-parameter',
72 | \ '-Wunused-variable',
73 | \ '-Wunused-value',
74 | \ '-Wempty-body',
75 | \ '-Wconditional-uninitialized',
76 | \ '-Wno-unknown-pragmas',
77 | \ '-Wno-shadow',
78 | \ '-Wno-four-char-constants',
79 | \ '-Wno-conversion',
80 | \ '-Wconstant-conversion',
81 | \ '-Wint-conversion',
82 | \ '-Wbool-conversion',
83 | \ '-Wenum-conversion',
84 | \ '-Wshorten-64-to-32',
85 | \ '-Wpointer-sign',
86 | \ '-Wno-newline-eof',
87 | \ '-Wno-selector',
88 | \ '-Wno-strict-selector-match',
89 | \ '-Wundeclared-selector',
90 | \ '-Wno-deprecated-implementations',
91 | \ '-fasm-blocks',
92 | \ '-fstrict-aliasing',
93 | \ '-Wprotocol',
94 | \ '-Wdeprecated-declarations',
95 | \ '-Wno-sign-conversion',
96 | \ '-Winfinite-recursion',
97 | \ ]
98 | endif
99 |
100 | let m.args += filter(copy(a:flags), 'v:val !~# "^-internal"')
101 | if exists('g:deoplete#sources#clang#executable')
102 | let m.exe = g:deoplete#sources#clang#executable
103 | else
104 | let m.exe = 'clang'
105 | endif
106 | endfunction
107 |
108 |
109 | function! s:find_placeholder(dir) abort
110 | let text = getline('.')
111 | let p1 = []
112 | let p2 = []
113 |
114 | if text !~# '<#.*#>'
115 | return [p1, p2]
116 | endif
117 |
118 | let p = getcurpos()
119 | let origin = p[2]
120 | if origin >= len(text) - 1
121 | let origin = 0
122 | endif
123 |
124 | if a:dir == -1
125 | let s = origin
126 | for _ in range(2)
127 | let s = match(text, '.*\zs<#.\{-}\%<'.s.'c')
128 | if s == -1
129 | let s = match(text, '.*\zs<#')
130 | endif
131 | endfor
132 | else
133 | let s = match(text, '<#', match(text, '<#', origin) == -1 ? 0 : origin)
134 | endif
135 |
136 | let e = match(text, '#\zs>', s)
137 |
138 | if s == -1 || e == -1
139 | return [p1, p2]
140 | endif
141 |
142 | let p[2] = s + 1
143 | let p1 = copy(p)
144 |
145 | let p[2] = e + 1
146 | let p2 = copy(p)
147 |
148 | return [p1, p2]
149 | endfunction
150 |
151 |
152 | function! s:select_placeholder(mode, dir) abort
153 | let [p1, p2] = s:find_placeholder(a:dir)
154 | if empty(p1) || empty(p2)
155 | if mode() =~? 's\|v' || a:dir == 0
156 | return ''
157 | endif
158 |
159 | let key = s:getmap(a:mode, a:dir == -1 ? 0 : 1)
160 | if !empty(key)
161 | return key
162 | endif
163 |
164 | let key = a:dir == -1 ? s:pl_prev : s:pl_next
165 | if key =~# '^<[^<>]*>$'
166 | return eval('"\'.key.'"')
167 | endif
168 |
169 | return key
170 | endif
171 |
172 | call setpos("'<", p1)
173 | call setpos("'>", p2)
174 |
175 | let vkeys = 'gvze'
176 | if visualmode() ==# 'V'
177 | let vkeys = 'gvvze'
178 | endif
179 |
180 | if a:mode ==# 's'
181 | return "\" . vkeys . "\"
182 | endif
183 |
184 | return "\" . vkeys . "\"
185 | endfunction
186 |
187 |
188 | function! clang2#_cl_meth(line, col) abort
189 | let [l, c] = getpos('.')[1:2]
190 | if a:line == l
191 | let c += 1
192 | endif
193 |
194 | call cursor(a:line, a:col)
195 | undojoin
196 | normal! i[
197 | call cursor(l, c)
198 | endfunction
199 |
200 |
201 | function! s:close_brace() abort
202 | if &filetype !~# '\u]\\:call clang2#_cl_meth(".l.",".c.")\"
214 | endif
215 |
216 | let m = s:getmap(']', 0)
217 | if empty(m)
218 | return ']'
219 | endif
220 |
221 | return m
222 | endfunction
223 |
224 | " Parse a map arg
225 | function! s:maparg(map, mode, ...) abort
226 | let default = {'expr': 0, 'rhs': a:0 ? a:1 : substitute(a:map,
227 | \ '\(\\\@]\+>\)',
228 | \ '\=eval(''"\''.submatch(1).''"'')', 'g')}
229 | let arg = maparg(a:map, a:mode, 0, 1)
230 | if empty(arg)
231 | return default
232 | endif
233 |
234 | while arg.rhs =~? '^'
235 | let arg = maparg(arg.rhs, a:mode, 0, 1)
236 | if empty(arg)
237 | return default
238 | endif
239 | endwhile
240 |
241 | let m = {
242 | \ 'rhs': substitute(arg.rhs, '\c', "\" . arg.sid . '_', 'g'),
243 | \ 'expr': arg.expr,
244 | \ }
245 | let m.rhs = substitute(m.rhs,
246 | \ '\(\\\@]\+>\)',
247 | \ '\=eval(''"\''.submatch(1).''"'')', 'g')
248 | return m
249 | endfunction
250 |
251 |
252 | function! s:getmap(mode, map) abort
253 | if !exists('b:clang2_orig_maps')
254 | return ''
255 | endif
256 |
257 | let mode = get(b:clang2_orig_maps, a:mode, [])
258 | if a:map < 0 || a:map >= len(mode)
259 | return a:map
260 | endif
261 |
262 | let m = mode[a:map]
263 |
264 | if empty(m)
265 | return ''
266 | endif
267 |
268 | if m.expr
269 | return eval(m.rhs)
270 | endif
271 |
272 | return m.rhs
273 | endfunction
274 |
275 |
276 | function! s:steal_keys() abort
277 | if exists('b:clang2_orig_maps')
278 | return
279 | endif
280 |
281 | if exists('g:UltiSnipsRemoveSelectModeMappings')
282 | \ && g:UltiSnipsRemoveSelectModeMappings
283 | " We must hide our criminal activities from UltiSnips since it's the world
284 | " police on select maps, apparently.
285 | let ignored = get(g:, 'UltiSnipsMappingsToIgnore', [])
286 | call add(ignored, 'deoplete-clang2')
287 | let g:UltiSnipsMappingsToIgnore = ignored
288 | endif
289 |
290 | " Original map args to use when there's no placeholders
291 | let b:clang2_orig_maps = {
292 | \ 's': [s:maparg(s:pl_prev, 's'), s:maparg(s:pl_next, 's')],
293 | \ 'n': [s:maparg(s:pl_prev, 'n'), s:maparg(s:pl_next, 'n')],
294 | \ 'i': [s:maparg(s:pl_prev, 'i'), s:maparg(s:pl_next, 'i')],
295 | \ ']': [s:maparg(']', 'i', ']')],
296 | \ }
297 |
298 | execute 'snoremap '.strtrans(s:pl_next).' select_placeholder("s", 1)'
299 | execute 'snoremap '.strtrans(s:pl_prev).' select_placeholder("s", -1)'
300 | execute 'nnoremap '.strtrans(s:pl_next).' select_placeholder("n", 1)'
301 | execute 'nnoremap '.strtrans(s:pl_prev).' select_placeholder("n", -1)'
302 | execute 'inoremap '.strtrans(s:pl_next).' select_placeholder("i", 1)'
303 | execute 'inoremap '.strtrans(s:pl_prev).' select_placeholder("i", -1)'
304 |
305 | inoremap ] close_brace()
306 |
307 | autocmd! clang2 InsertEnter
308 | endfunction
309 |
310 |
311 | function! clang2#init() abort
312 | if exists('b:did_clang2')
313 | return
314 | endif
315 |
316 | let b:did_clang2 = 1
317 |
318 | augroup clang2
319 | autocmd! *
320 | autocmd CompleteDone call clang2#after_complete()
321 | autocmd InsertEnter call s:steal_keys()
322 | augroup END
323 | endfunction
324 |
--------------------------------------------------------------------------------
/plugin/clang2.vim:
--------------------------------------------------------------------------------
1 | augroup clang2
2 | autocmd!
3 | autocmd FileType c,cpp,objc,objcpp call clang2#init()
4 | augroup END
5 |
6 | if has('nvim')
7 | if empty(remote#host#PluginsForHost('clang2-rplugin'))
8 | let s:path = expand(':p:h:h')
9 |
10 | " This seems a bit greedy, but it prevents blocking while the main python3
11 | " provider is dealing with completions. Deoplete might be the one that needs
12 | " to run in a Python3 clone.
13 | call remote#host#RegisterClone('clang2-rplugin', 'python3')
14 |
15 | " rplugin is not placed in rplugin/python3 so that it won't be added to the
16 | " manifiest when :UpdateRemotePlugins is called.
17 | call remote#host#RegisterPlugin('clang2-rplugin', s:path.'/rplugin/clang2', [
18 | \ {'sync': v:true, 'name': 'Clang2_objc_close_brace', 'type': 'function', 'opts': {}},
19 | \ ])
20 | endif
21 | else
22 | let s:foo = yarp#py3('clang2_wrap')
23 | function! Clang2_objc_close_brace(v) abort
24 | return s:foo.call('close_objc_brace', a:v)
25 | endfunction
26 | endif
27 |
--------------------------------------------------------------------------------
/pythonx/clang2_warp.py:
--------------------------------------------------------------------------------
1 | from clang2 import Clang2ElectricBoogaloo as _Clang2ElectricBoogaloo
2 | import vim
3 |
4 | _obj = _Clang2ElectricBoogaloo(vim)
5 |
6 |
7 | def close_objc_brace(*args):
8 | return _obj.close_objc_brace(args)
9 |
--------------------------------------------------------------------------------
/rplugin/clang2/__init__.py:
--------------------------------------------------------------------------------
1 | import neovim
2 |
3 | from . import scan
4 |
5 |
6 | @neovim.plugin
7 | class Clang2ElectricBoogaloo(object):
8 | def __init__(self, nvim):
9 | self.nvim = nvim
10 |
11 | @neovim.function('Clang2_objc_close_brace', sync=True)
12 | def close_objc_brace(self, args):
13 | line, col = args
14 | min_line = max(0, line - 10)
15 | buf_lines = self.nvim.current.buffer[min_line:line]
16 | buf_lines[-1] = buf_lines[-1][:col - 1]
17 | text = '\n'.join(buf_lines)
18 | i = scan.find_boundary(text)
19 | if i == -1:
20 | return 0, 0
21 |
22 | line -= text[i:].count('\n')
23 | col = i - text[:i].rfind('\n')
24 | return line, col
25 |
--------------------------------------------------------------------------------
/rplugin/clang2/scan.py:
--------------------------------------------------------------------------------
1 | import re
2 | import string
3 | import logging
4 |
5 | from collections import deque
6 |
7 | log = logging.getLogger('clang2')
8 |
9 | brace_open = '[({'
10 | brace_close = '])}'
11 | quote_delim = '"\''
12 | ascii_chars = string.ascii_letters
13 | whitespace_chars = string.whitespace
14 | word_chars = string.ascii_letters + string.digits + '_'
15 | operators = (
16 | '=', '*', '/', '+', '-', '%', '++', '--',
17 | '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '<<=', '>>=',
18 | '==', '>', '>=', '<', '<=', '!=',
19 | '||', '&&', '^', '!', '~', '&', '|', '?', ':', '<<', '>>',
20 | )
21 | reserved = (
22 | 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
23 | 'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline',
24 | 'int', 'long', 'register', 'restrict', 'return', 'short', 'signed',
25 | 'sizeof', 'static', 'struct', 'switch', 'typedef', 'union', 'unsigned',
26 | 'void', 'volatile', 'while', '_Bool', '_Complex', '_Imaginary', 'bycopy',
27 | 'byref', 'id', 'IMP', 'in', 'inout', 'nil', 'NO', 'NULL', 'oneway', 'out',
28 | 'Protocol', 'SEL', 'YES', '@interface', '@end', '@implementation',
29 | '@protocol', '@class', '@public', '@protected', '@private', '@property',
30 | '@try', '@throw', '@catch', '@finally', '@synthesize', '@dynamic',
31 | '@selector',
32 | )
33 |
34 | left_re = re.compile(r'(?:\(.*?\))?(?:(?:\[.*\])|'
35 | r'(?:[a-zA-Z]\w*?))$').match
36 | arg_re = re.compile(r'[a-zA-Z]\w*:$').match
37 | cast_re = re.compile(r'^\(.*?\)')
38 |
39 |
40 | def white_forward(i, text):
41 | """Scan forward and skip whitespace."""
42 | while i < len(text) and text[i] in whitespace_chars:
43 | i += 1
44 | return i
45 |
46 |
47 | def white_backward(i, text):
48 | """Scan backward and skip whitespace."""
49 | while i > 0 and text[i - 1] in whitespace_chars:
50 | i -= 1
51 | return i
52 |
53 |
54 | def white_backward_continue(i, text):
55 | """Scan backward and skip characters.
56 |
57 | Skipped characters are those that should be included in the current atom.
58 | """
59 | # Type casting can have spaces before the variable.
60 | # Example: (NSString *) variable
61 | if text[i - 1] == ')':
62 | return i - 1
63 |
64 | return i
65 |
66 |
67 | def skip_literal_mark(i, text):
68 | """Check if the current position indicates a literal type.
69 |
70 | Literals will be preceded with @. Example: @"", @{}, @[]
71 |
72 | This will also check for block literals, which are preceded with ^.
73 | Example: ^(params){}, or ^{}
74 | """
75 | if i > 0:
76 | if text[i - 1] == '@':
77 | return i - 1
78 |
79 | if text[i] in '({':
80 | # Check for blocks
81 | n = white_backward(i, text)
82 | if text[n - 1] == '^':
83 | return n
84 | return i
85 |
86 |
87 | def prev_pos(i, text):
88 | """Get the next position before the cursor.
89 |
90 | Returns the position where text can begin being evaluated as an
91 | Objective-C instance, method, or argument.
92 | """
93 | quote = ''
94 | brace = ''
95 | brace_skip = 0
96 | colon = text[i] == ':' and i > 0 and text[i - 1] != '\\'
97 | if colon:
98 | i -= 1
99 |
100 | while i > 0:
101 | if text[i - 1] == '\\':
102 | i -= 1
103 | continue
104 |
105 | c = text[i]
106 | if colon:
107 | # If we started at a colon, evaluate after hitting a non-word
108 | # character.
109 | if c not in word_chars:
110 | if c in whitespace_chars:
111 | i = white_backward(i + 1, text)
112 | n = white_backward_continue(i, text)
113 | if n < i:
114 | i = n
115 | continue
116 | else:
117 | i = white_backward(skip_literal_mark(i, text), text)
118 | n = white_backward_continue(i, text)
119 | if n < i:
120 | i = n
121 | continue
122 | return i + 1
123 | elif brace:
124 | if c in brace_close:
125 | b = brace_open[brace_close.index(c)]
126 | if b == brace:
127 | brace_skip += 1
128 | elif c == brace:
129 | if brace_skip:
130 | brace_skip -= 1
131 | else:
132 | brace = ''
133 | brace_skip = 0
134 | i = white_backward(skip_literal_mark(i, text), text)
135 | n = white_backward_continue(i, text)
136 | if n < i:
137 | i = n
138 | continue
139 | return i
140 | elif quote:
141 | if c == quote:
142 | quote = ''
143 | i = white_backward(skip_literal_mark(i, text), text)
144 | n = white_backward_continue(i, text)
145 | if n < i:
146 | i = n
147 | continue
148 | return i
149 | else:
150 | if c in quote_delim:
151 | quote = c
152 | elif c == ';':
153 | return i
154 | elif c in brace_open:
155 | # Hit an open brace. The caller should figure it out.
156 | return -1
157 | elif not colon and c == ':':
158 | return i + 1
159 | elif c in brace_close:
160 | brace = brace_open[brace_close.index(c)]
161 | brace_skip = 0
162 | elif c in whitespace_chars:
163 | i = white_backward(i + 1, text)
164 | n = white_backward_continue(i, text)
165 | if n < i:
166 | i = n
167 | continue
168 | return i
169 | i -= 1
170 | if i == 0:
171 | return i
172 | return -1
173 |
174 |
175 | def is_left_atom(atom):
176 | """Checks for a valid 'left side' atom in the current evaluation.
177 |
178 | Valid items are those that may have methods that can be called.
179 |
180 | Examples:
181 |
182 | - obj
183 | - [obj method]
184 | - (cast)[obj method]
185 | - @"string"
186 | - @{key: val}
187 | - @[val]
188 | """
189 | if left_re(atom):
190 | return True
191 |
192 | atom = cast_re.sub('', atom)
193 | if len(atom) > 2 and atom[0] == '@' and \
194 | ((atom[1] == '"' and atom[-1] == '"') or
195 | (atom[1] == '[' and atom[-1] == ']') or
196 | (atom[1] == '{' and atom[-1] == '}')):
197 | return True
198 | return False
199 |
200 |
201 | def valid_atoms(prev):
202 | """Validate the previous two atoms.
203 |
204 | The left atom can be an object literal, method call, or string of word
205 | characters. The name must begin with an ASCII letter. The left atom can
206 | be prefixed a type cast (or at least what looks like a type cast).
207 |
208 | The right atom must begin with an ASCII letter.
209 | """
210 | if len(prev) < 2:
211 | return False
212 |
213 | log.debug('Evaluating atoms. Left: %r, Right: %r', prev[0], prev[1])
214 |
215 | # Left
216 | if not is_left_atom(prev[0]):
217 | return False
218 |
219 | # Right
220 | if prev[1][0] not in ascii_chars:
221 | return False
222 |
223 | return True
224 |
225 |
226 | def find_boundary(text):
227 | """Finds starting position for closing an Objective-C method.
228 |
229 | It works by scanning backwards to the previous "atom". An atom is a
230 | contiguous string of printable characters, or any character enclosed in
231 | braces or quotes. For example:
232 |
233 | @"this is " stringByAppendingString:@"a string"
234 | ^[ insert ^ atom ^] start
235 |
236 | The colon in methods are only considered a delimiter when an atom
237 | boundary has already been crossed:
238 |
239 | @"this is " stringByAppendingString:@"a string" className
240 | ^ atom ^ atom ^[ insert ^ atom ^] start
241 | """
242 | i = white_backward(len(text), text)
243 |
244 | # An 'arg' is an atom that ends with a colon, while 'val' does not.
245 | # An 'arg' precedes a 'val'. If these become unbalanced, evaluate what has
246 | # been parsed.
247 | arg_count = 0
248 | val_count = 0
249 | prev = deque(maxlen=2)
250 |
251 | while i > 0:
252 | n = prev_pos(i - 1, text)
253 | if n == -1 or n == i:
254 | log.debug('At end: %r', n)
255 | if val_count > 1 and val_count > arg_count and \
256 | valid_atoms(prev):
257 | return white_forward(i, text)
258 | return -1
259 |
260 | # Encountering a semicolon ends the loop immediately.
261 | s = white_backward(n, text)
262 | hard_stop = text[s] == ';'
263 | if hard_stop:
264 | n = white_forward(n + 1, text)
265 | if val_count > 1 and val_count > arg_count and \
266 | valid_atoms(prev):
267 | log.debug('Hard stop with atoms')
268 | return white_forward(n, text)
269 | log.debug('Hard stop')
270 | return -1
271 |
272 | atom = text[n:i]
273 | atom_s = atom.strip()
274 | if not atom_s:
275 | log.debug('Empty: %r', prev)
276 | i = n
277 | continue
278 |
279 | log.debug('Current atom: %r, Arg: %d, Val: %d', atom_s, arg_count, val_count)
280 |
281 | # Break early if the current atom isn't an arg, but the previous atom
282 | # was a method call.
283 | if len(prev) == 1 and atom_s and atom_s[-1] != ':':
284 | if prev[-1][0] == '[' and prev[-1][-1] == ']':
285 | if not is_left_atom(atom_s):
286 | log.debug('Early break')
287 | return -1
288 | return white_forward(n, text)
289 |
290 | # Atoms can't be reserved keywords or operators.
291 | if atom_s in reserved or atom_s in operators:
292 | if val_count > 1 and val_count > arg_count and \
293 | valid_atoms(prev):
294 | return white_forward(i, text)
295 | prev.appendleft(atom_s)
296 | i = n
297 | log.debug('Skipping reserved or operator')
298 | continue
299 |
300 | if arg_re(atom_s):
301 | arg_count += 1
302 | else:
303 | val_count += 1
304 | log.debug('Args: %d, Vals: %d', arg_count, val_count)
305 |
306 | if val_count > 1 and val_count > arg_count:
307 | if not valid_atoms(prev):
308 | print('Not valid:', prev)
309 | prev.appendleft(atom_s)
310 | i = n
311 | continue
312 |
313 | if val_count == 2 and arg_count == 0:
314 | # If there were no arguments, use the pair from the current
315 | # position.
316 | i = n
317 |
318 | return white_forward(i, text)
319 |
320 | prev.appendleft(atom_s)
321 | i = n
322 |
323 | log.debug('Final eval')
324 | if val_count > 1 and val_count > arg_count and \
325 | valid_atoms(prev):
326 | return white_forward(i, text)
327 | return -1
328 |
--------------------------------------------------------------------------------
/rplugin/clang2/scan_test.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import logging
4 | import unittest
5 |
6 | if 'VERBOSE' in os.environ:
7 | log = logging.getLogger()
8 | log.setLevel(logging.DEBUG)
9 | handler = logging.StreamHandler(sys.stdout)
10 | handler.setLevel(logging.DEBUG)
11 | log.addHandler(handler)
12 |
13 | from . import scan
14 |
15 |
16 | class ScanTestHelper(unittest.TestCase):
17 | def assertBraceClose(self, sample, expect, msg=None):
18 | i = scan.find_boundary(sample)
19 | if i != -1:
20 | sample = sample[:i] + '[' + sample[i:] + ']'
21 | self.assertEqual(expect, sample, msg)
22 |
23 |
24 | class TestObjCScan(ScanTestHelper):
25 | # Note: Whitespace is insignificant and only matters for separating
26 | # arguments. There isn't a need to test newlines.
27 |
28 | def test_simple(self):
29 | self.assertBraceClose(
30 | 'obj method',
31 | '[obj method]',
32 | 'simple closing')
33 |
34 | self.assertBraceClose(
35 | 'obj method:arg',
36 | '[obj method:arg]',
37 | 'simple closing w/ argument')
38 |
39 | self.assertBraceClose(
40 | 'obj method:arg second:arg',
41 | '[obj method:arg second:arg]',
42 | 'simple multi-argument')
43 |
44 | self.assertBraceClose(
45 | 'obj obj method:arg second:arg',
46 | 'obj [obj method:arg second:arg]',
47 | 'simple ambiguous atoms')
48 |
49 | def test_type_cast(self):
50 | self.assertBraceClose(
51 | '(NSObject *)obj method',
52 | '[(NSObject *)obj method]',
53 | 'obj type cast')
54 |
55 | self.assertBraceClose(
56 | '(NSObject *)obj (NSObject *)method',
57 | '(NSObject *)obj (NSObject *)method',
58 | 'incorrect type cast method')
59 |
60 | self.assertBraceClose(
61 | 'obj method: (NSString *)arg',
62 | '[obj method: (NSString *)arg]',
63 | 'type cast arg')
64 |
65 | self.assertBraceClose(
66 | '(NSObject *)obj method: (NSString *)arg',
67 | '[(NSObject *)obj method: (NSString *)arg]',
68 | 'obj type cast and type cast arg')
69 |
70 | self.assertBraceClose(
71 | 'obj method: (void *)(NSString *)arg',
72 | '[obj method: (void *)(NSString *)arg]',
73 | 'double type cast arg')
74 |
75 | self.assertBraceClose(
76 | '(NSString *)[NSString alloc] init',
77 | '[(NSString *)[NSString alloc] init]',
78 | 'type cast method call')
79 |
80 | self.assertBraceClose(
81 | '(id)@"text" method:arg method:arg',
82 | '[(id)@"text" method:arg method:arg]',
83 | 'literal type cast')
84 |
85 | def test_assignment(self):
86 | self.assertBraceClose(
87 | 'NSString s = [NSString alloc] init',
88 | 'NSString s = [[NSString alloc] init]',
89 | 'simple assignment')
90 |
91 | self.assertBraceClose(
92 | 'NSString s = [[NSString alloc] init]',
93 | 'NSString s = [[NSString alloc] init]',
94 | 'left side of assignment not enclosed')
95 |
96 | def test_complicated(self):
97 | # Note: Detecting nested unclosed methods is not practical. But, a
98 | # value followed by another value can be seen as not being an argument
99 | # for the parent method.
100 | self.assertBraceClose(
101 | '(id)@"str" arg1:(void *)val1 arg2:val2 val2arg',
102 | '(id)@"str" arg1:(void *)val1 arg2:[val2 val2arg]',
103 | 'trailing value')
104 |
105 | self.assertBraceClose(
106 | '(id)@"str" arg1:(void *)val1 arg2:[val2 val2arg]',
107 | '[(id)@"str" arg1:(void *)val1 arg2:[val2 val2arg]]',
108 | 'trailing method')
109 |
110 | self.assertBraceClose(
111 | '(id)@"str" arg1:(void *)val1 arg2:[val2 val2arg] val2argarg',
112 | '(id)@"str" arg1:(void *)val1 arg2:[[val2 val2arg] val2argarg]',
113 | 'double trailing method')
114 |
115 | def test_semicolon(self):
116 | self.assertBraceClose(
117 | 'obj1 obj2 method:arg second:arg;',
118 | 'obj1 obj2 method:arg second:arg;',
119 | 'simple semicolon stop at end')
120 |
121 | self.assertBraceClose(
122 | 'obj1 obj2 method:arg; second:arg',
123 | 'obj1 obj2 method:arg; second:arg',
124 | 'simple semicolon stop before last arg')
125 |
126 | self.assertBraceClose(
127 | 'obj1 obj2; method:arg second:arg',
128 | 'obj1 obj2; method:arg second:arg',
129 | 'simple semicolon stop before first arg')
130 |
131 | self.assertBraceClose(
132 | 'obj1; obj2 method:arg second:arg',
133 | 'obj1; [obj2 method:arg second:arg]',
134 | 'simple semicolon stop before second obj')
135 |
136 |
137 | if __name__ == "__main__":
138 | unittest.main()
139 |
--------------------------------------------------------------------------------
/rplugin/python3/deoplete/sources/deoplete_clang2.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import sys
4 | import glob
5 | import json
6 | import time
7 | import shlex
8 | import hashlib
9 | import tarfile
10 | import tempfile
11 | import threading
12 | import subprocess
13 |
14 | from itertools import chain
15 | from urllib.request import urlopen
16 |
17 | from .base import Base
18 |
19 | repo_dirs = set(['.git', '.hg', '.svn'])
20 | flag_pattern = ''.join(r""""
21 | -resource-dir|
22 | (?:
23 | (?:-(?:internal|externc|c|cxx|objc))*?
24 | -i(?:
25 | (?:nclude-(?:p[ct]h))|
26 | quote|prefix|sysroot|system|framework|dirafter|macros|withprefix|
27 | withprefixbefore|withsysroot
28 | )
29 | )
30 | """.split())
31 |
32 | std_default = {
33 | 'c': 'c11',
34 | 'cpp': 'c++1z',
35 | 'objc': 'c11',
36 | 'objcpp': 'c++1z',
37 | }
38 |
39 | lang_names = {
40 | 'c': 'c',
41 | 'cpp': 'c++',
42 | 'objc': 'objective-c',
43 | 'objcpp': 'objective-c++',
44 | }
45 |
46 | pch_cache = os.path.join(tempfile.gettempdir(), 'deoplete-clang2')
47 |
48 | if sys.platform == 'darwin':
49 | darwin_sdk_dl_path = os.path.expanduser('~/Library/Developer/Frameworks')
50 | darwin_sdk_paths = (
51 | '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs',
52 | '/Developer/SDKs',
53 | darwin_sdk_dl_path,
54 | )
55 | else:
56 | darwin_sdk_dl_path = os.path.join(os.getenv('XDG_DATA_HOME', '~/.local/share'), 'SDKs')
57 | darwin_sdk_paths = (darwin_sdk_dl_path,)
58 |
59 | darwin_download = threading.Lock()
60 | darwin_versions = {
61 | '10.1': ('10.1.5', 1010),
62 | '10.1.5': ('10.1.5', 1010),
63 | '10.2': ('10.2.8', 1020),
64 | '10.2.8': ('10.2.8', 1020),
65 | '10.3': ('10.3.9', 1030),
66 | '10.3.0': ('10.3.0', 1030),
67 | '10.3.9': ('10.3.9', 1030),
68 | '10.4': ('10.4u', 1040),
69 | '10.5': ('10.5', 1050),
70 | '10.6': ('10.6', 1060),
71 | '10.7': ('10.7', 1070),
72 | '10.8': ('10.8', 1080),
73 | '10.9': ('10.9', 1090),
74 | '10.10': ('10.10', 100000),
75 | '10.11': ('10.11', 101100),
76 | }
77 |
78 | darwin_sdk_url = 'https://github.com/phracker/MacOSX-SDKs/releases/download/MacOSX10.11.sdk/MacOSX%s.sdk.tar.xz'
79 |
80 |
81 | def dl_progress(nvim, msg):
82 | nvim.async_call(lambda n, m: n.eval('clang2#status(%r)' % m),
83 | nvim, msg)
84 |
85 |
86 | def download_sdk(nvim, version):
87 | with darwin_download:
88 | dest = tempfile.NamedTemporaryFile('wb', delete=False)
89 | chunk = 16 * 1024
90 | try:
91 | r = urlopen(darwin_sdk_url % version)
92 | last = time.time()
93 | dl_bytes = 0
94 | while True:
95 | buf = r.read(chunk)
96 | if not buf:
97 | break
98 | dest.write(buf)
99 | dl_bytes += len(buf)
100 |
101 | if time.time() - last >= 1:
102 | dl_progress(nvim,
103 | 'Downloading MacOSX%s.sdk (%d bytes)'
104 | % (version, dl_bytes))
105 | last = time.time()
106 |
107 | dl_progress(nvim,
108 | 'Extracting MacOSX%s.sdk to %s'
109 | % (version, darwin_sdk_dl_path))
110 | dest.close()
111 |
112 | with tarfile.open(dest.name) as fp:
113 | fp.extractall(darwin_sdk_dl_path)
114 | except Exception as e:
115 | dl_progress('Error downloading SDK: %s' % e)
116 | finally:
117 | dest.remove()
118 |
119 |
120 | class Source(Base):
121 | def __init__(self, nvim):
122 | super(Source, self).__init__(nvim)
123 | self.clang_path = ''
124 | self.min_pattern_length = 0
125 | self.nvim = nvim
126 | self.name = 'clang2'
127 | self.mark = '[clang]'
128 | self.rank = 500
129 | self.bad_flags = []
130 | self.clang_flags = {}
131 | self.pch_flags = {}
132 | self.filetypes = ['c', 'cpp', 'objc', 'objcpp']
133 | self.db_files = {}
134 | self.last = {}
135 | self.scope_completions = []
136 | self.user_flags = {}
137 | self.sys_includes = {}
138 | self.darwin_version = 0
139 | self.last_neomake_flags = set()
140 |
141 | def on_event(self, context, filename=''):
142 | if context['event'] == 'BufWritePost':
143 | self.find_db_flags(context)
144 | self.last = {}
145 | self.scope_completions = []
146 |
147 | self.clang_path = context['vars'].get(
148 | 'deoplete#sources#clang#executable', 'clang')
149 |
150 | if context['vars'].get('deoplete#sources#clang#autofill_neomake', 1):
151 | cmd, flags = self.build_flags(context)
152 | if context['filetype'] == 'cpp':
153 | cmd += ['-stdlib=libc++']
154 | neomake_flags = set(cmd + flags)
155 | if self.last_neomake_flags ^ neomake_flags:
156 | self.last_neomake_flags = neomake_flags
157 | self.nvim.async_call(lambda n, m:
158 | n.eval('clang2#set_neomake_cflags(%r)' % m),
159 | self.nvim, cmd + flags)
160 |
161 | def get_complete_position(self, context):
162 | m = re.search(r'^\s*#(?:include|import)\s+["<]', context['input'])
163 | if m:
164 | context['clang2_include'] = m.group(0)
165 | return m.end()
166 |
167 | pat = r'->|\.'
168 | ft = context.get('filetype', '')
169 | objc = ft in ('objc', 'objcpp')
170 | if objc:
171 | pat += r'|[:@\[]|(?:\S\s+)'
172 | elif ft == 'cpp':
173 | pat += r'|::'
174 | pat = r'(?:' + pat + ')'
175 | context['clang2_pattern'] = pat
176 |
177 | m = re.search(r'(' + pat + r')(\w*?)$', context['input'])
178 | if m is not None:
179 | if objc and m.group(1) == '@':
180 | return m.start(1) - 1
181 | return m.start(2)
182 | return re.search(r'\w*$', context['input']).start()
183 |
184 | def find_file(self, path, filename):
185 | for root, dirs, files in os.walk(path):
186 | dirs[:] = [d for d in dirs if d not in repo_dirs]
187 | for file in files:
188 | if file == filename:
189 | return os.path.join(root, file)
190 | return None
191 |
192 | def clean_flags(self, flags):
193 | if isinstance(flags, str):
194 | flags = shlex.split(flags, comments=True, posix=True)
195 |
196 | home = os.path.expanduser('~')
197 | out = []
198 | for i, flag in enumerate(flags):
199 | flag = os.path.expandvars(flag)
200 | if flag.startswith('-darwin='):
201 | self.darwin_version = darwin_versions.get(flag.split('=', 1)[1])
202 | continue
203 |
204 | if flag.startswith('~') or \
205 | ('~' in flag and
206 | re.match(r'(-[IDF]|' + flag_pattern + r')', flag)):
207 | flag = flag.replace('~', home)
208 | out.append(flag)
209 |
210 | self.debug('cleaned flags: %r', out)
211 | return out
212 |
213 | def get_clang_flags(self, lang):
214 | """Get the default flags used by clang.
215 |
216 | XXX: Not exactly sure why, but using -fsyntax-only causes clang to not
217 | search the default include paths. Maybe my setup is busted?
218 | """
219 | if lang in self.clang_flags:
220 | return self.clang_flags[lang]
221 | flags = []
222 | clang_flags = ['-fsyntax-only', '-x', lang]
223 | if lang == 'c++':
224 | clang_flags.append('-stdlib=libc++')
225 | clang_flags.extend(['-', '-v'])
226 | stdout = self.call_clang([], clang_flags, True)
227 |
228 | for item in re.finditer(r'(' + flag_pattern + ')\s*(\S+)',
229 | ' '.join(stdout)):
230 | flag = item.group(1)
231 | val = item.group(2)
232 | if not os.path.exists(val):
233 | continue
234 |
235 | f = '%s%s' % (flag, val)
236 | if f not in flags:
237 | flags.append(f)
238 | self.clang_flags[lang] = self.clean_flags(flags)
239 | return self.clang_flags[lang]
240 |
241 | def parse_clang_flags_file(self, filename):
242 | """Parse a .clang file."""
243 | try:
244 | with open(filename, 'rt') as fp:
245 | return self.clean_flags(fp.read())
246 | except IOError:
247 | return []
248 |
249 | def get_user_flags(self, context):
250 | """Get flags from the .clang file or return flags from Vim."""
251 | cwd = context['cwd']
252 | flags = context['vars'].get('deoplete#sources#clang#flags', [])
253 |
254 | if cwd in self.user_flags:
255 | if self.user_flags[cwd]:
256 | return self.parse_clang_flags_file(self.user_flags[cwd])
257 | return self.clean_flags(flags)
258 |
259 | parent = cwd
260 | last_parent = ''
261 | while parent != last_parent:
262 | file = os.path.join(parent, '.clang')
263 | if os.path.isfile(file):
264 | self.user_flags[cwd] = file
265 | return self.parse_clang_flags_file(file)
266 |
267 | if set(os.listdir(parent)).isdisjoint(repo_dirs):
268 | # We're at an apparent project root.
269 | break
270 |
271 | last_parent = parent
272 | parent = os.path.dirname(parent)
273 |
274 | self.user_flags[cwd] = None
275 | return self.clean_flags(flags)
276 |
277 | def call_clang(self, src, cmd, ret_stderr=False, cwd=None):
278 | """Call clang to do something.
279 |
280 | This will call clang up to 5 times if it reports that unknown arguments
281 | are used. On each pass, the offending arguments are removed and
282 | rembmered.
283 |
284 | In my opinion, this is acceptable since we aren't interested in
285 | compiling a working binary. This would allow you to copy and paste
286 | your existing compile flags (for the most part) without needing to
287 | worry about what does or doesn't work for getting completion.
288 | """
289 | stdout = ''
290 | stderr = ''
291 | retry = 5
292 |
293 | cmd = [self.clang_path] + [arg for arg in cmd
294 | if arg not in self.bad_flags]
295 |
296 | # Retry a couple times if 'unknown argument' is encountered because I
297 | # just want completions and I don't care how they're obtained as long
298 | # as its useful.
299 | while retry > 0:
300 | self.debug('Command: %r', ' '.join(cmd))
301 | p = subprocess.Popen(cmd, stdin=subprocess.PIPE,
302 | stdout=subprocess.PIPE,
303 | stderr=subprocess.PIPE, cwd=cwd)
304 | source = '\n'.join(src).encode('utf8')
305 | if len(src) and source[-1] != b'\n':
306 | source += b'\n'
307 | out, err = p.communicate(source)
308 | stderr = err.decode('utf8')
309 | if 'unknown argument' in stderr:
310 | for bad in re.finditer(r"unknown argument: '([^']+)'", stderr):
311 | arg = bad.group(1)
312 | self.debug('Removing argument: %r', arg)
313 | if arg not in self.bad_flags:
314 | self.bad_flags.append(arg)
315 | if arg not in cmd:
316 | break
317 | while cmd.count(arg):
318 | cmd.remove(arg)
319 | retry -= 1
320 | continue
321 | elif self.is_debug_enabled and stderr.strip():
322 | # This can be really spammy.
323 | self.debug('stderr: %s', stderr)
324 |
325 | stdout = out.decode('utf8')
326 | break
327 |
328 | if ret_stderr:
329 | return stderr.split('\n')
330 | return stdout.split('\n')
331 |
332 | def find_db_flags(self, context):
333 | """Find the compile_commands.json file."""
334 | cwd = context.get('cwd', '')
335 | bufname = context.get('bufname', '')
336 | absname, _ = os.path.splitext(os.path.join(cwd, bufname))
337 |
338 | if cwd in self.db_files:
339 | return self.db_files[cwd]['entries'].get(absname)
340 |
341 | db_file = self.find_file(cwd, 'compile_commands.json')
342 | if db_file is None:
343 | return None
344 |
345 | entries = {}
346 |
347 | with open(db_file, 'rt') as fp:
348 | try:
349 | flags = []
350 | for entry in json.load(fp):
351 | directory = entry.get('directory', cwd)
352 | file, ext = os.path.splitext(entry.get('file', ''))
353 | for item in re.finditer(r'(-[IDFW]|' + flag_pattern +
354 | ')\s*(\S+)', entry.get('command')):
355 | flag = item.group(1)
356 | val = item.group(2)
357 |
358 | if flag not in ('-D', '-W'):
359 | val = os.path.normpath(os.path.join(directory, val))
360 |
361 | f = flag + val
362 | if f not in flags:
363 | flags.append(f)
364 | # The db is assumed to be as good as it gets. Don't pass
365 | # it through clean_flags.
366 | entries[file] = flags
367 | except:
368 | raise
369 |
370 | self.db_files[cwd] = {
371 | 'file': db_file,
372 | 'entries': entries,
373 | }
374 |
375 | return entries.get(absname)
376 |
377 | def generate_pch(self, context, cmd, flags):
378 | """Find a *-Prefix.pch file and generate the pre-compiled header.
379 |
380 | It is written to the temp directory and is cached according to the
381 | flags used to generate it.
382 | """
383 | cwd = context.get('cwd', '')
384 |
385 | if not os.path.exists(pch_cache):
386 | os.makedirs(pch_cache)
387 |
388 | hflags = hashlib.sha1()
389 | for f in chain(cmd, flags):
390 | hflags.update(f.encode('utf8'))
391 |
392 | if not cwd:
393 | return cmd, flags
394 |
395 | for pch in glob.glob(os.path.join(cwd, '*-Prefix.pch')):
396 | h = hflags.copy()
397 | h.update(pch.encode('utf8'))
398 | name = '%s.%s.h' % (os.path.basename(pch), h.hexdigest())
399 | generated = os.path.join(pch_cache, name)
400 |
401 | if not os.path.exists(generated) or \
402 | os.path.getmtime(pch) > os.path.getmtime(generated):
403 | self.call_clang([], ['-cc1'] + cmd + flags +
404 | [pch, '-emit-pch', '-o', generated])
405 | if os.path.exists(generated):
406 | cmd += ['-include-pch', generated]
407 | break
408 |
409 | return cmd, flags
410 |
411 | def parse_completion(self, item):
412 | name = item
413 | ctype = ''
414 | info = ''
415 | if ' : ' in item:
416 | name, comp = name.split(' : ', 1)
417 | else:
418 | comp = name
419 |
420 | if name == 'Pattern':
421 | if comp.startswith('[#'):
422 | i = comp.find('#]')
423 | if i != -1:
424 | ctype = comp[2:i]
425 | comp = comp[i+2:]
426 | comp = re.sub(r'(\S)\(', r'\1 (', re.sub(r'\)(\S)', r') \1', comp))
427 | name = comp
428 | info = re.sub(r'<#([^#]+)#>', r'\1', comp)
429 | else:
430 | if comp.startswith('[#'):
431 | i = comp.find('#]')
432 | if i != -1:
433 | ctype = comp[2:i]
434 | comp = comp[i+2:]
435 |
436 | if comp.endswith('#]'):
437 | i = comp.rfind('[#')
438 | if i != -1:
439 | comp = comp[:i]
440 |
441 | if comp.endswith(')'):
442 | info = re.sub(r'<#([^#]+)#>', r'\1', comp)
443 |
444 | if ctype != '':
445 | info = '(%s)%s' % (ctype, info)
446 |
447 | return {
448 | 'word': comp,
449 | 'abbr': name,
450 | 'kind': ctype,
451 | 'info': info,
452 | 'icase': 1,
453 | 'dup': 1,
454 | }
455 |
456 | def apply_darwin_flags(self, version, flags):
457 | sdk_name = 'MacOSX%s.sdk' % version[0]
458 | sdk_path = ''
459 | for p in darwin_sdk_paths:
460 | p = os.path.join(p, sdk_name)
461 | if os.path.isdir(p):
462 | sdk_path = p
463 | break
464 |
465 | if not sdk_path:
466 | if darwin_download.acquire(False):
467 | darwin_download.release()
468 | t = threading.Thread(target=download_sdk, args=(self.nvim, version[0]))
469 | t.daemon = True
470 | t.start()
471 | return flags
472 |
473 | return flags + [
474 | '-D__MACH__',
475 | '-D__MAC_OS_X_VERSION_MAX_ALLOWED=%d' % version[1],
476 | '-D__APPLE_CPP__',
477 | '-DTARGET_CPU_X86_64',
478 | '-fblocks',
479 | '-fasm-blocks',
480 | '-fno-builtin',
481 | '-isysroot%s' % sdk_path,
482 | '-iframework%s' % os.path.join(sdk_path, 'System/Library/Frameworks'),
483 | '-isystem%s' % os.path.join(sdk_path, 'usr/include'),
484 | ]
485 |
486 | def build_flags(self, context):
487 | filetype = context.get('filetype', '')
488 | cmd = ['-x']
489 | lang = lang_names.get(filetype)
490 | if not lang:
491 | return []
492 | std = context['vars'].get(
493 | 'deoplete#sources#clang#std', {}).get(filetype)
494 | if not std:
495 | std = std_default.get(filetype)
496 |
497 | cmd = ['-x', lang]
498 | std = '-std=%s' % std
499 |
500 | flags = self.get_user_flags(context)
501 |
502 | if self.darwin_version:
503 | flags = self.apply_darwin_flags(self.darwin_version, flags)
504 |
505 | flags = self.get_clang_flags(lang) + flags
506 | db_flags = self.find_db_flags(context)
507 | if db_flags:
508 | flags = db_flags + flags
509 |
510 | if not any(True for x in flags if x.startswith('-std=')):
511 | cmd.append(std)
512 |
513 | return cmd, flags
514 |
515 | def _gather_sys_includes(self, flag, path, seen):
516 | """#include <> files from compiler include flags."""
517 | if path in self.sys_includes:
518 | yield from filter(lambda x: x['word'] not in seen,
519 | self.sys_includes[path])
520 | return
521 |
522 | headers = []
523 |
524 | if flag == '-F' or 'framework' in flag:
525 | for framework in os.listdir(path):
526 | name = framework
527 | if name.endswith('.framework'):
528 | name = name[:-10]
529 | fw_path = os.path.join(path, framework, 'Headers') + '/'
530 | if not os.path.exists(fw_path):
531 | continue
532 |
533 | for root, dirs, files in os.walk(fw_path):
534 | dirs[:] = list(sorted([d for d in dirs if d not in repo_dirs]))
535 | for file in sorted(files):
536 | if not file.endswith('.h'):
537 | continue
538 | full_path = os.path.join(root, file)
539 | include_path = os.path.join(name,
540 | full_path[len(fw_path):])
541 | item = {'word': include_path, 'kind': 'sys'}
542 | headers.append(item)
543 | if include_path not in seen:
544 | seen.add(include_path)
545 | yield item
546 | else:
547 | for root, dirs, files in os.walk(path):
548 | dirs[:] = list(sorted([d for d in dirs if d not in repo_dirs]))
549 |
550 | for file in sorted(files):
551 | if not file.endswith('.h'):
552 | continue
553 | include_path = os.path.join(root, file)[len(path)+1:]
554 | item = {'word': include_path, 'kind': 'sys'}
555 | headers.append(item)
556 | if include_path not in seen:
557 | seen.add(include_path)
558 | yield item
559 |
560 | self.sys_includes[path] = headers
561 |
562 | def _gather_local_includes(self, context, seen):
563 | """#include "" files relative to the current buffer."""
564 | path = os.path.dirname(context.get('bufname', ''))
565 | l = len(path)
566 | for root, dirs, files in os.walk(path):
567 | dirs[:] = list(sorted([d for d in dirs if d not in repo_dirs]))
568 | for file in sorted(files):
569 | if not file.endswith('.h'):
570 | continue
571 | include_path = os.path.join(root, file)[l:].lstrip('/')
572 | seen.add(include_path)
573 | yield {'word': include_path, 'kind': 'dir'}
574 |
575 | def gather_includes(self, context):
576 | seen = set()
577 |
578 | # Exclude already imported files
579 | src = '\n'.join(self.nvim.current.buffer[:5000])
580 | seen.update(x[1:-1] for x in re.findall(
581 | r'^\s*#(?:include|import) ((?:"[^"]+")|(?:<[^>]+>))$', src, re.M))
582 |
583 | if context['clang2_include'][-1] == '"':
584 | yield from self._gather_local_includes(context, seen)
585 |
586 | _, flags = self.build_flags(context)
587 | for item in re.finditer(r'(-[IF]|' + flag_pattern +
588 | ')\s*(\S+)', ' '.join(flags)):
589 | yield from self._gather_sys_includes(item.group(1), item.group(2),
590 | seen)
591 |
592 | def gather_candidates(self, context):
593 | self.darwin_version = 0
594 |
595 | if 'clang2_include' in context:
596 | return list(self.gather_includes(context))
597 |
598 | input = context['input']
599 | filetype = context.get('filetype', '')
600 | complete_str = context['complete_str']
601 | min_length = context['vars'].get(
602 | 'deoplete#auto_complete_start_length', 2)
603 | pattern = context.get('clang2_pattern')
604 | length_exemption = pattern and re.search(pattern + r'$', input)
605 |
606 | if not length_exemption and len(complete_str) < min_length:
607 | # Since the source doesn't have a global pattern, its our
608 | # responsibility to honor the user settings.
609 | return []
610 |
611 | pos = context['complete_position']
612 | line = context['position'][1]
613 | last_input = self.last.get('input', '')
614 | same_line = self.last.get('line', 0) == line
615 |
616 | # Completions from clang will include all results that are relevant to
617 | # a delimiter position--not just the current word completion. This
618 | # means the results can be reused to drastically reduce the completion
619 | # time.
620 | if same_line and self.last.get('col', 0) == pos:
621 | self.debug('Reusing previous completions')
622 | return self.last.get('completions', [])
623 |
624 | # Additionally, if the completion is happeing in a position that will
625 | # result in completions for the current scope, reuse it.
626 | scope_pos = re.search(r'(?:\s+|(?:[\[\(:])\s*|@)$', input)
627 |
628 | # Check objc where spaces can be significant.
629 | scope_reuse = (filetype not in ('objc', 'objcpp') or
630 | (input != last_input and
631 | input.rstrip() == last_input.rstrip()))
632 |
633 | if scope_reuse and same_line and scope_pos and self.scope_completions:
634 | self.debug('Reusing scope completions')
635 | return self.scope_completions
636 |
637 | buf = self.nvim.current.buffer
638 | src = buf[:]
639 | max_lines = context['vars'].get(
640 | 'deoplete#sources#clang#preproc_max_lines', 50)
641 | if max_lines:
642 | for i, l in enumerate(reversed(src[max(0, line-max_lines):line])):
643 | l = l.lstrip()
644 | if l.startswith('#'):
645 | l = l.lstrip('# ')
646 | if l.startswith('ifdef'):
647 | self.debug('Ignoring preproc line %d', line - i)
648 | src[line - i - 1] = ''
649 | break
650 | elif l.startswith('endif'):
651 | self.debug('Stopped preproc search on line %d',
652 | line - i)
653 | break
654 |
655 | code_flags = [
656 | '-code-completion-macros',
657 | '-code-completion-patterns',
658 | # '-code-completion-brief-comments', - Not very useful atm.
659 | '-code-completion-at=-:%d:%d' % (line, pos+1),
660 | ]
661 |
662 | cmd, flags = self.build_flags(context)
663 | cmd, flags = self.generate_pch(context, cmd, flags)
664 |
665 | completions = []
666 | cmd = (['-cc1', '-fsyntax-only'] + cmd +
667 | code_flags + flags + ['-O0', '-w'] + ['-'])
668 |
669 | pattern = ''
670 |
671 | for item in self.call_clang(src, cmd, cwd=os.path.dirname(buf.name)):
672 | if item.startswith('COMPLETION:'):
673 | if pattern:
674 | completions.append(self.parse_completion(pattern))
675 | pattern = ''
676 |
677 | item = item[11:].strip()
678 | if item.startswith('Pattern :'):
679 | pattern = item
680 | continue
681 | completions.append(self.parse_completion(item))
682 | elif pattern:
683 | pattern += item
684 |
685 | if pattern:
686 | completions.append(self.parse_completion(pattern))
687 |
688 | self.last = {
689 | 'input': input,
690 | 'line': line,
691 | 'col': pos,
692 | 'completions': completions,
693 | }
694 |
695 | if scope_pos:
696 | self.scope_completions = completions
697 |
698 | return completions
699 |
--------------------------------------------------------------------------------