youcompleteme.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. # Copyright (C) 2011, 2012 Google Inc.
  2. #
  3. # This file is part of YouCompleteMe.
  4. #
  5. # YouCompleteMe is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # YouCompleteMe is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  17. from __future__ import unicode_literals
  18. from __future__ import print_function
  19. from __future__ import division
  20. from __future__ import absolute_import
  21. from future import standard_library
  22. standard_library.install_aliases()
  23. from builtins import * # noqa
  24. from future.utils import iteritems
  25. import os
  26. import vim
  27. import tempfile
  28. import json
  29. import re
  30. import signal
  31. import base64
  32. from subprocess import PIPE
  33. from ycm import paths, vimsupport
  34. from ycmd import utils
  35. from ycmd.request_wrap import RequestWrap
  36. from ycm.diagnostic_interface import DiagnosticInterface
  37. from ycm.omni_completer import OmniCompleter
  38. from ycm import syntax_parse
  39. from ycm.client.ycmd_keepalive import YcmdKeepalive
  40. from ycm.client.base_request import BaseRequest, BuildRequestData
  41. from ycm.client.completer_available_request import SendCompleterAvailableRequest
  42. from ycm.client.command_request import SendCommandRequest
  43. from ycm.client.completion_request import ( CompletionRequest,
  44. ConvertCompletionDataToVimData )
  45. from ycm.client.omni_completion_request import OmniCompletionRequest
  46. from ycm.client.event_notification import ( SendEventNotificationAsync,
  47. EventNotification )
  48. from ycmd.responses import ServerError
  49. try:
  50. from UltiSnips import UltiSnips_Manager
  51. USE_ULTISNIPS_DATA = True
  52. except ImportError:
  53. USE_ULTISNIPS_DATA = False
  54. def PatchNoProxy():
  55. current_value = os.environ.get('no_proxy', '')
  56. additions = '127.0.0.1,localhost'
  57. os.environ['no_proxy'] = ( additions if not current_value
  58. else current_value + ',' + additions )
  59. # We need this so that Requests doesn't end up using the local HTTP proxy when
  60. # talking to ycmd. Users should actually be setting this themselves when
  61. # configuring a proxy server on their machine, but most don't know they need to
  62. # or how to do it, so we do it for them.
  63. # Relevant issues:
  64. # https://github.com/Valloric/YouCompleteMe/issues/641
  65. # https://github.com/kennethreitz/requests/issues/879
  66. PatchNoProxy()
  67. # Force the Python interpreter embedded in Vim (in which we are running) to
  68. # ignore the SIGINT signal. This helps reduce the fallout of a user pressing
  69. # Ctrl-C in Vim.
  70. signal.signal( signal.SIGINT, signal.SIG_IGN )
  71. HMAC_SECRET_LENGTH = 16
  72. SERVER_CRASH_MESSAGE_STDERR_FILE = (
  73. "The ycmd server SHUT DOWN (restart with ':YcmRestartServer'). "
  74. "Run ':YcmToggleLogs stderr' to check the logs." )
  75. SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED = (
  76. "The ycmd server SHUT DOWN (restart with ':YcmRestartServer'). "
  77. "Logfile was deleted; set 'g:ycm_server_keep_logfiles' to see errors "
  78. "in the future." )
  79. SERVER_IDLE_SUICIDE_SECONDS = 10800 # 3 hours
  80. DIAGNOSTIC_UI_FILETYPES = set( [ 'cpp', 'cs', 'c', 'objc', 'objcpp' ] )
  81. class YouCompleteMe( object ):
  82. def __init__( self, user_options ):
  83. self._user_options = user_options
  84. self._user_notified_about_crash = False
  85. self._diag_interface = DiagnosticInterface( user_options )
  86. self._omnicomp = OmniCompleter( user_options )
  87. self._latest_file_parse_request = None
  88. self._latest_completion_request = None
  89. self._latest_diagnostics = []
  90. self._server_stdout = None
  91. self._server_stderr = None
  92. self._server_popen = None
  93. self._filetypes_with_keywords_loaded = set()
  94. self._ycmd_keepalive = YcmdKeepalive()
  95. self._SetupServer()
  96. self._ycmd_keepalive.Start()
  97. self._complete_done_hooks = {
  98. 'cs': lambda self: self._OnCompleteDone_Csharp()
  99. }
  100. def _SetupServer( self ):
  101. self._available_completers = {}
  102. server_port = utils.GetUnusedLocalhostPort()
  103. # The temp options file is deleted by ycmd during startup
  104. with tempfile.NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
  105. hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
  106. options_dict = dict( self._user_options )
  107. options_dict[ 'hmac_secret' ] = utils.ToUnicode(
  108. base64.b64encode( hmac_secret ) )
  109. json.dump( options_dict, options_file )
  110. options_file.flush()
  111. args = [ paths.PathToPythonInterpreter(),
  112. paths.PathToServerScript(),
  113. '--port={0}'.format( server_port ),
  114. '--options_file={0}'.format( options_file.name ),
  115. '--log={0}'.format( self._user_options[ 'server_log_level' ] ),
  116. '--idle_suicide_seconds={0}'.format(
  117. SERVER_IDLE_SUICIDE_SECONDS )]
  118. filename_format = os.path.join( utils.PathToCreatedTempDir(),
  119. 'server_{port}_{std}.log' )
  120. self._server_stdout = filename_format.format( port = server_port,
  121. std = 'stdout' )
  122. self._server_stderr = filename_format.format( port = server_port,
  123. std = 'stderr' )
  124. args.append( '--stdout={0}'.format( self._server_stdout ) )
  125. args.append( '--stderr={0}'.format( self._server_stderr ) )
  126. if self._user_options[ 'server_keep_logfiles' ]:
  127. args.append( '--keep_logfiles' )
  128. self._server_popen = utils.SafePopen( args, stdin_windows = PIPE,
  129. stdout = PIPE, stderr = PIPE )
  130. BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
  131. BaseRequest.hmac_secret = hmac_secret
  132. self._NotifyUserIfServerCrashed()
  133. def IsServerAlive( self ):
  134. returncode = self._server_popen.poll()
  135. # When the process hasn't finished yet, poll() returns None.
  136. return returncode is None
  137. def _NotifyUserIfServerCrashed( self ):
  138. if self._user_notified_about_crash or self.IsServerAlive():
  139. return
  140. self._user_notified_about_crash = True
  141. try:
  142. vimsupport.CheckFilename( self._server_stderr )
  143. vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_STDERR_FILE )
  144. except RuntimeError:
  145. vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED )
  146. def ServerPid( self ):
  147. if not self._server_popen:
  148. return -1
  149. return self._server_popen.pid
  150. def _ServerCleanup( self ):
  151. if self.IsServerAlive():
  152. self._server_popen.terminate()
  153. def RestartServer( self ):
  154. self._CloseLogs()
  155. vimsupport.PostVimMessage( 'Restarting ycmd server...' )
  156. self._user_notified_about_crash = False
  157. self._ServerCleanup()
  158. self._SetupServer()
  159. def CreateCompletionRequest( self, force_semantic = False ):
  160. request_data = BuildRequestData()
  161. if ( not self.NativeFiletypeCompletionAvailable() and
  162. self.CurrentFiletypeCompletionEnabled() ):
  163. wrapped_request_data = RequestWrap( request_data )
  164. if self._omnicomp.ShouldUseNow( wrapped_request_data ):
  165. self._latest_completion_request = OmniCompletionRequest(
  166. self._omnicomp, wrapped_request_data )
  167. return self._latest_completion_request
  168. request_data[ 'working_dir' ] = os.getcwd()
  169. self._AddExtraConfDataIfNeeded( request_data )
  170. if force_semantic:
  171. request_data[ 'force_semantic' ] = True
  172. self._latest_completion_request = CompletionRequest( request_data )
  173. return self._latest_completion_request
  174. def SendCommandRequest( self, arguments, completer ):
  175. if self.IsServerAlive():
  176. return SendCommandRequest( arguments, completer )
  177. def GetDefinedSubcommands( self ):
  178. if self.IsServerAlive():
  179. try:
  180. return BaseRequest.PostDataToHandler( BuildRequestData(),
  181. 'defined_subcommands' )
  182. except ServerError:
  183. return []
  184. else:
  185. return []
  186. def GetCurrentCompletionRequest( self ):
  187. return self._latest_completion_request
  188. def GetOmniCompleter( self ):
  189. return self._omnicomp
  190. def FiletypeCompleterExistsForFiletype( self, filetype ):
  191. try:
  192. return self._available_completers[ filetype ]
  193. except KeyError:
  194. pass
  195. if not self.IsServerAlive():
  196. return False
  197. exists_completer = SendCompleterAvailableRequest( filetype )
  198. if exists_completer is None:
  199. return False
  200. self._available_completers[ filetype ] = exists_completer
  201. return exists_completer
  202. def NativeFiletypeCompletionAvailable( self ):
  203. return any( [ self.FiletypeCompleterExistsForFiletype( x ) for x in
  204. vimsupport.CurrentFiletypes() ] )
  205. def NativeFiletypeCompletionUsable( self ):
  206. return ( self.CurrentFiletypeCompletionEnabled() and
  207. self.NativeFiletypeCompletionAvailable() )
  208. def OnFileReadyToParse( self ):
  209. self._omnicomp.OnFileReadyToParse( None )
  210. if not self.IsServerAlive():
  211. self._NotifyUserIfServerCrashed()
  212. extra_data = {}
  213. self._AddTagsFilesIfNeeded( extra_data )
  214. self._AddSyntaxDataIfNeeded( extra_data )
  215. self._AddExtraConfDataIfNeeded( extra_data )
  216. self._latest_file_parse_request = EventNotification( 'FileReadyToParse',
  217. extra_data )
  218. self._latest_file_parse_request.Start()
  219. def OnBufferUnload( self, deleted_buffer_file ):
  220. if not self.IsServerAlive():
  221. return
  222. SendEventNotificationAsync( 'BufferUnload',
  223. { 'unloaded_buffer': deleted_buffer_file } )
  224. def OnBufferVisit( self ):
  225. if not self.IsServerAlive():
  226. return
  227. extra_data = {}
  228. _AddUltiSnipsDataIfNeeded( extra_data )
  229. SendEventNotificationAsync( 'BufferVisit', extra_data )
  230. def OnInsertLeave( self ):
  231. if not self.IsServerAlive():
  232. return
  233. SendEventNotificationAsync( 'InsertLeave' )
  234. def OnCursorMoved( self ):
  235. self._diag_interface.OnCursorMoved()
  236. def OnVimLeave( self ):
  237. self._ServerCleanup()
  238. def OnCurrentIdentifierFinished( self ):
  239. if not self.IsServerAlive():
  240. return
  241. SendEventNotificationAsync( 'CurrentIdentifierFinished' )
  242. def OnCompleteDone( self ):
  243. complete_done_actions = self.GetCompleteDoneHooks()
  244. for action in complete_done_actions:
  245. action(self)
  246. def GetCompleteDoneHooks( self ):
  247. filetypes = vimsupport.CurrentFiletypes()
  248. for key, value in iteritems( self._complete_done_hooks ):
  249. if key in filetypes:
  250. yield value
  251. def GetCompletionsUserMayHaveCompleted( self ):
  252. latest_completion_request = self.GetCurrentCompletionRequest()
  253. if not latest_completion_request or not latest_completion_request.Done():
  254. return []
  255. completions = latest_completion_request.RawResponse()
  256. result = self._FilterToMatchingCompletions( completions, True )
  257. result = list( result )
  258. if result:
  259. return result
  260. if self._HasCompletionsThatCouldBeCompletedWithMoreText( completions ):
  261. # Since the way that YCM works leads to CompleteDone called on every
  262. # character, return blank if the completion might not be done. This won't
  263. # match if the completion is ended with typing a non-keyword character.
  264. return []
  265. result = self._FilterToMatchingCompletions( completions, False )
  266. return list( result )
  267. def _FilterToMatchingCompletions( self, completions, full_match_only ):
  268. self._PatchBasedOnVimVersion()
  269. return self._FilterToMatchingCompletions( completions, full_match_only)
  270. def _HasCompletionsThatCouldBeCompletedWithMoreText( self, completions ):
  271. self._PatchBasedOnVimVersion()
  272. return self._HasCompletionsThatCouldBeCompletedWithMoreText( completions )
  273. def _PatchBasedOnVimVersion( self ):
  274. if vimsupport.VimVersionAtLeast( "7.4.774" ):
  275. self._HasCompletionsThatCouldBeCompletedWithMoreText = \
  276. self._HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim
  277. self._FilterToMatchingCompletions = \
  278. self._FilterToMatchingCompletions_NewerVim
  279. else:
  280. self._FilterToMatchingCompletions = \
  281. self._FilterToMatchingCompletions_OlderVim
  282. self._HasCompletionsThatCouldBeCompletedWithMoreText = \
  283. self._HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim
  284. def _FilterToMatchingCompletions_NewerVim( self, completions,
  285. full_match_only ):
  286. """ Filter to completions matching the item Vim said was completed """
  287. completed = vimsupport.GetVariableValue( 'v:completed_item' )
  288. for completion in completions:
  289. item = ConvertCompletionDataToVimData( completion )
  290. match_keys = ( [ "word", "abbr", "menu", "info" ] if full_match_only
  291. else [ 'word' ] )
  292. matcher = lambda key: completed.get( key, "" ) == item.get( key, "" )
  293. if all( [ matcher( i ) for i in match_keys ] ):
  294. yield completion
  295. def _FilterToMatchingCompletions_OlderVim( self, completions,
  296. full_match_only ):
  297. """ Filter to completions matching the buffer text """
  298. if full_match_only:
  299. return # Only supported in 7.4.774+
  300. # No support for multiple line completions
  301. text = vimsupport.TextBeforeCursor()
  302. for completion in completions:
  303. word = completion[ "insertion_text" ]
  304. # Trim complete-ending character if needed
  305. text = re.sub( r"[^a-zA-Z0-9_]$", "", text )
  306. buffer_text = text[ -1 * len( word ) : ]
  307. if buffer_text == word:
  308. yield completion
  309. def _HasCompletionsThatCouldBeCompletedWithMoreText_NewerVim( self,
  310. completions ):
  311. completed_item = vimsupport.GetVariableValue( 'v:completed_item' )
  312. if not completed_item:
  313. return False
  314. completed_word = completed_item[ 'word' ]
  315. if not completed_word:
  316. return False
  317. # Sometime CompleteDone is called after the next character is inserted
  318. # If so, use inserted character to filter possible completions further
  319. text = vimsupport.TextBeforeCursor()
  320. reject_exact_match = True
  321. if text and text[ -1 ] != completed_word[ -1 ]:
  322. reject_exact_match = False
  323. completed_word += text[ -1 ]
  324. for completion in completions:
  325. word = ConvertCompletionDataToVimData( completion )[ 'word' ]
  326. if reject_exact_match and word == completed_word:
  327. continue
  328. if word.startswith( completed_word ):
  329. return True
  330. return False
  331. def _HasCompletionsThatCouldBeCompletedWithMoreText_OlderVim( self,
  332. completions ):
  333. # No support for multiple line completions
  334. text = vimsupport.TextBeforeCursor()
  335. for completion in completions:
  336. word = ConvertCompletionDataToVimData( completion )[ 'word' ]
  337. for i in range( 1, len( word ) - 1 ): # Excluding full word
  338. if text[ -1 * i : ] == word[ : i ]:
  339. return True
  340. return False
  341. def _OnCompleteDone_Csharp( self ):
  342. completions = self.GetCompletionsUserMayHaveCompleted()
  343. namespaces = [ self._GetRequiredNamespaceImport( c )
  344. for c in completions ]
  345. namespaces = [ n for n in namespaces if n ]
  346. if not namespaces:
  347. return
  348. if len( namespaces ) > 1:
  349. choices = [ "{0} {1}".format( i + 1, n )
  350. for i,n in enumerate( namespaces ) ]
  351. choice = vimsupport.PresentDialog( "Insert which namespace:", choices )
  352. if choice < 0:
  353. return
  354. namespace = namespaces[ choice ]
  355. else:
  356. namespace = namespaces[ 0 ]
  357. vimsupport.InsertNamespace( namespace )
  358. def _GetRequiredNamespaceImport( self, completion ):
  359. if ( "extra_data" not in completion
  360. or "required_namespace_import" not in completion[ "extra_data" ] ):
  361. return None
  362. return completion[ "extra_data" ][ "required_namespace_import" ]
  363. def GetErrorCount( self ):
  364. return self._diag_interface.GetErrorCount()
  365. def GetWarningCount( self ):
  366. return self._diag_interface.GetWarningCount()
  367. def DiagnosticUiSupportedForCurrentFiletype( self ):
  368. return any( [ x in DIAGNOSTIC_UI_FILETYPES
  369. for x in vimsupport.CurrentFiletypes() ] )
  370. def ShouldDisplayDiagnostics( self ):
  371. return bool( self._user_options[ 'show_diagnostics_ui' ] and
  372. self.DiagnosticUiSupportedForCurrentFiletype() )
  373. def PopulateLocationListWithLatestDiagnostics( self ):
  374. # Do nothing if loc list is already populated by diag_interface
  375. if not self._user_options[ 'always_populate_location_list' ]:
  376. self._diag_interface.PopulateLocationList( self._latest_diagnostics )
  377. return bool( self._latest_diagnostics )
  378. def UpdateDiagnosticInterface( self ):
  379. self._diag_interface.UpdateWithNewDiagnostics( self._latest_diagnostics )
  380. def FileParseRequestReady( self, block = False ):
  381. return bool( self._latest_file_parse_request and
  382. ( block or self._latest_file_parse_request.Done() ) )
  383. def HandleFileParseRequest( self, block = False ):
  384. # Order is important here:
  385. # FileParseRequestReady has a low cost, while
  386. # NativeFiletypeCompletionUsable is a blocking server request
  387. if ( self.FileParseRequestReady( block ) and
  388. self.NativeFiletypeCompletionUsable() ):
  389. if self.ShouldDisplayDiagnostics():
  390. self._latest_diagnostics = self._latest_file_parse_request.Response()
  391. self.UpdateDiagnosticInterface()
  392. else:
  393. # YCM client has a hard-coded list of filetypes which are known
  394. # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype()
  395. #
  396. # For filetypes which don't support diagnostics, we just want to check
  397. # the _latest_file_parse_request for any exception or UnknownExtraConf
  398. # response, to allow the server to raise configuration warnings, etc.
  399. # to the user. We ignore any other supplied data.
  400. self._latest_file_parse_request.Response()
  401. # We set the file parse request to None because we want to prevent
  402. # repeated issuing of the same warnings/errors/prompts. Setting this to
  403. # None makes FileParseRequestReady return False until the next
  404. # request is created.
  405. #
  406. # Note: it is the server's responsibility to determine the frequency of
  407. # error/warning/prompts when receiving a FileReadyToParse event, but
  408. # it our responsibility to ensure that we only apply the
  409. # warning/error/prompt received once (for each event).
  410. self._latest_file_parse_request = None
  411. def ShowDetailedDiagnostic( self ):
  412. if not self.IsServerAlive():
  413. return
  414. try:
  415. debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
  416. 'detailed_diagnostic' )
  417. if 'message' in debug_info:
  418. vimsupport.EchoText( debug_info[ 'message' ] )
  419. except ServerError as e:
  420. vimsupport.PostVimMessage( str( e ) )
  421. def DebugInfo( self ):
  422. if self.IsServerAlive():
  423. debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
  424. 'debug_info' )
  425. else:
  426. debug_info = 'Server crashed, no debug info from server'
  427. debug_info += '\nServer running at: {0}'.format(
  428. BaseRequest.server_location )
  429. debug_info += '\nServer process ID: {0}'.format( self._server_popen.pid )
  430. if self._server_stderr or self._server_stdout:
  431. debug_info += '\nServer logfiles:\n {0}\n {1}'.format(
  432. self._server_stdout,
  433. self._server_stderr )
  434. return debug_info
  435. def _OpenLogs( self, stdout = True, stderr = True ):
  436. # Open log files in a horizontal window with the same behavior as the
  437. # preview window (same height and winfixheight enabled). Automatically
  438. # watch for changes. Set the cursor position at the end of the file.
  439. options = {
  440. 'size': vimsupport.GetIntValue( '&previewheight' ),
  441. 'fix': True,
  442. 'watch': True,
  443. 'position': 'end'
  444. }
  445. if stdout:
  446. vimsupport.OpenFilename( self._server_stdout, options )
  447. if stderr:
  448. vimsupport.OpenFilename( self._server_stderr, options )
  449. def _CloseLogs( self, stdout = True, stderr = True ):
  450. if stdout:
  451. vimsupport.CloseBuffersForFilename( self._server_stdout )
  452. if stderr:
  453. vimsupport.CloseBuffersForFilename( self._server_stderr )
  454. def ToggleLogs( self, stdout = True, stderr = True ):
  455. if ( stdout and
  456. vimsupport.BufferIsVisibleForFilename( self._server_stdout ) or
  457. stderr and
  458. vimsupport.BufferIsVisibleForFilename( self._server_stderr ) ):
  459. return self._CloseLogs( stdout = stdout, stderr = stderr )
  460. # Close hidden logfile buffers if any to keep a clean state
  461. self._CloseLogs( stdout = stdout, stderr = stderr )
  462. try:
  463. self._OpenLogs( stdout = stdout, stderr = stderr )
  464. except RuntimeError as error:
  465. vimsupport.PostVimMessage( 'YouCompleteMe encountered an error when '
  466. 'opening logs: {0}.'.format( error ) )
  467. def CurrentFiletypeCompletionEnabled( self ):
  468. filetypes = vimsupport.CurrentFiletypes()
  469. filetype_to_disable = self._user_options[
  470. 'filetype_specific_completion_to_disable' ]
  471. if '*' in filetype_to_disable:
  472. return False
  473. else:
  474. return not any([ x in filetype_to_disable for x in filetypes ])
  475. def _AddSyntaxDataIfNeeded( self, extra_data ):
  476. if not self._user_options[ 'seed_identifiers_with_syntax' ]:
  477. return
  478. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  479. if filetype in self._filetypes_with_keywords_loaded:
  480. return
  481. self._filetypes_with_keywords_loaded.add( filetype )
  482. extra_data[ 'syntax_keywords' ] = list(
  483. syntax_parse.SyntaxKeywordsForCurrentBuffer() )
  484. def _AddTagsFilesIfNeeded( self, extra_data ):
  485. def GetTagFiles():
  486. tag_files = vim.eval( 'tagfiles()' )
  487. # getcwd() throws an exception when the CWD has been deleted.
  488. try:
  489. current_working_directory = os.getcwd()
  490. except OSError:
  491. return []
  492. return [ os.path.join( current_working_directory, x ) for x in tag_files ]
  493. if not self._user_options[ 'collect_identifiers_from_tags_files' ]:
  494. return
  495. extra_data[ 'tag_files' ] = GetTagFiles()
  496. def _AddExtraConfDataIfNeeded( self, extra_data ):
  497. def BuildExtraConfData( extra_conf_vim_data ):
  498. return dict( ( expr, vimsupport.VimExpressionToPythonType( expr ) )
  499. for expr in extra_conf_vim_data )
  500. extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ]
  501. if extra_conf_vim_data:
  502. extra_data[ 'extra_conf_data' ] = BuildExtraConfData(
  503. extra_conf_vim_data )
  504. def _AddUltiSnipsDataIfNeeded( extra_data ):
  505. if not USE_ULTISNIPS_DATA:
  506. return
  507. try:
  508. # Since UltiSnips may run in a different python interpreter (python 3) than
  509. # YCM, UltiSnips_Manager singleton is not necessary the same as the one
  510. # used by YCM. In particular, it means that we cannot rely on UltiSnips to
  511. # set the current filetypes to the singleton. We need to do it ourself.
  512. UltiSnips_Manager.reset_buffer_filetypes()
  513. UltiSnips_Manager.add_buffer_filetypes(
  514. vimsupport.GetVariableValue( '&filetype' ) )
  515. rawsnips = UltiSnips_Manager._snips( '', True )
  516. except:
  517. return
  518. # UltiSnips_Manager._snips() returns a class instance where:
  519. # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
  520. # class.description - description of the snippet
  521. extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger,
  522. 'description': x.description
  523. } for x in rawsnips ]