Skip to content

Commit 34e2c29

Browse files
committed
Add extra conf support to Clangd completer
1 parent 3b5d0bc commit 34e2c29

File tree

15 files changed

+509
-222
lines changed

15 files changed

+509
-222
lines changed

README.md

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,13 @@ non-semantic.
9797

9898
There are also several semantic engines in YCM. There's a libclang-based
9999
completer and [clangd][clangd]-based completer that both provide semantic
100-
completion for C-family languages. The [clangd][clangd]-based completer doesn't
101-
support extra conf; you must have a compilation database. [clangd][clangd]
102-
support is currently **experimental** and changes in the near future might break
103-
backwards compatibility. There's also a Jedi-based completer for semantic
104-
completion for Python, an OmniSharp-based completer for C#, a
105-
[Gocode][gocode]-based completer for Go (using [Godef][godef] for jumping to
106-
definitions), a TSServer-based completer for JavaScript and TypeScript, and a
107-
[jdt.ls][jdtls]-based server for Java. More will be added with time.
100+
completion for C-family languages. [clangd][clangd] support is currently
101+
**experimental** and changes in the near future might break backwards
102+
compatibility. There's also a Jedi-based completer for semantic completion for
103+
Python, an OmniSharp-based completer for C#, a [Gocode][gocode]-based completer
104+
for Go (using [Godef][godef] for jumping to definitions), a TSServer-based
105+
completer for JavaScript and TypeScript, and a [jdt.ls][jdtls]-based server for
106+
Java. More will be added with time.
108107

109108
There are also other completion engines, like the filepath completer (part of
110109
the identifier completer).
@@ -217,8 +216,8 @@ The `.ycm_extra_conf.py` module may define the following functions:
217216
#### `Settings( **kwargs )`
218217

219218
This function allows users to configure the language completers on a per project
220-
basis or globally. Currently, it is required by the C-family completer and
221-
optional for the Python completer. The following arguments can be retrieved from
219+
basis or globally. Currently, it is required by the libclang-based completer and
220+
optional for other completers. The following arguments can be retrieved from
222221
the `kwargs` dictionary and are common to all completers:
223222

