├── .gitignore
├── ClangComplete.sublime-settings
├── Default.sublime-commands
├── Default.sublime-keymap
├── ErrorPanel.tmLanguage
├── Main.sublime-menu
├── README.md
├── clangcomplete.py
└── complete
├── Makefile
├── complete.cpp
├── complete.h
└── complete.py
/.gitignore:
--------------------------------------------------------------------------------
1 | complete/*.o
2 | complete/*.so
3 | complete/*.dylib
--------------------------------------------------------------------------------
/ClangComplete.sublime-settings:
--------------------------------------------------------------------------------
1 | {
2 | // Additional compiler options that are always added to the completion
3 | // parser
4 | "additional_options": ["-Wno-c++11-narrowing", "-D__STRICT_ANSI__", "-DQT_NO_DEBUG", "-isystem", "/usr/lib/llvm-3.8/lib/clang/3.8.0/include"],
5 | // Options that will be excluded
6 | "exclude_options": ["-W*unused-but-set-variable", "-W*maybe-uninitialized", "-W*logical-op"],
7 | // The cmake build directory where ClangCompletion will look for the
8 | // compiler flags
9 | "build_dir": ["build"],
10 | // If a valid build directory cannot be found then the default options
11 | // will be used instead
12 | "default_options": ["-std=c++11"],
13 | // Additional paths that are searched when doing completion for include
14 | // directives. Generally, it should always be the default include paths
15 | // set by the compiler. To see the default search paths for the compiler
16 | // type `echo | `gcc -print-prog-name=cc1plus` -v -E` into the command
17 | // line.
18 | "default_include_paths":
19 | [
20 | "/usr/include/c++/4.9",
21 | "/usr/include/x86_64-linux-gnu/c++/4.9",
22 | "/usr/include/c++/4.9/backward",
23 | "/usr/lib/gcc/x86_64-linux-gnu/4.9/include",
24 | "/usr/local/include",
25 | "/usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed",
26 | "/usr/include/x86_64-linux-gnu",
27 | "/usr/include"
28 | ],
29 | // How long ClangComplete will wait for completions. This helps prevent
30 | // clang from blocking the gui thread.
31 | "timeout": 200,
32 | // Suppress sublime's completion suggestions
33 | "inhibit_sublime_completions": true,
34 | // Show clang diagnostics on save: always, no_build, or never
35 | "show_diagnostics_on_save": "no_build"
36 | }
--------------------------------------------------------------------------------
/Default.sublime-commands:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences: ClangComplete Settings – Default",
4 | "command": "open_file", "args":
5 | {
6 | "file": "${packages}/ClangComplete/ClangComplete.sublime-settings"
7 | }
8 | },
9 | {
10 | "caption": "Preferences: ClangComplete Settings – User",
11 | "command": "open_file", "args":
12 | {
13 | "file": "${packages}/User/ClangComplete.sublime-settings"
14 | }
15 | },
16 | {
17 | "caption": "ClangComplete: Clear cache",
18 | "command": "clang_clear_cache"
19 | }
20 | ]
--------------------------------------------------------------------------------
/Default.sublime-keymap:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "command": "clang_complete_goto_def",
4 | "keys": ["alt+d", "alt+d"]
5 | },
6 | {
7 | "command": "clang_complete_clear_cache",
8 | "keys": ["alt+d", "alt+c"]
9 | },
10 | {
11 | "command": "clang_complete_show_type",
12 | "keys": ["alt+d", "alt+t"]
13 | },
14 | {
15 | "command": "clang_complete_find_uses",
16 | "keys": ["alt+d", "alt+u"]
17 | },
18 | {
19 | "command": "clang_complete_complete",
20 | "args": {"characters": "."},
21 | "keys": ["."],
22 | "context": [ {"key": "clangcomplete_supported_language"}, {"key": "clangcomplete_is_code"}]
23 | },
24 | {
25 | "command": "clang_complete_complete",
26 | "args": {"characters": ":"},
27 | "keys": [":"],
28 | "context": [ {"key": "clangcomplete_supported_language"}, {"key": "clangcomplete_is_code"}]
29 | },
30 | {
31 | "command": "clang_complete_complete",
32 | "args": {"characters": ">"},
33 | "keys": [">"],
34 | "context": [ {"key": "clangcomplete_supported_language"}, {"key": "clangcomplete_is_code"}]
35 | },
36 | {
37 | "command": "clang_complete_toggle_panel",
38 | "keys": ["escape"],
39 | "context": [{"key": "clang_panel_visible"}]
40 | }
41 | ]
--------------------------------------------------------------------------------
/ErrorPanel.tmLanguage:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | fileTypes
5 |
6 |
7 | name
8 | ClangCompleteErrorPanel
9 | patterns
10 |
11 |
12 | comment
13 | Warnings
14 | match
15 | ^.* warning: .*$
16 | name
17 | string.quoted.double.c
18 |
19 |
20 | comment
21 | Errors
22 | match
23 | ^.* error: .*$
24 | name
25 | keyword.control.c
26 |
27 |
28 | scopeName
29 | text.clangcomplete
30 | uuid
31 | f91ba7f0-d070-11e2-8b8b-0800200c9a66
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Main.sublime-menu:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "caption": "Preferences",
4 | "mnemonic": "n",
5 | "id": "preferences",
6 | "children":
7 | [
8 | {
9 | "caption": "ClangComplete",
10 | "children":
11 | [
12 | {
13 | "command": "open_file", "args":
14 | {
15 | "file": "${packages}/ClangComplete/ClangComplete.sublime-settings"
16 | },
17 | "caption": "Settings – Default"
18 | },
19 | {
20 | "command": "open_file", "args":
21 | {
22 | "file": "${packages}/User/ClangComplete.sublime-settings"
23 | },
24 | "caption": "Settings – User"
25 | },
26 | { "caption": "-" },
27 | {
28 | "command": "open_file", "args":
29 | {
30 | "file": "${packages}/ClangComplete/Default.sublime-keymap"
31 | },
32 | "caption": "Key Bindings – Default"
33 | },
34 | {
35 | "command": "open_file", "args":
36 | {
37 | "file": "${packages}/User/Default (OSX).sublime-keymap",
38 | "platform": "OSX"
39 | },
40 | "caption": "Key Bindings – User"
41 | },
42 | {
43 | "command": "open_file", "args":
44 | {
45 | "file": "${packages}/User/Default (Linux).sublime-keymap",
46 | "platform": "Linux"
47 | },
48 | "caption": "Key Bindings – User"
49 | },
50 | {
51 | "command": "open_file",
52 | "args": {
53 | "file": "${packages}/User/Default (Windows).sublime-keymap",
54 | "platform": "Windows"
55 | },
56 | "caption": "Key Bindings – User"
57 | }
58 | ]
59 | }
60 | ]
61 | }
62 | ]
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ClangComplete
2 | =============
3 |
4 | Description
5 | -----------
6 |
7 | Clang completion for Sublime Text 3. Additionally, it provides diagnostics and some simple navigation capabilites.
8 |
9 | Installation
10 | ------------
11 |
12 | First, clone this repo into your sublime packages folder(it doesn't use Package Control). Then cd into the `complete` directory and type:
13 |
14 | make
15 |
16 | This will build the `complete.so` binary. It requires the development version of Clang to build(the package `libclang-dev` on debian-based distros). To get the appropriate development package on OS X, install LLVM via Homebrew:
17 |
18 | brew install --with-clang --with-all-targets --with-rtti --universal --jit llvm
19 |
20 | Usage
21 | -----
22 |
23 | ClangComplete provides code completion for C, C++, and Objective-C files. To figure out the compiler flags needed to parse the file, ClangComplete looks into the `build` directory in the project folder for the cmake build settings. If the build directory is placed somewhere else the `build_dir` can be set to the actual build directory. Also if cmake is not used, options can be manually set by setting the `default_options` setting.
24 |
25 | ClangComplete also shows diagnostics whenever a file is saved, and provides `Goto Definition` functionality. Here are the default shortcuts for ClangComplete:
26 |
27 | | Key | Action |
28 | |--------------|------------------|
29 | | alt+d, alt+d | Go to definition |
30 | | alt+d, alt+c | Clear cache |
31 | | alt+d, alt+t | Show type |
32 |
33 | Support
34 | -------
35 |
36 | [Donate](https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=HMB5AGA7DQ9NS&lc=US&item_name=Donation%20to%20clang%20complete&button_subtype=services¤cy_code=USD&bn=PP%2dBuyNowBF%3abtn_paynow_LG%2egif%3aNonHosted)
37 |
38 |
39 |
--------------------------------------------------------------------------------
/clangcomplete.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 | # You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2013, Paul Fultz II
6 |
7 | import sublime, sublime_plugin
8 |
9 | from threading import Timer, Lock
10 | from .complete.complete import find_uses, get_completions, get_diagnostics, get_definition, get_type, reparse, free_tu, free_all
11 | import os, re, sys, bisect, json, fnmatch, functools
12 |
13 | def get_settings():
14 | return sublime.load_settings("ClangComplete.sublime-settings")
15 |
16 | def get_setting(view, key, default=None):
17 | s = view.settings()
18 | if s.has("clangcomplete_%s" % key):
19 | return s.get("clangcomplete_%s" % key)
20 | return get_settings().get(key, default)
21 |
22 | def get_project_path(view):
23 | try:
24 | return view.window().folders()[0]
25 | except:
26 | pass
27 | return ""
28 |
29 |
30 | def get_unsaved_buffer(view):
31 | buffer = None
32 | if view.is_dirty():
33 | buffer = view.substr(sublime.Region(0, view.size()))
34 | return buffer
35 |
36 | def debug_print(*args):
37 | if get_settings().get("debug", False): print(*args)
38 |
39 | #
40 | #
41 | # Retrieve options from cmake
42 | #
43 | #
44 | def parse_flags(f):
45 | flags = []
46 | for line in open(f).readlines():
47 | if line.startswith(('CXX_FLAGS', 'CXX_DEFINES', 'CXX_INCLUDES', 'C_FLAGS', 'C_DEFINES', 'C_INCLUDES')):
48 | words = line[line.index('=')+1:].split()
49 | flags.extend(words)
50 | return flags
51 |
52 | def canonicalize_path(path, root):
53 | if path.startswith('-I'): return '-I'+os.path.normpath(os.path.join(root, path[2:])) # rel or abs path
54 | else: return path
55 |
56 | def parse_compile_commands(root, f):
57 | flags = []
58 | compile_commands = json.load(open(os.path.join(root, f)))
59 | for obj in compile_commands:
60 | for key, value in obj.items():
61 | if key == "command":
62 | for string in value.split()[1:]:
63 | # ninja adds local paths as -I. and -I..
64 | # make adds full paths as i flags
65 | flags.append(canonicalize_path(string, root))
66 | return flags
67 |
68 | def merge_flags(flags, pflags):
69 | result = []
70 | def append_result(f):
71 | if f.startswith(('-I', '-D', '-isystem', '-include', '-isysroot', '-W', '-std', '-pthread', '-f', '-pedantic', '-arch', '-m', '-hc')):
72 | if f not in pflags and f not in result: result.append(f)
73 | elif not f.startswith(('-O', '-o', '-c', '-g')) and f.startswith('-'): result.append(f)
74 | flag = ""
75 | for f in flags:
76 | if f.startswith('-'):
77 | append_result(flag)
78 | flag = f
79 | else: flag = flag + ' ' + f
80 | append_result(flag)
81 | return result
82 |
83 | def filter_flag(f, exclude_options):
84 | for pat in exclude_options:
85 | if fnmatch.fnmatch(f, pat): return False
86 | return True
87 |
88 | ordered_std_flags = ['-std=c++0x', '-std=gnu++0x', '-std=c++11', '-std=gnu++11', '-std=c++1y', '-std=gnu++1y', '-std=c++14', '-std=gnu++14', '-std=c++1z', '-std=gnu++1z', '-std=c++17', '-std=gnu++17']
89 | def find_index(l, elem):
90 | for i,x in enumerate(l):
91 | if x == elem: return i
92 | return -1
93 |
94 | def std_flag_rank(x):
95 | return find_index(ordered_std_flags, x)
96 |
97 |
98 | def max_std(x, y):
99 | if (std_flag_rank(x) > std_flag_rank(y)): return x
100 | else: return y
101 |
102 | def split_flags(flags, exclude_options):
103 | result = []
104 | std_flags = []
105 | for f in flags:
106 | if f.startswith('-std'): std_flags.append(f)
107 | elif filter_flag(f, exclude_options): result.extend(f.split())
108 | if len(std_flags) > 0: result.append(functools.reduce(max_std, std_flags))
109 | return result
110 |
111 | def accumulate_options(path, exclude_options):
112 | flags = []
113 | for root, dirs, filenames in os.walk(path):
114 | for f in filenames:
115 | if f.endswith('compile_commands.json'):
116 | flags.extend(merge_flags(parse_compile_commands(root, f), flags))
117 | return split_flags(flags, exclude_options)
118 | if f.endswith('flags.make'):
119 | flags.extend(merge_flags(parse_flags(os.path.join(root, f)), flags))
120 | return split_flags(flags, exclude_options)
121 |
122 | project_options = {}
123 |
124 | def clear_options():
125 | global project_options
126 | project_options = {}
127 |
128 | def get_build_dir(view):
129 | result = get_setting(view, "build_dir", ["build"])
130 | if isinstance(result, str): return [result]
131 | else: return result
132 |
133 | def get_options(project_path, additional_options, exclude_options, build_dirs, default_options):
134 | if project_path in project_options: return project_options[project_path]
135 |
136 | build_dir = next((build_dir for d in build_dirs for build_dir in [os.path.join(project_path, d)] if os.path.exists(build_dir)), None)
137 | if build_dir != None:
138 | project_options[project_path] = ['-x', 'c++'] + accumulate_options(build_dir, exclude_options) + additional_options
139 | else:
140 | project_options[project_path] = ['-x', 'c++'] + default_options + additional_options
141 |
142 | # debug_print(project_path, project_options[project_path])
143 | return project_options[project_path]
144 |
145 | def get_args(view):
146 | project_path = get_project_path(view)
147 | additional_options = get_setting(view, "additional_options", [])
148 | exclude_options = get_setting(view, "exclude_options", [])
149 | build_dir = get_build_dir(view)
150 | debug_print("build dirs:", build_dir)
151 | default_options = get_setting(view, "default_options", ["-std=c++11"])
152 | debug_print(get_options(project_path, additional_options, exclude_options, build_dir, default_options))
153 | return get_options(project_path, additional_options, exclude_options, build_dir, default_options)
154 |
155 | #
156 | #
157 | # Retrieve include files
158 | #
159 | #
160 | def find_any_of(s, items):
161 | for item in items:
162 | i = s.find(item)
163 | if (i != -1): return i
164 | return -1
165 |
166 | def bisect_right_prefix(a, x, lo=0, hi=None):
167 | if lo < 0:
168 | raise ValueError('lo must be non-negative')
169 | if hi is None:
170 | hi = len(a)
171 | while lo < hi:
172 | mid = (lo+hi)//2
173 | if x < a[mid] and not a[mid].startswith(x): hi = mid
174 | else: lo = mid+1
175 | return lo
176 |
177 | def find_prefix(items, prefix):
178 | return items[bisect.bisect_left(items, prefix): bisect_right_prefix(items, prefix)]
179 |
180 | project_includes = {}
181 | includes_lock = Lock()
182 |
183 | def clear_includes_impl():
184 | global project_includes
185 | if includes_lock.acquire(timeout=10000):
186 | project_includes = {}
187 | includes_lock.release()
188 | else:
189 | debug_print("Can't clear includes")
190 |
191 | def clear_includes():
192 | sublime.set_timeout(lambda:clear_includes_impl() , 1)
193 |
194 | def search_include(path):
195 | start = len(path)
196 | if path[-1] is not '/': start = start + 1
197 | result = []
198 | for root, dirs, filenames in os.walk(path):
199 | for f in filenames:
200 | full_name = os.path.join(root, f)
201 | result.append(full_name[start:])
202 | return result
203 |
204 | def find_includes(view, project_path):
205 | result = set()
206 | is_path = False
207 | for option in get_args(view):
208 | if option.startswith('-I'): result.update(search_include(option[2:]))
209 | if is_path: result.update(search_include(option))
210 | if option == '-isystem': is_path = True
211 | else: is_path = False
212 | for path in get_setting(view, "default_include_paths", ["/usr/include", "/usr/local/include"]):
213 | result.update(search_include(path))
214 | return sorted(result)
215 |
216 | def get_includes(view):
217 | global project_includes
218 | result = []
219 | if includes_lock.acquire(blocking=False):
220 | try:
221 | project_path = get_project_path(view)
222 | if project_path not in project_includes:
223 | project_includes[project_path] = find_includes(view, project_path)
224 | result = project_includes[project_path]
225 | except:
226 | pass
227 | finally:
228 | includes_lock.release()
229 | else:
230 | debug_print("Includes locked: return nothing")
231 | return result
232 |
233 | def parse_slash(path, index):
234 | last = path.find('/', index)
235 | if last == -1: return path[index:]
236 | return path[index:last + 1]
237 |
238 | def complete_includes(view, prefix):
239 | slash_index = prefix.rfind('/') + 1
240 | paths = find_prefix(get_includes(view), prefix)
241 | return sorted(set([parse_slash(path, slash_index) for path in paths]))
242 |
243 |
244 |
245 | #
246 | #
247 | # Error panel
248 | #
249 | #
250 | class ClangTogglePanel(sublime_plugin.WindowCommand):
251 | def run(self, **args):
252 | show = args["show"] if "show" in args else None
253 |
254 | if show or (show == None and not clang_error_panel.is_visible(self.window)):
255 | clang_error_panel.open(self.window)
256 | else:
257 | clang_error_panel.close()
258 |
259 |
260 | class ClangErrorPanelFlush(sublime_plugin.TextCommand):
261 | def run(self, edit, data):
262 | self.view.erase(edit, sublime.Region(0, self.view.size()))
263 | self.view.insert(edit, 0, data)
264 |
265 | def is_view_visible(view, window=None):
266 | ret = view != None and view.window() != None
267 | if ret and window:
268 | ret = view.window().id() == window.id()
269 | return ret
270 |
271 | class ClangErrorPanel(object):
272 | def __init__(self):
273 | self.view = None
274 | self.data = ""
275 |
276 | def set_data(self, data):
277 | self.data = data
278 | if self.is_visible(): self.flush()
279 |
280 | def get_view(self):
281 | return self.view
282 |
283 | def is_visible(self, window=None):
284 | return is_view_visible(self.view, window)
285 |
286 | def set_view(self, view):
287 | self.view = view
288 |
289 | def flush(self):
290 | self.view.set_read_only(False)
291 | self.view.set_scratch(True)
292 | self.view.run_command("clang_error_panel_flush", {"data": self.data})
293 | self.view.set_read_only(True)
294 |
295 | def open(self, window=None):
296 | if window == None:
297 | window = sublime.active_window()
298 | if not self.is_visible(window):
299 | self.view = window.get_output_panel("clangcomplete")
300 | self.view.settings().set("result_file_regex", "^(..[^:\n]*):([0-9]+):?([0-9]+)?:? (.*)$")
301 | self.view.set_syntax_file('Packages/ClangComplete/ErrorPanel.tmLanguage')
302 | self.flush()
303 |
304 | window.run_command("show_panel", {"panel": "output.clangcomplete"})
305 |
306 | def close(self):
307 | sublime.active_window().run_command("hide_panel", {"panel": "output.clangcomplete"})
308 |
309 |
310 | clang_error_panel = ClangErrorPanel()
311 |
312 | #
313 | #
314 | # Get language from sublime
315 | #
316 | #
317 |
318 | language_regex = re.compile("(?<=source\.)[\w+#]+")
319 |
320 | def get_language(view):
321 | try:
322 | caret = view.sel()[0].a
323 | language = language_regex.search(view.scope_name(caret))
324 | if language == None:
325 | return None
326 | return language.group(0)
327 | except:
328 | return None
329 |
330 | def is_supported_language(view):
331 | language = get_language(view)
332 | if language == None or (language != "c++" and
333 | language != "c" and
334 | language != "objc" and
335 | language != "objc++"):
336 | return False
337 | return True
338 |
339 |
340 |
341 | member_regex = re.compile(r"(([a-zA-Z_]+[0-9_]*)|([\)\]])+)((\.)|(->))$")
342 | not_code_regex = re.compile("(string.)|(comment.)")
343 |
344 | def convert_completion(x):
345 | if '\n' in x:
346 | c = x.split('\n', 1)
347 | return (c[0], c[1])
348 | else:
349 | return (x, x)
350 |
351 | def convert_completions(completions):
352 | return [convert_completion(x) for x in completions]
353 |
354 | # def is_member_completion(view, caret):
355 | # line = view.substr(sublime.Region(view.line(caret).a, caret))
356 | # if member_regex.search(line) != None:
357 | # return True
358 | # elif get_language(view).startswith("objc"):
359 | # return re.search(r"\[[\.\->\s\w\]]+\s+$", line) != None
360 | # return False
361 |
362 | class ClangCompleteClearCache(sublime_plugin.TextCommand):
363 | def run(self, edit):
364 | sublime.status_message("Clearing cache...")
365 | clear_includes()
366 | clear_options()
367 | free_all()
368 |
369 | class ClangCompleteFindUses(sublime_plugin.TextCommand):
370 | def run(self, edit):
371 | debug_print("Find Uses")
372 | filename = self.view.file_name()
373 | # The view hasnt finsished loading yet
374 | if (filename is None): return
375 |
376 | row, col = self.view.rowcol(self.view.sel()[0].begin())
377 | uses = find_uses(filename, get_args(self.view), row+1, col+1, None)
378 | self.view.window().show_quick_panel(uses, self.on_done, sublime.MONOSPACE_FONT, 0, lambda index:self.quick_open(uses, index))
379 |
380 | def quick_open(self, uses, index):
381 | self.view.window().open_file(uses[index], sublime.ENCODED_POSITION | sublime.TRANSIENT)
382 |
383 | def on_done(self):
384 | pass
385 |
386 | class ClangCompleteGotoDef(sublime_plugin.TextCommand):
387 | def run(self, edit):
388 | filename = self.view.file_name()
389 | # The view hasnt finsished loading yet
390 | if (filename is None): return
391 |
392 | reparse(filename, get_args(self.view), get_unsaved_buffer(self.view))
393 |
394 | pos = self.view.sel()[0].begin()
395 | row, col = self.view.rowcol(pos)
396 | target = get_definition(filename, get_args(self.view), row+1, col+1)
397 |
398 | if (len(target) is 0): sublime.status_message("Cant find definition")
399 | else: self.view.window().open_file(target, sublime.ENCODED_POSITION)
400 |
401 | class ClangCompleteShowType(sublime_plugin.TextCommand):
402 | def run(self, edit):
403 | filename = self.view.file_name()
404 | # The view hasnt finsished loading yet
405 | if (filename is None): return
406 |
407 | reparse(filename, get_args(self.view), get_unsaved_buffer(self.view))
408 |
409 | pos = self.view.sel()[0].begin()
410 | row, col = self.view.rowcol(pos)
411 | type = get_type(filename, get_args(self.view), row+1, col+1)
412 |
413 | sublime.status_message(type)
414 |
415 | class ClangCompleteComplete(sublime_plugin.TextCommand):
416 | def show_complete(self):
417 | self.view.run_command("hide_auto_complete")
418 | sublime.set_timeout(lambda: self.view.run_command("auto_complete"), 1)
419 |
420 | def run(self, edit, characters):
421 | debug_print("ClangCompleteComplete")
422 | regions = [a for a in self.view.sel()]
423 | self.view.sel().clear()
424 | for region in reversed(regions):
425 | pos = 0
426 | region.end() + len(characters)
427 | if region.size() > 0:
428 | self.view.replace(edit, region, characters)
429 | pos = region.begin() + len(characters)
430 | else:
431 | self.view.insert(edit, region.end(), characters)
432 | pos = region.end() + len(characters)
433 |
434 | self.view.sel().add(sublime.Region(pos, pos))
435 | caret = self.view.sel()[0].begin()
436 | word = self.view.substr(self.view.word(caret)).strip()
437 | debug_print("Complete", word)
438 | triggers = ['->', '::', '.']
439 | if word in triggers:
440 | debug_print("Popup completions")
441 | self.show_complete()
442 |
443 | build_panel_window_id = None
444 |
445 | def is_build_panel_visible(window):
446 | return build_panel_window_id != None and window != None and window.id() == build_panel_window_id
447 |
448 | class ClangCompleteAutoComplete(sublime_plugin.EventListener):
449 | def complete_at(self, view, prefix, location, timeout):
450 | debug_print("complete_at", prefix)
451 | filename = view.file_name()
452 | # The view hasnt finsished loading yet
453 | if (filename is None): return []
454 | if not is_supported_language(view):
455 | return []
456 |
457 | completions = []
458 |
459 | line = view.substr(view.line(location))
460 |
461 | if line.startswith("#include") or line.startswith("# include"):
462 | row, col = view.rowcol(location - len(prefix))
463 | start = find_any_of(line, ['<', '"'])
464 | if start != -1: completions = convert_completions(complete_includes(view, line[start+1:col] + prefix))
465 | else:
466 | r = view.word(location - len(prefix))
467 | word = view.substr(r)
468 | # Skip completions for single colon or dash, since we want to
469 | # optimize for completions after the `::` or `->` characters
470 | if word == ':' or word == '-': return []
471 | p = 0
472 | if re.match('^\w+$', word): p = r.begin()
473 | else: p = r.end() - 1
474 | row, col = view.rowcol(p)
475 | # debug_print("complete: ", row, col, word)
476 | completions = convert_completions(get_completions(filename, get_args(view), row+1, col+1, "", timeout, get_unsaved_buffer(view)))
477 |
478 | return completions
479 |
480 |
481 | def diagnostics(self, view):
482 | filename = view.file_name()
483 | # The view hasnt finsished loading yet
484 | if (filename is None): return []
485 | diagnostics = get_diagnostics(filename, get_args(view))
486 | # If there are errors in the precompiled headers, then we will free
487 | # the tu, and reload the diagnostics
488 | for diag in diagnostics:
489 | if "has been modified since the precompiled header" in diag or "modified since it was first processed" in diag:
490 | free_tu(filename)
491 | diagnostics = get_diagnostics(filename, get_args(view))
492 | break
493 | return [diag for diag in diagnostics if "#pragma once in main file" not in diag]
494 |
495 | def show_diagnostics(self, view):
496 | output = '\n'.join(self.diagnostics(view))
497 | clang_error_panel.set_data(output)
498 | window = view.window()
499 | if not window is None and len(output) > 1:
500 | window.run_command("clang_toggle_panel", {"show": True})
501 |
502 | def on_window_command(self, window, command_name, args):
503 | global build_panel_window_id
504 | debug_print(command_name, args)
505 | if command_name == 'show_panel' and args['panel'] == 'output.exec':
506 | if 'toggle' in args and args['toggle'] == True and build_panel_window_id != None: build_panel_window_id=None
507 | else: build_panel_window_id = window.id()
508 | if command_name == 'hide_panel':
509 | if build_panel_window_id != None or args != None and ('panel' in args and args['panel'] == 'output.exec'):
510 | build_panel_window_id = None
511 | return None
512 |
513 |
514 | def on_post_text_command(self, view, name, args):
515 | if not is_supported_language(view): return
516 |
517 | if 'delete' in name: return
518 |
519 | pos = view.sel()[0].begin()
520 | self.complete_at(view, "", pos, 0)
521 |
522 |
523 | def on_query_completions(self, view, prefix, locations):
524 | if not is_supported_language(view):
525 | return []
526 |
527 | completions = self.complete_at(view, prefix, locations[0], get_setting(view, "timeout", 200))
528 | debug_print("on_query_completions:", prefix, len(completions))
529 | if (get_setting(view, "inhibit_sublime_completions", True)):
530 | return (completions, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)
531 | else:
532 | return (completions)
533 |
534 | def on_activated_async(self, view):
535 | debug_print("on_activated_async")
536 | if not is_supported_language(view): return
537 |
538 | debug_print("on_activated_async: get_includes")
539 | get_includes(view)
540 | debug_print("on_activated_async: complete_at")
541 | self.complete_at(view, "", view.sel()[0].begin(), 0)
542 |
543 |
544 | def on_post_save_async(self, view):
545 | if not is_supported_language(view): return
546 |
547 | show_panel = None
548 | show_diagnostics_on_save = get_setting(view, "show_diagnostics_on_save", "no_build")
549 | if show_diagnostics_on_save == 'always': show_panel = True
550 | elif show_diagnostics_on_save == 'never': show_panel = False
551 | else: show_panel = not is_build_panel_visible(view.window())
552 |
553 | if show_panel: self.show_diagnostics(view)
554 |
555 | pos = view.sel()[0].begin()
556 | self.complete_at(view, "", pos, 0)
557 |
558 | def on_close(self, view):
559 | if is_supported_language(view):
560 | free_tu(view.file_name())
561 |
562 | def on_query_context(self, view, key, operator, operand, match_all):
563 | if key == "clangcomplete_supported_language":
564 | if view == None: view = sublime.active_window().active_view()
565 | return is_supported_language(view)
566 | elif key == "clangcomplete_is_code":
567 | return not_code_regex.search(view.scope_name(view.sel()[0].begin())) == None
568 | elif key == "clangcomplete_panel_visible":
569 | return clang_error_panel.is_visible()
570 |
--------------------------------------------------------------------------------
/complete/Makefile:
--------------------------------------------------------------------------------
1 | CXX=c++
2 | CXXFLAGS=-std=gnu++0x
3 |
4 | CLANG_PREFIX=$(shell \
5 | (cd /usr/local/include/clang-c/ && cd ../.. && pwd) 2> /dev/null || \
6 | (cd /usr/lib/llvm-9.0/ && pwd) 2> /dev/null || \
7 | (cd /usr/lib/llvm-8.0/ && pwd) 2> /dev/null || \
8 | (cd /usr/lib/llvm-7.0/ && pwd) 2> /dev/null || \
9 | (cd /usr/lib/llvm-6.0/ && pwd) 2> /dev/null || \
10 | (cd /usr/lib/llvm-5.0/ && pwd) 2> /dev/null || \
11 | (cd /usr/lib/llvm-4.0/ && pwd) 2> /dev/null || \
12 | (cd /usr/lib/llvm-3.6/ && pwd) 2> /dev/null || \
13 | (cd /usr/lib/llvm-3.8/ && pwd) 2> /dev/null || \
14 | (cd /usr/lib/llvm-3.7/ && pwd) 2> /dev/null || \
15 | (cd /usr/lib/llvm-3.6/ && pwd) 2> /dev/null || \
16 | (cd /usr/lib/llvm-3.5/ && pwd) 2> /dev/null || \
17 | (cd /usr/lib/llvm-3.4/ && pwd) 2> /dev/null || \
18 | (cd /usr/lib/llvm-3.3/ && pwd) 2> /dev/null || \
19 | (cd /usr/local/opt/llvm && pwd) 2> /dev/null)
20 | CLANG_FLAGS=-I$(CLANG_PREFIX)/include/
21 | CLANG_LIBS=-L$(CLANG_PREFIX)/lib/ -lclang
22 |
23 | FLAGS=$(CLANG_FLAGS)
24 | LIBS=$(CLANG_LIBS)
25 |
26 | LIB_NAME=libcomplete
27 | SRC=complete
28 |
29 | UNAME_S := $(shell uname -s)
30 | ifeq ($(UNAME_S),Linux)
31 | COMPLETE_LDFLAGS = -shared -Wl,-soname,$(LIB_NAME).so -o $(LIB_NAME).so $(SRC).o $(LIBS) -Wl,-rpath=$(CLANG_PREFIX)/lib
32 | endif
33 | ifeq ($(UNAME_S),Darwin)
34 | COMPLETE_LDFLAGS = -dynamiclib -Wl,-install_name,$(LIB_NAME).dylib -o $(LIB_NAME).dylib $(SRC).o $(LIBS)
35 | endif
36 |
37 | all:
38 | $(CXX) $(CXXFLAGS) $(FLAGS) -DNDEBUG -Os -c -fPIC complete.cpp -o $(SRC).o
39 | $(CXX) $(COMPLETE_LDFLAGS) $(LDFLAGS)
40 | debug:
41 | $(CXX) $(CXXFLAGS) $(FLAGS) -g -c -fPIC -DCLANG_COMPLETE_LOG $(SRC).cpp -o $(SRC).o
42 | $(CXX) $(COMPLETE_LDFLAGS) $(LDFLAGS)
43 | log:
44 | $(CXX) $(CXXFLAGS) $(FLAGS) -Os -c -fPIC -DCLANG_COMPLETE_LOG $(SRC).cpp -o $(SRC).o
45 | $(CXX) $(COMPLETE_LDFLAGS) $(LDFLAGS)
46 | prefix:
47 | echo $(CLANG_PREFIX)
48 |
--------------------------------------------------------------------------------
/complete/complete.cpp:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 | // You can obtain one at http://mozilla.org/MPL/2.0/.
4 | //
5 | // Copyright (c) 2013, Paul Fultz II
6 |
7 |
8 | #ifndef CLANG_UTILS_TRANSLATION_UNIT_H
9 | #define CLANG_UTILS_TRANSLATION_UNIT_H
10 |
11 | // #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 | #include
26 |
27 | #include "complete.h"
28 |
29 | #ifdef CLANG_COMPLETE_LOG
30 | std::ofstream dump_log("clang_log", std::ios_base::app);
31 | #define DUMP(x) dump_log << std::string(__PRETTY_FUNCTION__) << ": " << #x << " = " << x << std::endl
32 |
33 | #define DUMP_FUNCTION dump_log << "Called: " << std::string(__PRETTY_FUNCTION__) << std::endl;
34 |
35 | #define TIMER() timer dump_log_timer(true);
36 |
37 | #define DUMP_TIMER() DUMP(dump_log_timer)
38 |
39 | #define DUMP_LOG_TIME(x) dump_log << x << ": " << dump_log_timer.reset().count() << std::endl
40 |
41 | #else
42 |
43 | #define DUMP(x)
44 |
45 | #define DUMP_FUNCTION
46 |
47 | #define TIMER()
48 |
49 | #define DUMP_TIMER()
50 |
51 | #define DUMP_LOG_TIME(x)
52 |
53 | #endif
54 |
55 |
56 | namespace std {
57 |
58 | string& to_string(string& s)
59 | {
60 | return s;
61 | }
62 |
63 | const string& to_string(const string& s)
64 | {
65 | return s;
66 | }
67 |
68 | }
69 |
70 | class timer
71 | {
72 | typedef typename std::conditional::type clock_type;
75 | typedef std::chrono::milliseconds milliseconds;
76 | public:
77 | explicit timer(bool run = false)
78 | {
79 | if (run) this->reset();
80 | }
81 | milliseconds reset()
82 | {
83 | milliseconds x = this->elapsed();
84 | this->start = clock_type::now();
85 | return x;
86 | }
87 | milliseconds elapsed() const
88 | {
89 | return std::chrono::duration_cast(clock_type::now() - this->start);
90 | }
91 | template
92 | friend Stream& operator<<(Stream& out, const timer& self)
93 | {
94 | return out << self.elapsed().count();
95 | }
96 | private:
97 | clock_type::time_point start;
98 | };
99 |
100 | // An improved async, that doesn't block
101 | template< class Function, class... Args>
102 | std::future::type>
103 | detach_async( Function&& f, Args&&... args )
104 | {
105 | typedef typename std::result_of::type result_type;
106 | std::packaged_task task(std::forward(f));
107 | auto fut = task.get_future();
108 | std::thread(std::move(task)).detach();
109 | return std::move(fut);
110 | }
111 |
112 | inline bool starts_with(const char *str, const char *pre)
113 | {
114 | size_t lenpre = strlen(pre),
115 | lenstr = strlen(str);
116 | return lenstr < lenpre ? false : strncmp(pre, str, lenpre) == 0;
117 | }
118 |
119 | inline bool istarts_with(const std::string& str, const std::string& pre)
120 | {
121 | return str.length() < pre.length() ? false :
122 | std::equal(pre.begin(), pre.end(), str.begin(), [](char a, char b) { return std::tolower(a) == std::tolower(b); });
123 | }
124 |
125 | std::string get_line_at(const std::string& str, unsigned int line)
126 | {
127 | int n = 1;
128 | std::string::size_type pos = 0;
129 | std::string::size_type prev = 0;
130 | while ((pos = str.find('\n', prev)) != std::string::npos)
131 | {
132 | if (n == line) return str.substr(prev, pos - prev);
133 | prev = pos + 1;
134 | n++;
135 | }
136 |
137 | // To get the last line
138 | if (n == line) return str.substr(prev);
139 | else return "";
140 | }
141 |
142 | CXIndex get_index(bool clear=false)
143 | {
144 | static std::shared_ptr index = std::shared_ptr(clang_createIndex(1, 1), &clang_disposeIndex);
145 | if (clear) index = std::shared_ptr(clang_createIndex(1, 1), &clang_disposeIndex);
146 | return index.get();
147 | }
148 |
149 | class translation_unit
150 | {
151 | // CXIndex index;
152 | CXTranslationUnit tu;
153 | std::string filename;
154 | std::timed_mutex m;
155 |
156 | CXUnsavedFile unsaved_buffer(const char * buffer, unsigned len)
157 | {
158 | CXUnsavedFile result;
159 | result.Filename = this->filename.c_str();
160 | result.Contents = buffer;
161 | result.Length = len;
162 | return result;
163 | }
164 |
165 | static std::string to_std_string(CXString str)
166 | {
167 | std::string result;
168 | const char * s = clang_getCString(str);
169 | if (s != nullptr) result = s;
170 | clang_disposeString(str);
171 | return result;
172 | }
173 |
174 | struct completion_results
175 | {
176 | std::shared_ptr results;
177 | typedef CXCompletionResult* iterator;
178 |
179 | completion_results(CXCodeCompleteResults* r)
180 | {
181 | this->results = std::shared_ptr(r, &clang_disposeCodeCompleteResults);
182 | }
183 |
184 | std::size_t size() const
185 | {
186 | if (results == nullptr) return 0;
187 | else return results->NumResults;
188 | }
189 |
190 | iterator begin()
191 | {
192 | if (results == nullptr) return nullptr;
193 | else return results->Results;
194 | }
195 |
196 | iterator end()
197 | {
198 | if (results == nullptr) return nullptr;
199 | else return results->Results + results->NumResults;
200 | }
201 | };
202 |
203 | template
204 | static void for_each_completion_string(CXCompletionResult& c, F f)
205 | {
206 | if ( clang_getCompletionAvailability( c.CompletionString ) == CXAvailability_Available )
207 | {
208 | int num = clang_getNumCompletionChunks(c.CompletionString);
209 | for(int i=0;itu, this->filename.c_str(), line, col, nullptr, 0, code_complete_options());
243 | }
244 | else
245 | {
246 | auto unsaved = this->unsaved_buffer(buffer, len);
247 | return clang_codeCompleteAt(this->tu, this->filename.c_str(), line, col, &unsaved, 1, code_complete_options());
248 | }
249 | }
250 |
251 | static unsigned parse_options()
252 | {
253 | return
254 | CXTranslationUnit_KeepGoing |
255 | CXTranslationUnit_DetailedPreprocessingRecord |
256 | CXTranslationUnit_IncludeBriefCommentsInCodeCompletion |
257 | CXTranslationUnit_PrecompiledPreamble |
258 | CXTranslationUnit_CacheCompletionResults;
259 | }
260 |
261 | void unsafe_reparse(const char * buffer=nullptr, unsigned len=0)
262 | {
263 | if (buffer == nullptr) clang_reparseTranslationUnit(this->tu, 0, nullptr, parse_options());
264 | else
265 | {
266 | auto unsaved = this->unsaved_buffer(buffer, len);
267 | clang_reparseTranslationUnit(this->tu, 1, &unsaved, parse_options());
268 | }
269 | }
270 | public:
271 | struct cursor
272 | {
273 | CXCursor c;
274 | CXTranslationUnit tu;
275 |
276 | cursor(CXCursor c, CXTranslationUnit tu) : c(c), tu(tu)
277 | {}
278 |
279 | CXCursorKind get_kind()
280 | {
281 | return clang_getCursorKind(this->c);
282 | }
283 |
284 | cursor get_reference()
285 | {
286 | return cursor(clang_getCursorReferenced(this->c), this->tu);
287 | }
288 |
289 | cursor get_definition()
290 | {
291 | return cursor(clang_getCursorDefinition(this->c), this->tu);
292 | }
293 |
294 | cursor get_type()
295 | {
296 | return cursor(clang_getTypeDeclaration(clang_getCanonicalType(clang_getCursorType(this->c))), this->tu);
297 | }
298 |
299 | std::string get_display_name()
300 | {
301 | return to_std_string(clang_getCursorDisplayName(this->c));
302 | }
303 |
304 | std::string get_spelling()
305 | {
306 | return to_std_string(clang_getCursorSpelling(this->c));
307 | }
308 |
309 | std::string get_type_name()
310 | {
311 | return to_std_string(clang_getTypeSpelling(clang_getCanonicalType(clang_getCursorType(this->c))));
312 | }
313 |
314 | CXSourceLocation get_location()
315 | {
316 | return clang_getCursorLocation(this->c);
317 | }
318 |
319 | std::string get_location_path()
320 | {
321 | CXFile f;
322 | unsigned line, col, offset;
323 | clang_getSpellingLocation(this->get_location(), &f, &line, &col, &offset);
324 | return to_std_string(clang_getFileName(f)) + ":" + std::to_string(line) + ":" + std::to_string(col);
325 | }
326 |
327 | std::string get_include_file()
328 | {
329 | CXFile f = clang_getIncludedFile(this->c);
330 | return to_std_string(clang_getFileName(f));
331 | }
332 |
333 | std::vector get_overloaded_cursors()
334 | {
335 | std::vector result = {*this};
336 | if (clang_getCursorKind(this->c) == CXCursor_OverloadedDeclRef)
337 | {
338 | for(int i=0;ic);i++)
339 | {
340 | result.emplace_back(clang_getOverloadedDecl(this->c, i), this->tu);
341 | }
342 | }
343 | return result;
344 | }
345 |
346 | template
347 | struct find_references_trampoline
348 | {
349 | F f;
350 | cursor * self;
351 |
352 | find_references_trampoline(F f, cursor * self) : f(f), self(self)
353 | {}
354 | CXVisitorResult operator()(CXCursor c, CXSourceRange r) const
355 | {
356 | f(cursor(c, self->tu), r);
357 | return CXVisit_Continue;
358 | }
359 | };
360 |
361 | template
362 | void find_references(const char* name, F f)
363 | {
364 | CXFile file = clang_getFile(this->tu, name);
365 | find_references_trampoline trampoline(f, this);
366 | CXCursorAndRangeVisitor visitor = {};
367 | visitor.context = &trampoline;
368 | visitor.visit = [](void *context, CXCursor c, CXSourceRange r) -> CXVisitorResult
369 | {
370 | return (*(reinterpret_cast*>(context)))(c, r);
371 | };
372 | clang_findReferencesInFile(this->c, file, visitor);
373 | }
374 |
375 | bool is_null()
376 | {
377 | return clang_Cursor_isNull(this->c);
378 | }
379 | };
380 | translation_unit(const char * filename, const char ** args, int argv) : filename(filename)
381 | {
382 | // this->index = clang_createIndex(1, 1);
383 | this->tu = clang_parseTranslationUnit(get_index(), filename, args, argv, NULL, 0, parse_options());
384 | detach_async([=]() { this->reparse(); });
385 | }
386 |
387 | translation_unit(const translation_unit&) = delete;
388 |
389 | cursor get_cursor_at(unsigned long line, unsigned long col, const char * name=nullptr)
390 | {
391 | if (name == nullptr) name = this->filename.c_str();
392 | CXFile f = clang_getFile(this->tu, name);
393 | CXSourceLocation loc = clang_getLocation(this->tu, f, line, col);
394 | return cursor(clang_getCursor(this->tu, loc), this->tu);
395 | }
396 |
397 | void reparse(const char * buffer=nullptr, unsigned len=0)
398 | {
399 | std::lock_guard lock(this->m);
400 | this->unsafe_reparse(buffer, len);
401 | }
402 |
403 | struct usage
404 | {
405 | CXTUResourceUsage u;
406 |
407 | typedef CXTUResourceUsageEntry* iterator;
408 |
409 | usage(CXTUResourceUsage u) : u(u)
410 | {}
411 |
412 | usage(const usage&) = delete;
413 |
414 |
415 | iterator begin()
416 | {
417 | return u.entries;
418 | }
419 |
420 | iterator end()
421 | {
422 | return u.entries + u.numEntries;
423 | }
424 |
425 | ~usage()
426 | {
427 | clang_disposeCXTUResourceUsage(u);
428 | }
429 | };
430 |
431 | std::unordered_map get_usage()
432 | {
433 | std::lock_guard lock(this->m);
434 | std::unordered_map result;
435 | auto u = std::make_shared(clang_getCXTUResourceUsage(this->tu));
436 | for(CXTUResourceUsageEntry e:*u)
437 | {
438 | result.insert(std::make_pair(clang_getTUResourceUsageName(e.kind), e.amount));
439 | }
440 | return result;
441 |
442 | }
443 |
444 |
445 | typedef std::tuple completion;
446 |
447 | std::vector complete_at(unsigned line, unsigned col, const char * prefix, const char * buffer=nullptr, unsigned len=0)
448 | {
449 | std::lock_guard lock(this->m);
450 | TIMER();
451 | std::vector results;
452 |
453 | std::string display;
454 | std::string replacement;
455 | std::string description;
456 | char buf[1024];
457 | auto completions = this->completions_at(line, col, buffer, len);
458 | DUMP_LOG_TIME("Clang to complete");
459 | results.reserve(completions.size());
460 | for(auto& c:completions)
461 | {
462 | auto priority = clang_getCompletionPriority(c.CompletionString);
463 | auto ck = c.CursorKind;
464 | auto num = clang_getNumCompletionChunks(c.CompletionString);
465 |
466 | display.reserve(num*8);
467 | replacement.reserve(num*8);
468 | description.clear();
469 |
470 | std::size_t idx = 1;
471 | for_each_completion_string(c, [&](const std::string& text, CXCompletionChunkKind kind)
472 | {
473 | switch (kind)
474 | {
475 | case CXCompletionChunk_LeftParen:
476 | case CXCompletionChunk_RightParen:
477 | case CXCompletionChunk_LeftBracket:
478 | case CXCompletionChunk_RightBracket:
479 | case CXCompletionChunk_LeftBrace:
480 | case CXCompletionChunk_RightBrace:
481 | case CXCompletionChunk_LeftAngle:
482 | case CXCompletionChunk_RightAngle:
483 | case CXCompletionChunk_CurrentParameter:
484 | case CXCompletionChunk_Colon:
485 | case CXCompletionChunk_Comma:
486 | case CXCompletionChunk_HorizontalSpace:
487 | case CXCompletionChunk_VerticalSpace:
488 | display += text;
489 | replacement += text;
490 | break;
491 | case CXCompletionChunk_TypedText:
492 | display += text;
493 | replacement += text;
494 | if (ck == CXCursor_Constructor)
495 | {
496 | std::snprintf(buf, 1024, "%lu", idx++);
497 | replacement.append(" ${").append(buf).append(":v}");
498 | }
499 | break;
500 | case CXCompletionChunk_Placeholder:
501 | display += text;
502 | std::snprintf(buf, 1024, "%lu", idx++);
503 | replacement.append("${").append(buf).append(":").append(text).append("}");
504 | break;
505 | case CXCompletionChunk_ResultType:
506 | case CXCompletionChunk_Text:
507 | case CXCompletionChunk_Informative:
508 | case CXCompletionChunk_Equal:
509 | description.append(text).append(" ");
510 | break;
511 | case CXCompletionChunk_Optional:
512 | case CXCompletionChunk_SemiColon:
513 | break;
514 | }
515 | });
516 | display.append("\t").append(description);
517 | // Lower priority for completions that start with `operator` and `~`
518 | if (starts_with(display.c_str(), "operator") or starts_with(display.c_str(), "~")) priority = std::numeric_limits::max();
519 | if (not display.empty() and not replacement.empty() and starts_with(display.c_str(), prefix))
520 | results.emplace_back(priority, std::move(display), std::move(replacement));
521 | }
522 | std::sort(results.begin(), results.end());
523 | // Perhaps a reparse can help rejuvenate clang?
524 | // if (results.size() == 0) this->unsafe_reparse(buffer, len);
525 | DUMP_LOG_TIME("Process completions");
526 | DUMP(results.size());
527 | return results;
528 | }
529 |
530 | std::vector get_diagnostics(int timeout=-1)
531 | {
532 | std::unique_lock lock(this->m, std::defer_lock);
533 | if (timeout < 0)
534 | {
535 | lock.lock();
536 | }
537 | else
538 | {
539 | if (!lock.try_lock_for(std::chrono::milliseconds(timeout))) return {};
540 | }
541 | std::vector result;
542 | auto n = clang_getNumDiagnostics(this->tu);
543 | for(int i=0;i(clang_getDiagnostic(this->tu, i), &clang_disposeDiagnostic);
546 | if (diag != nullptr and clang_getDiagnosticSeverity(diag.get()) != CXDiagnostic_Ignored)
547 | {
548 | auto str = clang_formatDiagnostic(diag.get(), clang_defaultDiagnosticDisplayOptions());
549 | result.push_back(to_std_string(str));
550 | }
551 | }
552 | return result;
553 | }
554 |
555 | std::string get_definition(unsigned line, unsigned col)
556 | {
557 | std::lock_guard lock(this->m);
558 | std::string result;
559 | cursor c = this->get_cursor_at(line, col);
560 | DUMP(c.get_display_name());
561 | cursor ref = c.get_reference();
562 | DUMP(ref.is_null());
563 | if (!ref.is_null())
564 | {
565 | DUMP(ref.get_display_name());
566 | result = ref.get_location_path();
567 | }
568 | else if (c.get_kind() == CXCursor_InclusionDirective)
569 | {
570 | result = c.get_include_file();
571 | }
572 | return result;
573 | }
574 |
575 | std::string get_type(unsigned line, unsigned col)
576 | {
577 | std::lock_guard lock(this->m);
578 |
579 | return this->get_cursor_at(line, col).get_type_name();
580 |
581 | }
582 |
583 | std::set find_uses_in(unsigned line, unsigned col, const char * name=nullptr)
584 | {
585 | std::lock_guard lock(this->m);
586 | std::set result;
587 | if (name == nullptr) name = this->filename.c_str();
588 | auto c = this->get_cursor_at(line, col);
589 | for(auto oc:c.get_overloaded_cursors())
590 | {
591 | oc.find_references(name, [&](cursor ref, CXSourceRange r)
592 | {
593 | result.insert(ref.get_location_path());
594 | });
595 | }
596 | return result;
597 | }
598 |
599 | ~translation_unit()
600 | {
601 | std::lock_guard lock(this->m);
602 | clang_disposeTranslationUnit(this->tu);
603 | }
604 | };
605 |
606 | class async_translation_unit : public translation_unit, public std::enable_shared_from_this
607 | {
608 |
609 | struct query
610 | {
611 | std::future> results_future;
612 | std::vector results;
613 | unsigned line;
614 | unsigned col;
615 |
616 | query() : line(0), col(0)
617 | {}
618 |
619 | std::pair get_loc()
620 | {
621 | return std::make_pair(this->line, this->col);
622 | }
623 |
624 | void set(std::future> && results_future, unsigned line, unsigned col)
625 | {
626 | this->results = {};
627 | this->results_future = std::move(results_future);
628 | this->line = line;
629 | this->col = col;
630 | }
631 |
632 | std::vector get(int timeout)
633 | {
634 | if (results_future.valid() and this->ready(timeout))
635 | {
636 | this->results = this->results_future.get();
637 | // Force another query if completion results are empty
638 | if (this->results.size() == 0) std::tie(line, col) = std::make_pair(0, 0);
639 | }
640 | return this->results;
641 | }
642 |
643 | bool ready(int timeout = 10)
644 | {
645 | if (results_future.valid()) return (timeout > 0 and results_future.wait_for(std::chrono::milliseconds(timeout)) == std::future_status::ready);
646 | else return true;
647 | }
648 |
649 | };
650 | std::timed_mutex async_mutex;
651 | query q;
652 |
653 | public:
654 | async_translation_unit(const char * filename, const char ** args, int argv) : translation_unit(filename, args, argv)
655 | {}
656 |
657 |
658 | std::vector async_complete_at(unsigned line, unsigned col, const char * prefix, int timeout, const char * buffer=nullptr, unsigned len=0)
659 | {
660 | DUMP_FUNCTION
661 | std::unique_lock lock(this->async_mutex, std::defer_lock);
662 | if (!lock.try_lock_for(std::chrono::milliseconds(20))) return {};
663 |
664 | if (std::make_pair(line, col) != q.get_loc())
665 | {
666 | // If we are busy with a query, lets avoid making lots of new queries
667 | if (not this->q.ready()) return {};
668 |
669 | std::weak_ptr self = this->shared_from_this();
670 | std::string buffer_as_string(buffer, buffer+len);
671 | this->q.set(detach_async([=]
672 | {
673 | auto b = buffer_as_string.c_str();
674 | if (buffer == nullptr) b = nullptr;
675 | // TODO: Should we always reparse?
676 | // else this->reparse(b, len);
677 | if (auto s = self.lock())
678 | {
679 | return s->complete_at(line, col, "", b, buffer_as_string.length());
680 | }
681 | else
682 | {
683 | return std::vector();
684 | }
685 | }), line, col);
686 | }
687 | auto completions = q.get(timeout);
688 | if (prefix != nullptr and *prefix != 0)
689 | {
690 | std::string pre = prefix;
691 | std::vector results;
692 | std::copy_if(completions.begin(), completions.end(), inserter(results, results.begin()), [&](const completion& x)
693 | {
694 | return istarts_with(std::get<2>(x), pre);
695 | });
696 | return std::move(results);
697 | }
698 | else
699 | {
700 | return std::move(completions);
701 | }
702 | }
703 | };
704 |
705 | std::timed_mutex tus_mutex{};
706 | std::unordered_map> tus;
707 |
708 |
709 |
710 | // #ifdef __MACH__
711 | // #include
712 | // #define CLOCK_MONOTONIC 0
713 | // //clock_gettime is not implemented on OSX
714 | // int clock_gettime(int /*clk_id*/, struct timespec* t) {
715 | // struct timeval now;
716 | // int rv = gettimeofday(&now, NULL);
717 | // if (rv) return rv;
718 | // t->tv_sec = now.tv_sec;
719 | // t->tv_nsec = now.tv_usec * 1000;
720 | // return 0;
721 | // }
722 | // #endif
723 |
724 |
725 | // std::chrono::steady_clock::time_point
726 | // get_now()
727 | // {
728 | // struct timespec tp{};
729 | // // TODO: Check error
730 | // clock_gettime(CLOCK_MONOTONIC, &tp);
731 | // return std::chrono::steady_clock::time_point(std::chrono::seconds(tp.tv_sec) + std::chrono::nanoseconds(tp.tv_nsec));
732 | // }
733 |
734 | std::shared_ptr get_tu(const char * filename, const char ** args, int argv, int timeout=-1)
735 | {
736 | DUMP_FUNCTION
737 | std::unique_lock lock(tus_mutex, std::defer_lock);
738 | DUMP(timeout);
739 | if (timeout < 0) lock.lock();
740 | else if (!lock.try_lock_until(std::chrono::system_clock::now() + std::chrono::milliseconds(timeout))) return {};
741 | if (tus.find(filename) == tus.end())
742 | {
743 | tus[filename] = std::make_shared(filename, args, argv);
744 | }
745 | return tus[filename];
746 | }
747 |
748 | template
749 | std::mutex& get_allocations_mutex()
750 | {
751 | DUMP_FUNCTION
752 | static std::mutex m;
753 | return m;
754 | }
755 |
756 | template
757 | std::unordered_map& get_allocations()
758 | {
759 | DUMP_FUNCTION
760 | static std::mutex m;
761 | static std::unordered_map allocations;
762 | return allocations;
763 | };
764 |
765 | template
766 | unsigned int new_wrapper()
767 | {
768 | DUMP_FUNCTION
769 | std::unique_lock lock(get_allocations_mutex());
770 | unsigned int id = (get_allocations().size() * 8 + sizeof(T)) % std::numeric_limits::max();
771 | while (get_allocations().count(id) > 0 and id < (std::numeric_limits::max() - 1)) id++;
772 | assert(get_allocations().count(id) == 0);
773 | get_allocations().emplace(id, T());
774 | return id;
775 | }
776 |
777 | template
778 | T& unwrap(unsigned int i)
779 | {
780 | DUMP_FUNCTION
781 | assert(i > 0);
782 | std::unique_lock lock(get_allocations_mutex());
783 | return get_allocations().at(i);
784 | }
785 |
786 | template
787 | void free_wrapper(unsigned int i)
788 | {
789 | DUMP_FUNCTION
790 | std::unique_lock lock(get_allocations_mutex());
791 | get_allocations().erase(i);
792 | }
793 |
794 | std::string& get_string(clang_complete_string s)
795 | {
796 | DUMP_FUNCTION
797 | return unwrap(s);
798 | }
799 |
800 | unsigned int new_string(const std::string& s)
801 | {
802 | DUMP_FUNCTION
803 | auto i = new_wrapper();
804 | unwrap(i) = std::string(s);
805 | return i;
806 | }
807 |
808 | typedef std::vector slist;
809 |
810 | slist& get_slist(clang_complete_string_list list)
811 | {
812 | DUMP_FUNCTION
813 | static slist empty_vec;
814 | assert(empty_vec.empty());
815 | if (list == 0) return empty_vec;
816 | else return unwrap(list);
817 | }
818 |
819 | unsigned int new_slist()
820 | {
821 | DUMP_FUNCTION
822 | return new_wrapper();
823 | }
824 |
825 | unsigned int empty_slist()
826 | {
827 | DUMP_FUNCTION
828 | return 0;
829 | }
830 |
831 | template
832 | clang_complete_string_list export_slist(const Range& r)
833 | {
834 | DUMP_FUNCTION
835 | auto id = new_slist();
836 | auto& list = get_slist(id);
837 |
838 | for (const auto& s:r)
839 | {
840 | list.push_back(s);
841 | }
842 |
843 | return id;
844 | }
845 |
846 | template
847 | clang_complete_string_list export_slist_completion(const Range& r)
848 | {
849 | DUMP_FUNCTION
850 | auto id = new_slist();
851 | auto& list = get_slist(id);
852 |
853 | for (const auto& s:r)
854 | {
855 | list.push_back(std::get<1>(s) + "\n" + std::get<2>(s));
856 | }
857 |
858 | return id;
859 | }
860 |
861 | template()())>::type>
862 | Result try_(F f)
863 | {
864 | try
865 | {
866 | return f();
867 | }
868 | catch(const std::exception& e)
869 | {
870 | DUMP(e.what());
871 | return Result{};
872 | }
873 | catch(...)
874 | {
875 | DUMP("Unknown exception");
876 | return Result{};
877 | }
878 | }
879 |
880 | template
881 | void try_void(F f)
882 | {
883 | try
884 | {
885 | return f();
886 | }
887 | catch(const std::exception& e)
888 | {
889 | DUMP(e.what());
890 | }
891 | catch(...)
892 | {
893 | DUMP("Unknown exception");
894 | }
895 | }
896 |
897 | extern "C" {
898 |
899 | const char * clang_complete_string_value(clang_complete_string s)
900 | {
901 | DUMP_FUNCTION
902 | return try_([&]{
903 | return get_string(s).c_str();
904 | });
905 | }
906 | void clang_complete_string_free(clang_complete_string s)
907 | {
908 | DUMP_FUNCTION
909 | free_wrapper(s);
910 | }
911 | void clang_complete_string_list_free(clang_complete_string_list list)
912 | {
913 | DUMP_FUNCTION
914 | free_wrapper(list);
915 | }
916 | int clang_complete_string_list_len(clang_complete_string_list list)
917 | {
918 | DUMP_FUNCTION
919 | return try_([&]() -> int
920 | {
921 | if (list == 0) return 0;
922 | else return get_slist(list).size();
923 | });
924 | }
925 | const char * clang_complete_string_list_at(clang_complete_string_list list, int index)
926 | {
927 | DUMP_FUNCTION
928 | return try_([&]() -> const char *
929 | {
930 | if (list == 0) return nullptr;
931 | else return get_slist(list).at(index).c_str();
932 | });
933 | }
934 |
935 | clang_complete_string_list clang_complete_get_completions(
936 | const char * filename,
937 | const char ** args,
938 | int argv,
939 | unsigned line,
940 | unsigned col,
941 | const char * prefix,
942 | int timeout,
943 | const char * buffer,
944 | unsigned len)
945 | {
946 | DUMP_FUNCTION
947 | return try_([&]
948 | {
949 | auto tu = get_tu(filename, args, argv, 200);
950 | if (tu == nullptr) return empty_slist();
951 | else return export_slist_completion(tu->async_complete_at(line, col, prefix, timeout, buffer, len));
952 | });
953 | }
954 |
955 | clang_complete_string_list clang_complete_find_uses(const char * filename, const char ** args, int argv, unsigned line, unsigned col, const char * search)
956 | {
957 | DUMP_FUNCTION
958 | return try_([&]
959 | {
960 | auto tu = get_tu(filename, args, argv);
961 |
962 | return export_slist(tu->find_uses_in(line, col, search));
963 | });
964 | }
965 |
966 | clang_complete_string_list clang_complete_get_diagnostics(const char * filename, const char ** args, int argv)
967 | {
968 | DUMP_FUNCTION
969 | return try_([&]
970 | {
971 | auto tu = get_tu(filename, args, argv, 200);
972 | if (tu == nullptr) return empty_slist();
973 | else
974 | {
975 | tu->reparse(nullptr, 0);
976 | return export_slist(tu->get_diagnostics(250));
977 | }
978 | });
979 | }
980 |
981 | clang_complete_string clang_complete_get_definition(const char * filename, const char ** args, int argv, unsigned line, unsigned col)
982 | {
983 | DUMP_FUNCTION
984 | return try_([&]
985 | {
986 | auto tu = get_tu(filename, args, argv);
987 |
988 | return new_string(tu->get_definition(line, col));
989 | });
990 | }
991 |
992 | clang_complete_string clang_complete_get_type(const char * filename, const char ** args, int argv, unsigned line, unsigned col)
993 | {
994 | DUMP_FUNCTION
995 | return try_([&]
996 | {
997 | auto tu = get_tu(filename, args, argv);
998 |
999 | return new_string(tu->get_type(line, col));
1000 | });
1001 | }
1002 |
1003 | void clang_complete_reparse(const char * filename, const char ** args, int argv, const char * buffer, unsigned len)
1004 | {
1005 | DUMP_FUNCTION
1006 | try_void([&]
1007 | {
1008 | auto tu = get_tu(filename, args, argv);
1009 | tu->reparse();
1010 | });
1011 | }
1012 |
1013 | void clang_complete_free_tu(const char * filename)
1014 | {
1015 | DUMP_FUNCTION
1016 | try_void([&]
1017 | {
1018 | std::string name = filename;
1019 | detach_async([=]
1020 | {
1021 | std::lock_guard lock(tus_mutex);
1022 | if (tus.find(name) != tus.end())
1023 | {
1024 | tus.erase(name);
1025 | }
1026 | });
1027 | });
1028 | }
1029 |
1030 | void clang_complete_free_all()
1031 | {
1032 | DUMP_FUNCTION
1033 | std::lock_guard lock(tus_mutex);
1034 | tus.clear();
1035 | get_index(true);
1036 | }
1037 | }
1038 |
1039 |
1040 | #endif
1041 |
--------------------------------------------------------------------------------
/complete/complete.h:
--------------------------------------------------------------------------------
1 | // This Source Code Form is subject to the terms of the Mozilla Public
2 | // License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 | // You can obtain one at http://mozilla.org/MPL/2.0/.
4 | //
5 | // Copyright (c) 2013, Paul Fultz II
6 |
7 | #ifndef CLANGCOMPLETE_COMPLETE_H
8 | #define CLANGCOMPLETE_COMPLETE_H
9 |
10 | extern "C"
11 | {
12 | typedef unsigned int clang_complete_string;
13 | const char * clang_complete_string_value(clang_complete_string s);
14 | void clang_complete_string_free(clang_complete_string s);
15 |
16 | typedef unsigned int clang_complete_string_list;
17 | void clang_complete_string_list_free(clang_complete_string_list list);
18 | int clang_complete_string_list_len(clang_complete_string_list list);
19 | const char * clang_complete_string_list_at(clang_complete_string_list list, int index);
20 |
21 | clang_complete_string_list clang_complete_get_completions(
22 | const char * filename,
23 | const char ** args,
24 | int argv,
25 | unsigned line,
26 | unsigned col,
27 | const char * prefix,
28 | int timeout,
29 | const char * buffer,
30 | unsigned len);
31 |
32 | clang_complete_string_list clang_complete_find_uses(const char * filename, const char ** args, int argv, unsigned line, unsigned col, const char * search);
33 |
34 | clang_complete_string_list clang_complete_get_diagnostics(const char * filename, const char ** args, int argv);
35 |
36 | // clang_complete_string_list clang_complete_get_usage(const char * filename, const char ** args, int argv);
37 |
38 | clang_complete_string clang_complete_get_definition(const char * filename, const char ** args, int argv, unsigned line, unsigned col);
39 |
40 | clang_complete_string clang_complete_get_type(const char * filename, const char ** args, int argv, unsigned line, unsigned col);
41 |
42 | void clang_complete_reparse(const char * filename, const char ** args, int argv, const char * buffer, unsigned len);
43 |
44 | void clang_complete_free_tu(const char * filename);
45 |
46 | void clang_complete_free_all();
47 | }
48 |
49 | #endif
--------------------------------------------------------------------------------
/complete/complete.py:
--------------------------------------------------------------------------------
1 | # This Source Code Form is subject to the terms of the Mozilla Public
2 | # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 | # You can obtain one at http://mozilla.org/MPL/2.0/.
4 | #
5 | # Copyright (c) 2013, Paul Fultz II
6 |
7 | from ctypes import cdll
8 | from ctypes import c_int
9 | from ctypes import c_char_p
10 | from ctypes import c_void_p
11 | from ctypes import c_uint
12 | from ctypes import py_object
13 | from copy import copy
14 | import os
15 | import platform
16 | current_path = os.path.dirname(os.path.abspath(__file__))
17 | suffix = 'so'
18 | if platform.system() == 'Darwin':
19 | suffix = 'dylib'
20 | complete = cdll.LoadLibrary('%s/libcomplete.%s' % (current_path, suffix))
21 |
22 | #
23 | #
24 | # Clang c api wrapper
25 | #
26 | #
27 |
28 | complete.clang_complete_string_list_len.restype = c_int
29 | complete.clang_complete_string_list_at.restype = c_char_p
30 | complete.clang_complete_string_value.restype = c_char_p
31 | complete.clang_complete_find_uses.restype = c_uint
32 | complete.clang_complete_get_completions.restype = c_uint
33 | complete.clang_complete_get_diagnostics.restype = c_uint
34 | complete.clang_complete_get_definition.restype = c_uint
35 | complete.clang_complete_get_type.restype = c_uint
36 |
37 | def convert_to_c_string_array(a):
38 | result = (c_char_p * len(a))()
39 | result[:] = [x.encode('utf-8') for x in a]
40 | return result
41 |
42 | def convert_string_list(l):
43 | result = [complete.clang_complete_string_list_at(l, i).decode('utf-8') for i in range(complete.clang_complete_string_list_len(l))]
44 | complete.clang_complete_string_list_free(l)
45 | return result
46 |
47 | def convert_string(s):
48 | result = complete.clang_complete_string_value(s).decode('utf-8')
49 | complete.clang_complete_string_free(s)
50 | return result
51 |
52 | def find_uses(filename, args, line, col, file_to_search):
53 | search = None
54 | if file_to_search is not None: search = file_to_search.encode('utf-8')
55 | return convert_string_list(complete.clang_complete_find_uses(filename.encode('utf-8'), convert_to_c_string_array(args), len(args), line, col, search))
56 |
57 | def get_completions(filename, args, line, col, prefix, timeout, unsaved_buffer):
58 | if unsaved_buffer is None and not os.path.exists(filename): return []
59 | buffer = None
60 | if (unsaved_buffer is not None): buffer = unsaved_buffer.encode("utf-8")
61 | buffer_len = 0
62 | if (buffer is not None): buffer_len = len(buffer)
63 |
64 | return convert_string_list(complete.clang_complete_get_completions(filename.encode('utf-8'), convert_to_c_string_array(args), len(args), line, col, prefix.encode('utf-8'), timeout, buffer, buffer_len))
65 |
66 | def get_diagnostics(filename, args):
67 | return convert_string_list(complete.clang_complete_get_diagnostics(filename.encode('utf-8'), convert_to_c_string_array(args), len(args)))
68 |
69 | def get_definition(filename, args, line, col):
70 | return convert_string(complete.clang_complete_get_definition(filename.encode('utf-8'), convert_to_c_string_array(args), len(args), line, col))
71 |
72 | def get_type(filename, args, line, col):
73 | return convert_string(complete.clang_complete_get_type(filename.encode('utf-8'), convert_to_c_string_array(args), len(args), line, col))
74 |
75 | def reparse(filename, args, unsaved_buffer):
76 | buffer = None
77 | if (unsaved_buffer is not None): buffer = unsaved_buffer.encode("utf-8")
78 | buffer_len = 0
79 | if (buffer is not None): buffer_len = len(buffer)
80 |
81 | complete.clang_complete_reparse(filename.encode('utf-8'), convert_to_c_string_array(args), len(args), buffer, buffer_len)
82 |
83 | def free_tu(filename):
84 | complete.clang_complete_free_tu(filename.encode('utf-8'))
85 |
86 | def free_all():
87 | complete.clang_complete_free_all()
--------------------------------------------------------------------------------