]> git.rmz.io Git - dotfiles.git/blob - vim/ycm_extra_conf.py
vim/ycm: replace broken levenshtein with working difflib SequenceMatcher
[dotfiles.git] / vim / ycm_extra_conf.py
1 # https://github.com/Valloric/ycmd/blob/master/cpp/ycm/.ycm_extra_conf.py
2 # https://jonasdevlieghere.com/a-better-youcompleteme-config/
3 # https://github.com/arximboldi/dotfiles/blob/master/emacs/.ycm_extra_conf.py
4
5 import os
6 import os.path
7 from glob import glob
8 import logging
9 import ycm_core
10 import difflib
11
12 BASE_FLAGS = [
13 '-Wall',
14 '-std=c++1z',
15 '-x', 'c++',
16 '-isystem', '/usr/include',
17 '-isystem', '/usr/local/include',
18 ]
19
20 EXTRA_FLAGS = [
21 '-Wall',
22 '-Wextra',
23 # '-Wshadow',
24 # '-Werror',
25 # '-Wc++98-compat',
26 # '-Wno-long-long',
27 # '-Wno-variadic-macros',
28 # '-fexceptions',
29 # '-DNDEBUG',
30 ]
31
32 SOURCE_EXTENSIONS = [
33 '.cpp',
34 '.cxx',
35 '.cc',
36 '.c',
37 '.m',
38 '.mm'
39 ]
40
41 HEADER_EXTENSIONS = [
42 '.h',
43 '.hxx',
44 '.hpp',
45 '.hh'
46 ]
47
48
49 def similarity_ratio(s, t):
50 return difflib.SequenceMatcher(a=s.lower(), b=t.lower()).ratio()
51
52
53 def generate_qt_flags():
54 flags = ['-isystem', '/usr/include/qt/']
55 for p in glob('/usr/include/qt/*/'):
56 flags += ['-isystem', p]
57 return flags
58
59
60 def find_similar_file_in_database(dbpath, filename):
61 import json
62 logging.info("Trying to find some file close to: " + filename)
63 db = json.load(open(dbpath))
64 best_filename = ''
65 best_ratio = 0
66 for entry in db:
67 entry_filename = os.path.normpath(os.path.join(entry["directory"],
68 entry["file"]))
69 ratio = similarity_ratio(str(filename), str(entry_filename))
70 if ratio > best_ratio:
71 best_filename = entry_filename
72 best_ratio = ratio
73 return best_filename
74
75 def ok_compilation_info(info):
76 return bool(info.compiler_flags_)
77
78 def get_compilation_info_for_file(dbpath, database, filename):
79 info = database.GetCompilationInfoForFile(filename)
80 if ok_compilation_info(info):
81 logging.info("Flags for file where found in database: " + filename)
82 return info
83 else:
84 logging.info("Flags for file not found in database: " + filename)
85 basename = os.path.splitext(filename)[0]
86 for extension in SOURCE_EXTENSIONS:
87 replacement_file = basename + extension
88 logging.info("Trying to replace extension with: " + extension)
89 info = database.GetCompilationInfoForFile(replacement_file)
90 if ok_compilation_info(info):
91 logging.info("Replacing header with: " + replacement_file)
92 return info
93 replacement_file = find_similar_file_in_database(dbpath, filename)
94 logging.info("Replacing header with: " + replacement_file)
95 return database.GetCompilationInfoForFile(replacement_file)
96
97
98 def find_nearest_compilation_database(root='.'):
99 dirs = glob(root + '/*/compile_commands.json', recursive=True)
100
101 if len(dirs) == 1:
102 return dirs[0]
103 elif len(dirs) > 1:
104 logging.info("Multiple compilation databases found!")
105 logging.info(dirs)
106 logging.info("Selecting first: %s" % (dir))
107 return dirs[0]
108
109 parent = os.path.dirname(os.path.abspath(root))
110 if parent == root:
111 raise RuntimeError("Could not find compile_commands.json")
112 return find_nearest_compilation_database(parent)
113
114
115 def find_nearest(path, target):
116 candidates = [
117 os.path.join(path, target),
118 os.path.join(path, 'build', target),
119 os.path.join(path, 'output', target),
120 ]
121 for candidate in candidates:
122 if os.path.isfile(candidate) or os.path.isdir(candidate):
123 logging.info("Found nearest " + target + " at " + candidate)
124 return candidate
125 parent = os.path.dirname(os.path.abspath(path))
126 if parent == path:
127 raise RuntimeError("Could not find " + target)
128 return find_nearest(parent, target)
129
130
131 def make_relative_paths_in_flags_absolute(flags, working_directory):
132 if not working_directory:
133 return list(flags)
134 new_flags = []
135 make_next_absolute = False
136 path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]
137 for flag in flags:
138 new_flag = flag
139 if make_next_absolute:
140 make_next_absolute = False
141 if not flag.startswith('/'):
142 new_flag = os.path.join(working_directory, flag)
143 for path_flag in path_flags:
144 if flag == path_flag:
145 make_next_absolute = True
146 break
147 if flag.startswith(path_flag):
148 path = flag[ len(path_flag): ]
149 new_flag = path_flag + os.path.join(working_directory, path)
150 break
151 if new_flag:
152 new_flags.append(new_flag)
153 return new_flags
154
155
156 def flags_for_include(root):
157 try:
158 include_path = find_nearest(root, 'include')
159 flags = []
160 for dirroot, dirnames, filenames in os.walk(include_path):
161 for dir_path in dirnames:
162 real_path = os.path.join(dirroot, dir_path)
163 flags = flags + ["-I" + real_path]
164 return flags
165 except Exception as err:
166 logging.info("Error while looking flags for includes in root: " + root)
167 logging.error(err)
168 return None
169
170
171 def flags_for_compilation_database(root, filename):
172 try:
173 compilation_db_path = find_nearest_compilation_database(root)
174 compilation_db_dir = os.path.dirname(compilation_db_path)
175 logging.info("Set compilation database directory to " + compilation_db_dir)
176 compilation_db = ycm_core.CompilationDatabase(compilation_db_dir)
177 if not compilation_db:
178 logging.info("Compilation database file found but unable to load")
179 return None
180 compilation_info = get_compilation_info_for_file(
181 compilation_db_path, compilation_db, filename)
182 if not compilation_info:
183 logging.info("No compilation info for " + filename + " in compilation database")
184 return None
185 return make_relative_paths_in_flags_absolute(
186 compilation_info.compiler_flags_,
187 compilation_info.compiler_working_dir_)
188 except Exception as err:
189 logging.info("Error while trying to get flags for " + filename + " in compilation database")
190 logging.error(err)
191 return None
192
193
194 def FlagsForFile(filename, **kwargs):
195 client_data = kwargs['client_data']
196 root = client_data['getcwd()']
197
198 compilation_db_flags = flags_for_compilation_database(root, filename)
199 if compilation_db_flags:
200 final_flags = compilation_db_flags
201 else:
202 final_flags = BASE_FLAGS
203 include_flags = flags_for_include(root)
204 if include_flags:
205 final_flags = final_flags + include_flags
206
207 final_flags += generate_qt_flags()
208 final_flags += [
209 '-I', root,
210 '-I', root + '/include',
211 ]
212 return {
213 'flags': final_flags + EXTRA_FLAGS,
214 'do_cache': True
215 }