├── .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() --------------------------------------------------------------------------------