224223
- `language`: an identifier of the completer that called the function. Its value
@@ -231,7 +230,7 @@ the `kwargs` dictionary and are common to all completers:
231230
language = kwargs[ 'language' ]
232231
if language == 'cfamily':
233232
return {
234-
# Settings for the C-family completer.
233+
# Settings for the libclang and clangd-based completer.
235234
}
236235
if language == 'python':
237236
return {
@@ -240,6 +239,8 @@ the `kwargs` dictionary and are common to all completers:
240239
return {}
241240
```
242241

242+
- `filename`: absolute path of the file currently edited.
243+
243244
- `client_data`: any additional data supplied by the client application.
244245
See the [YouCompleteMe documentation][extra-conf-vim-data-doc] for an
245246
example.
@@ -248,32 +249,32 @@ The return value is a dictionary whose content depends on the completer.
248249

249250
##### C-family settings
250251

251-
The `Settings` function is called by the C-family completer to get the compiler
252-
flags to use when compiling the current file. The absolute path of this file is
253-
accessible under the `filename` key of the `kwargs` dictionary.
254-
[clangd][clangd]-based completer doesn't support extra conf files. If you are
255-
using [clangd][clangd]-based completer, you must have a compilation database in
256-
your project's root or in one of the parent directories to provide compiler
257-
flags.
252+
The `Settings` function is called by the libclang and clangd-based completers to
253+
get the compiler flags to use when compiling the current file. The absolute path
254+
of this file is accessible under the `filename` key of the `kwargs` dictionary.
258255

259-
The return value expected by the completer is a dictionary containing the
256+
The return value expected by both completers is a dictionary containing the
260257
following items:
261258

262-
- `flags`: (mandatory) a list of compiler flags.
259+
- `flags`: (mandatory for libclang, optional for clangd) a list of compiler
260+
flags.
263261

264-
- `include_paths_relative_to_dir`: (optional) the directory to which the
265-
include paths in the list of flags are relative. Defaults to ycmd working
266-
directory.
267-
268-
- `override_filename`: (optional) a string indicating the name of the file to
269-
parse as the translation unit for the supplied file name. This fairly
270-
advanced feature allows for projects that use a 'unity'-style build, or
271-
for header files which depend on other includes in other files.
262+
- `include_paths_relative_to_dir`: (optional) the directory to which the include
263+
paths in the list of flags are relative. Defaults to ycmd working directory
264+
for the libclang completer and `.ycm_extra_conf.py`'s directory for the
265+
clangd completer.
272266

273267
- `do_cache`: (optional) a boolean indicating whether or not the result of
274268
this call (i.e. the list of flags) should be cached for this file name.
275269
Defaults to `True`. If unsure, the default is almost always correct.
276270

271+
The libclang-based completer also supports the following items:
272+
273+
- `override_filename`: (optional) a string indicating the name of the file to
274+
parse as the translation unit for the supplied file name. This fairly advanced
275+
feature allows for projects that use a 'unity'-style build, or for header
276+
files which depend on other includes in other files.
277+
277278
- `flags_ready`: (optional) a boolean indicating that the flags should be
278279
used. Defaults to `True`. If unsure, the default is almost always correct.
279280

@@ -393,7 +394,7 @@ License
393394
-------
394395

395396
This software is licensed under the [GPL v3 license][gpl].
396-
© 2015-2018 ycmd contributors
397+
© 2015-2019 ycmd contributors
397398

398399
[ycmd-users]: https://groups.google.com/forum/?hl=en#!forum/ycmd-users
399400
[ycm]: http://valloric.github.io/YouCompleteMe/

ycmd/completers/cpp/clangd_completer.py

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
import os
2727
import subprocess
2828

29-
from ycmd import responses
29+
from ycmd import extra_conf_store, responses
3030
from ycmd.completers.completer_utils import GetFileLines
31+
from ycmd.completers.cpp.flags import ( RemoveUnusedFlags,
32+
ShouldAllowWinStyleFlags )
3133
from ycmd.completers.language_server import simple_language_server_completer
3234
from ycmd.completers.language_server import language_server_completer
3335
from ycmd.completers.language_server import language_server_protocol as lsp
@@ -200,6 +202,27 @@ def ShouldEnableClangdCompleter( user_options ):
200202
return True
201203

202204

205+
def PrependCompilerToFlags( flags, enable_windows_style_flags ):
206+
"""Removes everything before the first flag and returns the remaining flags
207+
prepended with clangd."""
208+
for index, flag in enumerate( flags ):
209+
if ( flag.startswith( '-' ) or
210+
( enable_windows_style_flags and
211+
flag.startswith( '/' ) and
212+
not os.path.exists( flag ) ) ):
213+
flags = flags[ index: ]
214+
break
215+
return [ 'clang-tool' ] + flags
216+
217+
218+
def BuildCompilationCommand( flags, filepath ):
219+
"""Returns a compilation command from a list of flags and a file."""
220+
enable_windows_style_flags = ShouldAllowWinStyleFlags( flags )
221+
flags = PrependCompilerToFlags( flags, enable_windows_style_flags )
222+
flags = RemoveUnusedFlags( flags, filepath, enable_windows_style_flags )
223+
return flags + [ filepath ]
224+
225+
203226
class ClangdCompleter( simple_language_server_completer.SimpleLSPCompleter ):
204227
"""A LSP-based completer for C-family languages, powered by Clangd.
205228
@@ -214,6 +237,19 @@ def __init__( self, user_options ):
214237

215238
self._clangd_command = GetClangdCommand( user_options )
216239
self._use_ycmd_caching = user_options[ 'clangd_uses_ycmd_caching' ]
240+
self._flags_for_file = {}
241+
242+
self.RegisterOnFileReadyToParse( self._SendFlagsFromExtraConf )
243+
244+
245+
def _Reset( self ):
246+
with self._server_state_mutex:
247+
super( ClangdCompleter, self )._Reset()
248+
self._flags_for_file = {}
249+
250+
251+
def GetCompleterName( self ):
252+
return 'C-family'
217253

218254

219255
def GetServerName( self ):
@@ -224,6 +260,10 @@ def GetCommandLine( self ):
224260
return self._clangd_command
225261

226262

263+
def Language( self ):
264+
return 'cfamily'
265+
266+
227267
def SupportedFiletypes( self ):
228268
return ( 'c', 'cpp', 'objc', 'objcpp', 'cuda' )
229269

@@ -361,3 +401,45 @@ def GetDetailedDiagnostic( self, request_data ):
361401
minimum_distance = distance
362402

363403
return responses.BuildDisplayMessageResponse( message )
404+
405+
406+
def _SendFlagsFromExtraConf( self, request_data ):
407+
"""Reads the flags from the extra conf of the given request and sends them
408+
to Clangd as an entry of a compilation database using the
409+
'compilationDatabaseChanges' configuration."""
410+
filepath = request_data[ 'filepath' ]
411+
412+
with self._server_info_mutex:
413+
module = extra_conf_store.ModuleForSourceFile( filepath )
414+
if not module:
415+
return
416+
417+
settings = self.GetSettings( module, request_data )
418+
419+
if 'flags' not in settings:
420+
# No flags returned. Let Clangd find the flags.
421+
return
422+
423+
if settings.get( 'do_cache', True ) and filepath in self._flags_for_file:
424+
# Flags for this file have already been sent to Clangd.
425+
return
426+
427+
flags = settings[ 'flags' ]
428+
429+
self.GetConnection().SendNotification( lsp.DidChangeConfiguration( {
430+
'compilationDatabaseChanges': {
431+
filepath: {
432+
'compilationCommand': BuildCompilationCommand( flags, filepath ),
433+
'workingDirectory': settings.get( 'include_paths_relative_to_dir',
434+
self._project_directory )
435+
}
436+
}
437+
} ) )
438+
439+
self._flags_for_file[ filepath ] = flags
440+
441+
442+
def ExtraDebugItems( self, request_data ):
443+
return [ responses.DebugInfoItem(
444+
'Extra Configuration Flags',
445+
self._flags_for_file.get( request_data[ 'filepath' ], False ) ) ]

ycmd/completers/cpp/flags.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ def _ParseFlagsFromExtraConfOrDatabase( self,
161161
sanitized_flags = PrepareFlagsForClang( flags,
162162
filename,
163163
add_extra_clang_flags,
164-
_ShouldAllowWinStyleFlags( flags ) )
164+
ShouldAllowWinStyleFlags( flags ) )
165165

166166
if results.get( 'do_cache', True ):
167167
self.flags_for_file[ filename, client_data ] = sanitized_flags, filename
@@ -248,7 +248,7 @@ def _ExtractFlagsList( flags_for_file_output ):
248248
return [ ToUnicode( x ) for x in flags_for_file_output[ 'flags' ] ]
249249

250250

251-
def _ShouldAllowWinStyleFlags( flags ):
251+
def ShouldAllowWinStyleFlags( flags ):
252252
if OnWindows():
253253
# Iterate in reverse because we only care
254254
# about the last occurrence of --driver-mode flag.
@@ -302,7 +302,7 @@ def PrepareFlagsForClang( flags,
302302
enable_windows_style_flags = False ):
303303
flags = _AddLanguageFlagWhenAppropriate( flags, enable_windows_style_flags )
304304
flags = _RemoveXclangFlags( flags )
305-
flags = _RemoveUnusedFlags( flags, filename, enable_windows_style_flags )
305+
flags = RemoveUnusedFlags( flags, filename, enable_windows_style_flags )
306306
if add_extra_clang_flags:
307307
# This flag tells libclang where to find the builtin includes.
308308
flags.append( '-resource-dir=' + CLANG_RESOURCE_DIR )
@@ -407,7 +407,7 @@ def _AddLanguageFlagWhenAppropriate( flags, enable_windows_style_flags ):
407407
return flags
408408

409409

410-
def _RemoveUnusedFlags( flags, filename, enable_windows_style_flags ):
410+
def RemoveUnusedFlags( flags, filename, enable_windows_style_flags ):
411411
"""Given an iterable object that produces strings (flags for Clang), removes
412412
the '-c' and '-o' options that Clang does not like to see when it's producing
413413
completions for a file. Same for '-MD' etc.
@@ -622,7 +622,7 @@ def _MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
622622
new_flags = []
623623
make_next_absolute = False
624624
path_flags = ( PATH_FLAGS + INCLUDE_FLAGS_WIN_STYLE
625-
if _ShouldAllowWinStyleFlags( flags )
625+
if ShouldAllowWinStyleFlags( flags )
626626
else PATH_FLAGS )
627627
for flag in flags:
628628
new_flag = flag
@@ -685,7 +685,7 @@ def UserIncludePaths( user_flags, filename ):
685685
'-isystem': include_paths,
686686
'-F': framework_paths,
687687
'-iframework': framework_paths }
688-
if _ShouldAllowWinStyleFlags( user_flags ):
688+
if ShouldAllowWinStyleFlags( user_flags ):
689689
include_flags[ '/I' ] = include_paths
690690

691691
try:

ycmd/completers/language_server/language_server_completer.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (C) 2017-2018 ycmd contributors
1+
# Copyright (C) 2017-2019 ycmd contributors
22
#
33
# This file is part of ycmd.
44
#
@@ -22,6 +22,7 @@
2222
# Not installing aliases from python-future; it's unreliable and slow.
2323
from builtins import * # noqa
2424

25+
from functools import partial
2526
from future.utils import iteritems, iterkeys
2627
import abc
2728
import collections
@@ -741,6 +742,9 @@ def __init__( self, user_options ):
741742
# cached query is a prefix of the subsequent queries.
742743
self._completions_cache = LanguageServerCompletionsCache()
743744

745+
self._on_file_ready_to_parse_handlers = []
746+
self.RegisterOnFileReadyToParse( self._UpdateServerWithFileContents )
747+
744748

745749
def ServerReset( self ):
746750
"""Clean up internal state related to the running server instance.
@@ -1087,10 +1091,12 @@ def _DiscoverSubcommandSupport( self, commands ):
10871091
return subcommands_map
10881092

10891093

1090-
def _GetSettings( self, module, client_data ):
1094+
def GetSettings( self, module, request_data ):
10911095
if hasattr( module, 'Settings' ):
1092-
settings = module.Settings( language = self.Language(),
1093-
client_data = client_data )
1096+
settings = module.Settings(
1097+
language = self.Language(),
1098+
filename = request_data[ 'filepath' ],
1099+
client_data = request_data[ 'extra_conf_data' ] )
10941100
if settings is not None:
10951101
return settings
10961102

@@ -1102,7 +1108,7 @@ def _GetSettings( self, module, client_data ):
11021108
def _GetSettingsFromExtraConf( self, request_data ):
11031109
module = extra_conf_store.ModuleForSourceFile( request_data[ 'filepath' ] )
11041110
if module:
1105-
settings = self._GetSettings( module, request_data[ 'extra_conf_data' ] )
1111+
settings = self.GetSettings( module, request_data )
11061112
self._settings = settings.get( 'ls' ) or {}
11071113
# Only return the dir if it was found in the paths; we don't want to use
11081114
# the path of the global extra conf as a project root dir.
@@ -1139,17 +1145,19 @@ def OnFileReadyToParse( self, request_data ):
11391145
if not self.ServerIsHealthy():
11401146
return
11411147

1142-
# If we haven't finished initializing yet, we need to queue up a call to
1143-
# _UpdateServerWithFileContents. This ensures that the server is up to date
1144-
# as soon as we are able to send more messages. This is important because
1145-
# server start up can be quite slow and we must not block the user, while we
1146-
# must keep the server synchronized.
1148+
# If we haven't finished initializing yet, we need to queue up all functions
1149+
# registered on the FileReadyToParse event and in particular
1150+
# _UpdateServerWithFileContents in reverse order of registration. This
1151+
# ensures that the server is up to date as soon as we are able to send more
1152+
# messages. This is important because server start up can be quite slow and
1153+
# we must not block the user, while we must keep the server synchronized.
11471154
if not self._initialize_event.is_set():
1148-
self._OnInitializeComplete(
1149-
lambda self: self._UpdateServerWithFileContents( request_data ) )
1155+
for handler in reversed( self._on_file_ready_to_parse_handlers ):
1156+
self._OnInitializeComplete( partial( handler, request_data ) )
11501157
return
11511158

1152-
self._UpdateServerWithFileContents( request_data )
1159+
for handler in reversed( self._on_file_ready_to_parse_handlers ):
1160+
handler( request_data )
11531161

11541162
# Return the latest diagnostics that we have received.
11551163
#
@@ -1437,8 +1445,8 @@ def OnBufferUnload( self, request_data ):
14371445
# server start up can be quite slow and we must not block the user, while we
14381446
# must keep the server synchronized.
14391447
if not self._initialize_event.is_set():
1440-
self._OnInitializeComplete(
1441-
lambda self: self._PurgeFileFromServer( request_data[ 'filepath' ] ) )
1448+
self._OnInitializeComplete( partial( self._PurgeFileFromServer,
1449+
request_data[ 'filepath' ] ) )
14421450
return
14431451

14441452
self._PurgeFileFromServer( request_data[ 'filepath' ] )
@@ -1595,7 +1603,7 @@ def _HandleInitializeInPollThread( self, response ):
15951603
# exchange. Typically, this will be calls to _UpdateServerWithFileContents
15961604
# or something that occurred while we were waiting.
15971605
for handler in self._on_initialize_complete_handlers:
1598-
handler( self )
1606+
handler()
15991607

16001608
self._on_initialize_complete_handlers = []
16011609

@@ -1609,6 +1617,10 @@ def _OnInitializeComplete( self, handler ):
16091617
self._on_initialize_complete_handlers.append( handler )
16101618

16111619

1620+
def RegisterOnFileReadyToParse( self, handler ):
1621+
self._on_file_ready_to_parse_handlers.append( handler )
1622+
1623+
16121624
def GetHoverResponse( self, request_data ):
16131625
"""Return the raw LSP response to the hover request for the supplied
16141626
context. Implementations can use this for e.g. GetDoc and GetType requests,

0 commit comments

Comments
 (0)