Skip to content

Commit 3c6e5e0

Browse files
committed
Merge branch 'clangd-completer-extra-conf' into signature-help
2 parents 7183d45 + a3a5f6e commit 3c6e5e0

File tree

15 files changed

+503
-211
lines changed

15 files changed

+503
-211
lines changed

README.md

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,13 @@ non-semantic.
9696

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

109107
There are also other completion engines, like the filepath completer (part of
110108
the identifier completer).
@@ -217,8 +215,8 @@ The `.ycm_extra_conf.py` module may define the following functions:
217215
#### `Settings( **kwargs )`
218216

219217
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
218+
basis or globally. Currently, it is required by the libclang-based completer and
219+
optional for other completers. The following arguments can be retrieved from
222220
the `kwargs` dictionary and are common to all completers:
223221

224222
- `language`: an identifier of the completer that called the function. Its value
@@ -231,7 +229,7 @@ the `kwargs` dictionary and are common to all completers:
231229
language = kwargs[ 'language' ]
232230
if language == 'cfamily':
233231
return {
234-
# Settings for the C-family completer.
232+
# Settings for the libclang and clangd-based completer.
235233
}
236234
if language == 'python':
237235
return {
@@ -240,6 +238,8 @@ the `kwargs` dictionary and are common to all completers:
240238
return {}
241239
```
242240

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

249249
##### C-family settings
250250

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.
251+
The `Settings` function is called by the libclang and clangd-based completers to
252+
get the compiler flags to use when compiling the current file. The absolute path
253+
of this file is accessible under the `filename` key of the `kwargs` dictionary.
258254

259-
The return value expected by the completer is a dictionary containing the
255+
The return value expected by both completers is a dictionary containing the
260256
following items:
261257

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

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.
261+
- `include_paths_relative_to_dir`: (optional) the directory to which the include
262+
paths in the list of flags are relative. Defaults to ycmd working directory
263+
for the libclang completer and `.ycm_extra_conf.py`'s directory for the
264+
clangd completer.
272265

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

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

ycmd/completers/cpp/clangd_completer.py

Lines changed: 85 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,21 @@ 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(
243+
lambda self, request_data: self._SendFlagsFromExtraConf( request_data )
244+
)
245+
246+
247+
def _Reset( self ):
248+
with self._server_state_mutex:
249+
super( ClangdCompleter, self )._Reset()
250+
self._flags_for_file = {}
251+
252+
253+
def GetCompleterName( self ):
254+
return 'C-family'
217255

218256

219257
def GetServerName( self ):
@@ -224,6 +262,10 @@ def GetCommandLine( self ):
224262
return self._clangd_command
225263

226264

265+
def Language( self ):
266+
return 'cfamily'
267+
268+
227269
def SupportedFiletypes( self ):
228270
return ( 'c', 'cpp', 'objc', 'objcpp', 'cuda' )
229271

@@ -361,3 +403,45 @@ def GetDetailedDiagnostic( self, request_data ):
361403
minimum_distance = distance
362404

363405
return responses.BuildDisplayMessageResponse( message )
406+
407+
408+
def _SendFlagsFromExtraConf( self, request_data ):
409+
"""Reads the flags from the extra conf of the given request and sends them
410+
to Clangd as an entry of a compilation database using the
411+
'compilationDatabaseChanges' configuration."""
412+
filepath = request_data[ 'filepath' ]
413+
414+
with self._server_info_mutex:
415+
module = extra_conf_store.ModuleForSourceFile( filepath )
416+
if not module:
417+
return
418+
419+
settings = self.GetSettings( module, request_data )
420+
421+
if 'flags' not in settings:
422+
# No flags returned. Let Clangd find the flags.
423+
return
424+
425+
if settings.get( 'do_cache', True ) and filepath in self._flags_for_file:
426+
# Flags for this file have already been sent to Clangd.
427+
return
428+
429+
flags = settings[ 'flags' ]
430+
431+
self.GetConnection().SendNotification( lsp.DidChangeConfiguration( {
432+
'compilationDatabaseChanges': {
433+
filepath: {
434+
'compilationCommand': BuildCompilationCommand( flags, filepath ),
435+
'workingDirectory': settings.get( 'include_paths_relative_to_dir',
436+
self._project_directory )
437+
}
438+
}
439+
} ) )
440+
441+
self._flags_for_file[ filepath ] = flags
442+
443+
444+
def ExtraDebugItems( self, request_data ):
445+
return [ responses.DebugInfoItem(
446+
'Extra Configuration Flags',
447+
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 & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -745,6 +746,12 @@ def __init__( self, user_options ):
745746
self._completer_name = self.__class__.__name__.replace( 'Completer', '' )
746747
self._language = self._completer_name.lower()
747748

749+
self._on_file_ready_to_parse_handlers = []
750+
self.RegisterOnFileReadyToParse(
751+
lambda self, request_data:
752+
self._UpdateServerWithFileContents( request_data )
753+
)
754+
748755

749756
def ServerReset( self ):
750757
"""Clean up internal state related to the running server instance.
@@ -1120,10 +1127,12 @@ def _DiscoverSubcommandSupport( self, commands ):
11201127
return subcommands_map
11211128

11221129

1123-
def _GetSettings( self, module, client_data ):
1130+
def GetSettings( self, module, request_data ):
11241131
if hasattr( module, 'Settings' ):
1125-
settings = module.Settings( language = self.Language(),
1126-
client_data = client_data )
1132+
settings = module.Settings(
1133+
language = self.Language(),
1134+
filename = request_data[ 'filepath' ],
1135+
client_data = request_data[ 'extra_conf_data' ] )
11271136
if settings is not None:
11281137
return settings
11291138

@@ -1135,7 +1144,7 @@ def _GetSettings( self, module, client_data ):
11351144
def _GetSettingsFromExtraConf( self, request_data ):
11361145
module = extra_conf_store.ModuleForSourceFile( request_data[ 'filepath' ] )
11371146
if module:
1138-
settings = self._GetSettings( module, request_data[ 'extra_conf_data' ] )
1147+
settings = self.GetSettings( module, request_data )
11391148
self._settings = settings.get( 'ls' ) or {}
11401149
# Only return the dir if it was found in the paths; we don't want to use
11411150
# the path of the global extra conf as a project root dir.
@@ -1172,17 +1181,20 @@ def OnFileReadyToParse( self, request_data ):
11721181
if not self.ServerIsHealthy():
11731182
return
11741183

1175-
# If we haven't finished initializing yet, we need to queue up a call to
1176-
# _UpdateServerWithFileContents. This ensures that the server is up to date
1177-
# as soon as we are able to send more messages. This is important because
1178-
# server start up can be quite slow and we must not block the user, while we
1179-
# must keep the server synchronized.
1184+
# If we haven't finished initializing yet, we need to queue up all functions
1185+
# registered on the FileReadyToParse event and in particular
1186+
# _UpdateServerWithFileContents in reverse order of registration. This
1187+
# ensures that the server is up to date as soon as we are able to send more
1188+
# messages. This is important because server start up can be quite slow and
1189+
# we must not block the user, while we must keep the server synchronized.
11801190
if not self._initialize_event.is_set():
1181-
self._OnInitializeComplete(
1182-
lambda self: self._UpdateServerWithFileContents( request_data ) )
1191+
for handler in reversed( self._on_file_ready_to_parse_handlers ):
1192+
self._OnInitializeComplete( partial( handler,
1193+
request_data = request_data ) )
11831194
return
11841195

1185-
self._UpdateServerWithFileContents( request_data )
1196+
for handler in reversed( self._on_file_ready_to_parse_handlers ):
1197+
handler( self, request_data )
11861198

11871199
# Return the latest diagnostics that we have received.
11881200
#
@@ -1667,6 +1679,10 @@ def _OnInitializeComplete( self, handler ):
16671679
self._on_initialize_complete_handlers.append( handler )
16681680

16691681

1682+
def RegisterOnFileReadyToParse( self, handler ):
1683+
self._on_file_ready_to_parse_handlers.append( handler )
1684+
1685+
16701686
def GetHoverResponse( self, request_data ):
16711687
"""Return the raw LSP response to the hover request for the supplied
16721688
context. Implementations can use this for e.g. GetDoc and GetType requests,

ycmd/completers/language_server/simple_language_server_completer.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
class SimpleLSPCompleter( lsc.LanguageServerCompleter ):
3636
@abc.abstractmethod
3737
def GetServerName( self ):
38-
pass
38+
pass # pragma: no cover
3939

4040

4141
def GetServerEnvironment( self ):
@@ -44,7 +44,7 @@ def GetServerEnvironment( self ):
4444

4545
@abc.abstractmethod
4646
def GetCommandLine( self ):
47-
pass
47+
pass # pragma: no cover
4848

4949

5050
def GetCustomSubcommands( self ):
@@ -76,13 +76,18 @@ def GetConnection( self ):
7676
return self._connection
7777

7878

79+
def ExtraDebugItems( self, request_data ):
80+
return []
81+
82+
7983
def DebugInfo( self, request_data ):
8084
with self._server_state_mutex:
85+
extras = self.CommonDebugItems() + self.ExtraDebugItems( request_data )
8186
server = responses.DebugInfoServer( name = self.GetServerName(),
8287
handle = self._server_handle,
8388
executable = self.GetCommandLine(),
8489
logfiles = [ self._stderr_file ],
85-
extras = self.CommonDebugItems() )
90+
extras = extras )
8691

8792
return responses.BuildDebugInfoResponse( name = self.GetCompleterName(),
8893
servers = [ server ] )

0 commit comments

Comments
 (0)