youcompleteme.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. # Copyright (C) 2011-2018 YouCompleteMe contributors
  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. # Not installing aliases from python-future; it's unreliable and slow.
  22. from builtins import * # noqa
  23. from future.utils import iteritems
  24. import base64
  25. import json
  26. import logging
  27. import os
  28. import signal
  29. import vim
  30. from subprocess import PIPE
  31. from tempfile import NamedTemporaryFile
  32. from ycm import base, paths, vimsupport
  33. from ycm.buffer import ( BufferDict,
  34. DIAGNOSTIC_UI_FILETYPES,
  35. DIAGNOSTIC_UI_ASYNC_FILETYPES )
  36. from ycmd import utils
  37. from ycmd import server_utils
  38. from ycmd.request_wrap import RequestWrap
  39. from ycm.omni_completer import OmniCompleter
  40. from ycm import syntax_parse
  41. from ycm.client.ycmd_keepalive import YcmdKeepalive
  42. from ycm.client.base_request import ( BaseRequest, BuildRequestData,
  43. HandleServerException )
  44. from ycm.client.completer_available_request import SendCompleterAvailableRequest
  45. from ycm.client.command_request import SendCommandRequest
  46. from ycm.client.completion_request import ( CompletionRequest,
  47. ConvertCompletionDataToVimData )
  48. from ycm.client.debug_info_request import ( SendDebugInfoRequest,
  49. FormatDebugInfoResponse )
  50. from ycm.client.omni_completion_request import OmniCompletionRequest
  51. from ycm.client.event_notification import SendEventNotificationAsync
  52. from ycm.client.shutdown_request import SendShutdownRequest
  53. from ycm.client.messages_request import MessagesPoll
  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_SHUTDOWN_MESSAGE = (
  73. "The ycmd server SHUT DOWN (restart with ':YcmRestartServer')." )
  74. EXIT_CODE_UNEXPECTED_MESSAGE = (
  75. "Unexpected exit code {code}. "
  76. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  77. CORE_UNEXPECTED_MESSAGE = (
  78. "Unexpected error while loading the YCM core library. "
  79. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  80. CORE_MISSING_MESSAGE = (
  81. 'YCM core library not detected; you need to compile YCM before using it. '
  82. 'Follow the instructions in the documentation.' )
  83. CORE_PYTHON2_MESSAGE = (
  84. "YCM core library compiled for Python 2 but loaded in Python 3. "
  85. "Set the 'g:ycm_server_python_interpreter' option to a Python 2 "
  86. "interpreter path." )
  87. CORE_PYTHON3_MESSAGE = (
  88. "YCM core library compiled for Python 3 but loaded in Python 2. "
  89. "Set the 'g:ycm_server_python_interpreter' option to a Python 3 "
  90. "interpreter path." )
  91. CORE_OUTDATED_MESSAGE = (
  92. 'YCM core library too old; PLEASE RECOMPILE by running the install.py '
  93. 'script. See the documentation for more details.' )
  94. SERVER_IDLE_SUICIDE_SECONDS = 1800 # 30 minutes
  95. CLIENT_LOGFILE_FORMAT = 'ycm_'
  96. SERVER_LOGFILE_FORMAT = 'ycmd_{port}_{std}_'
  97. # Flag to set a file handle inheritable by child processes on Windows. See
  98. # https://msdn.microsoft.com/en-us/library/ms724935.aspx
  99. HANDLE_FLAG_INHERIT = 0x00000001
  100. # The following two methods exist for testability only
  101. def _CompleteDoneHook_CSharp( ycm ):
  102. ycm._OnCompleteDone_Csharp()
  103. def _CompleteDoneHook_Java( ycm ):
  104. ycm._OnCompleteDone_Java()
  105. class YouCompleteMe( object ):
  106. def __init__( self, user_options ):
  107. self._available_completers = {}
  108. self._user_options = user_options
  109. self._user_notified_about_crash = False
  110. self._omnicomp = OmniCompleter( user_options )
  111. self._buffers = BufferDict( user_options )
  112. self._latest_completion_request = None
  113. self._logger = logging.getLogger( 'ycm' )
  114. self._client_logfile = None
  115. self._server_stdout = None
  116. self._server_stderr = None
  117. self._server_popen = None
  118. self._filetypes_with_keywords_loaded = set()
  119. self._ycmd_keepalive = YcmdKeepalive()
  120. self._server_is_ready_with_cache = False
  121. self._SetUpLogging()
  122. self._SetUpServer()
  123. self._ycmd_keepalive.Start()
  124. self._complete_done_hooks = {
  125. 'cs': _CompleteDoneHook_CSharp,
  126. 'java': _CompleteDoneHook_Java,
  127. }
  128. def _SetUpServer( self ):
  129. self._available_completers = {}
  130. self._user_notified_about_crash = False
  131. self._filetypes_with_keywords_loaded = set()
  132. self._server_is_ready_with_cache = False
  133. self._message_poll_request = None
  134. hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
  135. options_dict = dict( self._user_options )
  136. options_dict[ 'hmac_secret' ] = utils.ToUnicode(
  137. base64.b64encode( hmac_secret ) )
  138. options_dict[ 'server_keep_logfiles' ] = self._user_options[
  139. 'keep_logfiles' ]
  140. # The temp options file is deleted by ycmd during startup.
  141. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
  142. json.dump( options_dict, options_file )
  143. server_port = utils.GetUnusedLocalhostPort()
  144. BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
  145. BaseRequest.hmac_secret = hmac_secret
  146. try:
  147. python_interpreter = paths.PathToPythonInterpreter()
  148. except RuntimeError as error:
  149. error_message = (
  150. "Unable to start the ycmd server. {0}. "
  151. "Correct the error then restart the server "
  152. "with ':YcmRestartServer'.".format( str( error ).rstrip( '.' ) ) )
  153. self._logger.exception( error_message )
  154. vimsupport.PostVimMessage( error_message )
  155. return
  156. args = [ python_interpreter,
  157. paths.PathToServerScript(),
  158. '--port={0}'.format( server_port ),
  159. '--options_file={0}'.format( options_file.name ),
  160. '--log={0}'.format( self._user_options[ 'log_level' ] ),
  161. '--idle_suicide_seconds={0}'.format(
  162. SERVER_IDLE_SUICIDE_SECONDS ) ]
  163. self._server_stdout = utils.CreateLogfile(
  164. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) )
  165. self._server_stderr = utils.CreateLogfile(
  166. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) )
  167. args.append( '--stdout={0}'.format( self._server_stdout ) )
  168. args.append( '--stderr={0}'.format( self._server_stderr ) )
  169. if self._user_options[ 'keep_logfiles' ]:
  170. args.append( '--keep_logfiles' )
  171. self._server_popen = utils.SafePopen( args, stdin_windows = PIPE,
  172. stdout = PIPE, stderr = PIPE )
  173. def _SetUpLogging( self ):
  174. def FreeFileFromOtherProcesses( file_object ):
  175. if utils.OnWindows():
  176. from ctypes import windll
  177. import msvcrt
  178. file_handle = msvcrt.get_osfhandle( file_object.fileno() )
  179. windll.kernel32.SetHandleInformation( file_handle,
  180. HANDLE_FLAG_INHERIT,
  181. 0 )
  182. self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT )
  183. log_level = self._user_options[ 'log_level' ]
  184. numeric_level = getattr( logging, log_level.upper(), None )
  185. if not isinstance( numeric_level, int ):
  186. raise ValueError( 'Invalid log level: {0}'.format( log_level ) )
  187. self._logger.setLevel( numeric_level )
  188. handler = logging.FileHandler( self._client_logfile )
  189. # On Windows and Python prior to 3.4, file handles are inherited by child
  190. # processes started with at least one replaced standard stream, which is the
  191. # case when we start the ycmd server (we are redirecting all standard
  192. # outputs into a pipe). These files cannot be removed while the child
  193. # processes are still up. This is not desirable for a logfile because we
  194. # want to remove it at Vim exit without having to wait for the ycmd server
  195. # to be completely shut down. We need to make the logfile handle
  196. # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more
  197. # details.
  198. FreeFileFromOtherProcesses( handler.stream )
  199. formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )
  200. handler.setFormatter( formatter )
  201. self._logger.addHandler( handler )
  202. def IsServerAlive( self ):
  203. # When the process hasn't finished yet, poll() returns None.
  204. return bool( self._server_popen ) and self._server_popen.poll() is None
  205. def CheckIfServerIsReady( self ):
  206. if not self._server_is_ready_with_cache:
  207. with HandleServerException( display = False ):
  208. self._server_is_ready_with_cache = BaseRequest.GetDataFromHandler(
  209. 'ready' )
  210. return self._server_is_ready_with_cache
  211. def IsServerReady( self ):
  212. return self._server_is_ready_with_cache
  213. def NotifyUserIfServerCrashed( self ):
  214. if ( not self._server_popen or self._user_notified_about_crash or
  215. self.IsServerAlive() ):
  216. return
  217. self._user_notified_about_crash = True
  218. return_code = self._server_popen.poll()
  219. logfile = os.path.basename( self._server_stderr )
  220. if return_code == server_utils.CORE_UNEXPECTED_STATUS:
  221. error_message = CORE_UNEXPECTED_MESSAGE.format(
  222. logfile = logfile )
  223. elif return_code == server_utils.CORE_MISSING_STATUS:
  224. error_message = CORE_MISSING_MESSAGE
  225. elif return_code == server_utils.CORE_PYTHON2_STATUS:
  226. error_message = CORE_PYTHON2_MESSAGE
  227. elif return_code == server_utils.CORE_PYTHON3_STATUS:
  228. error_message = CORE_PYTHON3_MESSAGE
  229. elif return_code == server_utils.CORE_OUTDATED_STATUS:
  230. error_message = CORE_OUTDATED_MESSAGE
  231. else:
  232. error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format(
  233. code = return_code,
  234. logfile = logfile )
  235. error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message
  236. self._logger.error( error_message )
  237. vimsupport.PostVimMessage( error_message )
  238. def ServerPid( self ):
  239. if not self._server_popen:
  240. return -1
  241. return self._server_popen.pid
  242. def _ShutdownServer( self ):
  243. SendShutdownRequest()
  244. def RestartServer( self ):
  245. vimsupport.PostVimMessage( 'Restarting ycmd server...' )
  246. self._ShutdownServer()
  247. self._SetUpServer()
  248. def SendCompletionRequest( self, force_semantic = False ):
  249. request_data = BuildRequestData()
  250. request_data[ 'force_semantic' ] = force_semantic
  251. if ( not self.NativeFiletypeCompletionAvailable() and
  252. self.CurrentFiletypeCompletionEnabled() ):
  253. wrapped_request_data = RequestWrap( request_data )
  254. if self._omnicomp.ShouldUseNow( wrapped_request_data ):
  255. self._latest_completion_request = OmniCompletionRequest(
  256. self._omnicomp, wrapped_request_data )
  257. self._latest_completion_request.Start()
  258. return
  259. self._AddExtraConfDataIfNeeded( request_data )
  260. self._latest_completion_request = CompletionRequest( request_data )
  261. self._latest_completion_request.Start()
  262. def CompletionRequestReady( self ):
  263. return bool( self._latest_completion_request and
  264. self._latest_completion_request.Done() )
  265. def GetCompletionResponse( self ):
  266. response = self._latest_completion_request.Response()
  267. response[ 'completions' ] = base.AdjustCandidateInsertionText(
  268. response[ 'completions' ] )
  269. return response
  270. def SendCommandRequest( self,
  271. arguments,
  272. completer,
  273. has_range,
  274. start_line,
  275. end_line ):
  276. extra_data = {
  277. 'options': {
  278. 'tab_size': vimsupport.GetIntValue( 'shiftwidth()' ),
  279. 'insert_spaces': vimsupport.GetBoolValue( '&expandtab' )
  280. }
  281. }
  282. if has_range:
  283. extra_data.update( vimsupport.BuildRange( start_line, end_line ) )
  284. self._AddExtraConfDataIfNeeded( extra_data )
  285. return SendCommandRequest( arguments, completer, extra_data )
  286. def GetDefinedSubcommands( self ):
  287. with HandleServerException():
  288. return BaseRequest.PostDataToHandler( BuildRequestData(),
  289. 'defined_subcommands' )
  290. return []
  291. def GetCurrentCompletionRequest( self ):
  292. return self._latest_completion_request
  293. def GetOmniCompleter( self ):
  294. return self._omnicomp
  295. def FiletypeCompleterExistsForFiletype( self, filetype ):
  296. try:
  297. return self._available_completers[ filetype ]
  298. except KeyError:
  299. pass
  300. exists_completer = SendCompleterAvailableRequest( filetype )
  301. if exists_completer is None:
  302. return False
  303. self._available_completers[ filetype ] = exists_completer
  304. return exists_completer
  305. def NativeFiletypeCompletionAvailable( self ):
  306. return any( [ self.FiletypeCompleterExistsForFiletype( x ) for x in
  307. vimsupport.CurrentFiletypes() ] )
  308. def NativeFiletypeCompletionUsable( self ):
  309. return ( self.CurrentFiletypeCompletionEnabled() and
  310. self.NativeFiletypeCompletionAvailable() )
  311. def NeedsReparse( self ):
  312. return self.CurrentBuffer().NeedsReparse()
  313. def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ):
  314. bufnr = vimsupport.GetBufferNumberForFilename( filepath )
  315. if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ):
  316. # Note: We only update location lists, etc. for visible buffers, because
  317. # otherwise we defualt to using the curren location list and the results
  318. # are that non-visible buffer errors clobber visible ones.
  319. self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics )
  320. else:
  321. # The project contains errors in file "filepath", but that file is not
  322. # open in any buffer. This happens for Language Server Protocol-based
  323. # completers, as they return diagnostics for the entire "project"
  324. # asynchronously (rather than per-file in the response to the parse
  325. # request).
  326. #
  327. # There are a number of possible approaches for
  328. # this, but for now we simply ignore them. Other options include:
  329. # - Use the QuickFix list to report project errors?
  330. # - Use a special buffer for project errors
  331. # - Put them in the location list of whatever the "current" buffer is
  332. # - Store them in case the buffer is opened later
  333. # - add a :YcmProjectDiags command
  334. # - Add them to errror/warning _counts_ but not any actual location list
  335. # or other
  336. # - etc.
  337. #
  338. # However, none of those options are great, and lead to their own
  339. # complexities. So for now, we just ignore these diagnostics for files not
  340. # open in any buffer.
  341. pass
  342. def OnPeriodicTick( self ):
  343. if not self.IsServerAlive():
  344. # Server has died. We'll reset when the server is started again.
  345. return False
  346. elif not self.IsServerReady():
  347. # Try again in a jiffy
  348. return True
  349. if not self._message_poll_request:
  350. self._message_poll_request = MessagesPoll()
  351. if not self._message_poll_request.Poll( self ):
  352. # Don't poll again until some event which might change the server's mind
  353. # about whether to provide messages for the current buffer (e.g. buffer
  354. # visit, file ready to parse, etc.)
  355. self._message_poll_request = None
  356. return False
  357. # Poll again in a jiffy
  358. return True
  359. def OnFileReadyToParse( self ):
  360. if not self.IsServerAlive():
  361. self.NotifyUserIfServerCrashed()
  362. return
  363. if not self.IsServerReady():
  364. return
  365. extra_data = {}
  366. self._AddTagsFilesIfNeeded( extra_data )
  367. self._AddSyntaxDataIfNeeded( extra_data )
  368. self._AddExtraConfDataIfNeeded( extra_data )
  369. self.CurrentBuffer().SendParseRequest( extra_data )
  370. def OnBufferUnload( self, deleted_buffer_number ):
  371. SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number )
  372. def UpdateMatches( self ):
  373. self.CurrentBuffer().UpdateMatches()
  374. def OnBufferVisit( self ):
  375. extra_data = {}
  376. self._AddUltiSnipsDataIfNeeded( extra_data )
  377. SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data )
  378. def CurrentBuffer( self ):
  379. return self._buffers[ vimsupport.GetCurrentBufferNumber() ]
  380. def OnInsertLeave( self ):
  381. SendEventNotificationAsync( 'InsertLeave' )
  382. def OnCursorMoved( self ):
  383. self.CurrentBuffer().OnCursorMoved()
  384. def _CleanLogfile( self ):
  385. logging.shutdown()
  386. if not self._user_options[ 'keep_logfiles' ]:
  387. if self._client_logfile:
  388. utils.RemoveIfExists( self._client_logfile )
  389. def OnVimLeave( self ):
  390. self._ShutdownServer()
  391. self._CleanLogfile()
  392. def OnCurrentIdentifierFinished( self ):
  393. SendEventNotificationAsync( 'CurrentIdentifierFinished' )
  394. def OnCompleteDone( self ):
  395. complete_done_actions = self.GetCompleteDoneHooks()
  396. for action in complete_done_actions:
  397. action(self)
  398. def GetCompleteDoneHooks( self ):
  399. filetypes = vimsupport.CurrentFiletypes()
  400. for key, value in iteritems( self._complete_done_hooks ):
  401. if key in filetypes:
  402. yield value
  403. def GetCompletionsUserMayHaveCompleted( self ):
  404. latest_completion_request = self.GetCurrentCompletionRequest()
  405. if not latest_completion_request or not latest_completion_request.Done():
  406. return []
  407. completed_item = vimsupport.GetVariableValue( 'v:completed_item' )
  408. completions = latest_completion_request.RawResponse()[ 'completions' ]
  409. if 'user_data' in completed_item and completed_item[ 'user_data' ] != '':
  410. # Vim supports user_data (8.0.1493) or later, so we actually know the
  411. # _exact_ element that was selected, having put its index in the user_data
  412. # field.
  413. return [ completions[ int( completed_item[ 'user_data' ] ) ] ]
  414. # Otherwise, we have to guess by matching the values in the completed item
  415. # and the list of completions. Sometimes this returns multiple
  416. # possibilities, which is essentially unresolvable.
  417. result = self._FilterToMatchingCompletions( completed_item,
  418. completions,
  419. True )
  420. result = list( result )
  421. if result:
  422. return result
  423. if self._HasCompletionsThatCouldBeCompletedWithMoreText( completed_item,
  424. completions ):
  425. # Since the way that YCM works leads to CompleteDone called on every
  426. # character, return blank if the completion might not be done. This won't
  427. # match if the completion is ended with typing a non-keyword character.
  428. return []
  429. result = self._FilterToMatchingCompletions( completed_item,
  430. completions,
  431. False )
  432. return list( result )
  433. def _FilterToMatchingCompletions( self,
  434. completed_item,
  435. completions,
  436. full_match_only ):
  437. """Filter to completions matching the item Vim said was completed"""
  438. match_keys = ( [ "word", "abbr", "menu", "info" ] if full_match_only
  439. else [ 'word' ] )
  440. for index, completion in enumerate( completions ):
  441. item = ConvertCompletionDataToVimData( index, completion )
  442. def matcher( key ):
  443. return ( utils.ToUnicode( completed_item.get( key, "" ) ) ==
  444. utils.ToUnicode( item.get( key, "" ) ) )
  445. if all( [ matcher( i ) for i in match_keys ] ):
  446. yield completion
  447. def _HasCompletionsThatCouldBeCompletedWithMoreText( self,
  448. completed_item,
  449. completions ):
  450. if not completed_item:
  451. return False
  452. completed_word = utils.ToUnicode( completed_item[ 'word' ] )
  453. if not completed_word:
  454. return False
  455. # Sometimes CompleteDone is called after the next character is inserted.
  456. # If so, use inserted character to filter possible completions further.
  457. text = vimsupport.TextBeforeCursor()
  458. reject_exact_match = True
  459. if text and text[ -1 ] != completed_word[ -1 ]:
  460. reject_exact_match = False
  461. completed_word += text[ -1 ]
  462. for index, completion in enumerate( completions ):
  463. word = utils.ToUnicode(
  464. ConvertCompletionDataToVimData( index, completion )[ 'word' ] )
  465. if reject_exact_match and word == completed_word:
  466. continue
  467. if word.startswith( completed_word ):
  468. return True
  469. return False
  470. def _OnCompleteDone_Csharp( self ):
  471. completions = self.GetCompletionsUserMayHaveCompleted()
  472. namespaces = [ self._GetRequiredNamespaceImport( c )
  473. for c in completions ]
  474. namespaces = [ n for n in namespaces if n ]
  475. if not namespaces:
  476. return
  477. if len( namespaces ) > 1:
  478. choices = [ "{0} {1}".format( i + 1, n )
  479. for i, n in enumerate( namespaces ) ]
  480. choice = vimsupport.PresentDialog( "Insert which namespace:", choices )
  481. if choice < 0:
  482. return
  483. namespace = namespaces[ choice ]
  484. else:
  485. namespace = namespaces[ 0 ]
  486. vimsupport.InsertNamespace( namespace )
  487. def _GetRequiredNamespaceImport( self, completion ):
  488. if ( "extra_data" not in completion
  489. or "required_namespace_import" not in completion[ "extra_data" ] ):
  490. return None
  491. return completion[ "extra_data" ][ "required_namespace_import" ]
  492. def _OnCompleteDone_Java( self ):
  493. completions = self.GetCompletionsUserMayHaveCompleted()
  494. fixit_completions = [ self._GetFixItCompletion( c ) for c in completions ]
  495. fixit_completions = [ f for f in fixit_completions if f ]
  496. if not fixit_completions:
  497. return
  498. # If we have user_data in completions (8.0.1493 or later), then we would
  499. # only ever return max. 1 completion here. However, if we had to guess, it
  500. # is possible that we matched multiple completion items (e.g. for overloads,
  501. # or similar classes in multiple packages). In any case, rather than
  502. # prompting the user and disturbing her workflow, we just apply the first
  503. # one. This might be wrong, but the solution is to use a (very) new version
  504. # of Vim which supports user_data on completion items
  505. fixit_completion = fixit_completions[ 0 ]
  506. for fixit in fixit_completion:
  507. vimsupport.ReplaceChunks( fixit[ 'chunks' ], silent=True )
  508. def _GetFixItCompletion( self, completion ):
  509. if ( "extra_data" not in completion
  510. or "fixits" not in completion[ "extra_data" ] ):
  511. return None
  512. return completion[ "extra_data" ][ "fixits" ]
  513. def GetErrorCount( self ):
  514. return self.CurrentBuffer().GetErrorCount()
  515. def GetWarningCount( self ):
  516. return self.CurrentBuffer().GetWarningCount()
  517. def DiagnosticUiSupportedForCurrentFiletype( self ):
  518. return any( [ x in DIAGNOSTIC_UI_FILETYPES or
  519. x in DIAGNOSTIC_UI_ASYNC_FILETYPES
  520. for x in vimsupport.CurrentFiletypes() ] )
  521. def ShouldDisplayDiagnostics( self ):
  522. return bool( self._user_options[ 'show_diagnostics_ui' ] and
  523. self.DiagnosticUiSupportedForCurrentFiletype() )
  524. def _PopulateLocationListWithLatestDiagnostics( self ):
  525. return self.CurrentBuffer().PopulateLocationList()
  526. def FileParseRequestReady( self ):
  527. # Return True if server is not ready yet, to stop repeating check timer.
  528. return ( not self.IsServerReady() or
  529. self.CurrentBuffer().FileParseRequestReady() )
  530. def HandleFileParseRequest( self, block = False ):
  531. if not self.IsServerReady():
  532. return
  533. current_buffer = self.CurrentBuffer()
  534. # Order is important here:
  535. # FileParseRequestReady has a low cost, while
  536. # NativeFiletypeCompletionUsable is a blocking server request
  537. if ( not current_buffer.IsResponseHandled() and
  538. current_buffer.FileParseRequestReady( block ) and
  539. self.NativeFiletypeCompletionUsable() ):
  540. if self.ShouldDisplayDiagnostics():
  541. # Forcefuly update the location list, etc. from the parse request when
  542. # doing something like :YcmDiags
  543. current_buffer.UpdateDiagnostics( block is True )
  544. else:
  545. # YCM client has a hard-coded list of filetypes which are known
  546. # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype()
  547. #
  548. # For filetypes which don't support diagnostics, we just want to check
  549. # the _latest_file_parse_request for any exception or UnknownExtraConf
  550. # response, to allow the server to raise configuration warnings, etc.
  551. # to the user. We ignore any other supplied data.
  552. current_buffer.GetResponse()
  553. # We set the file parse request as handled because we want to prevent
  554. # repeated issuing of the same warnings/errors/prompts. Setting this
  555. # makes IsRequestHandled return True until the next request is created.
  556. #
  557. # Note: it is the server's responsibility to determine the frequency of
  558. # error/warning/prompts when receiving a FileReadyToParse event, but
  559. # it is our responsibility to ensure that we only apply the
  560. # warning/error/prompt received once (for each event).
  561. current_buffer.MarkResponseHandled()
  562. def DebugInfo( self ):
  563. debug_info = ''
  564. if self._client_logfile:
  565. debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
  566. extra_data = {}
  567. self._AddExtraConfDataIfNeeded( extra_data )
  568. debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) )
  569. debug_info += 'Server running at: {0}\n'.format(
  570. BaseRequest.server_location )
  571. if self._server_popen:
  572. debug_info += 'Server process ID: {0}\n'.format( self._server_popen.pid )
  573. if self._server_stdout and self._server_stderr:
  574. debug_info += ( 'Server logfiles:\n'
  575. ' {0}\n'
  576. ' {1}'.format( self._server_stdout,
  577. self._server_stderr ) )
  578. return debug_info
  579. def GetLogfiles( self ):
  580. logfiles_list = [ self._client_logfile,
  581. self._server_stdout,
  582. self._server_stderr ]
  583. debug_info = SendDebugInfoRequest()
  584. if debug_info:
  585. completer = debug_info[ 'completer' ]
  586. if completer:
  587. for server in completer[ 'servers' ]:
  588. logfiles_list.extend( server[ 'logfiles' ] )
  589. logfiles = {}
  590. for logfile in logfiles_list:
  591. logfiles[ os.path.basename( logfile ) ] = logfile
  592. return logfiles
  593. def _OpenLogfile( self, logfile ):
  594. # Open log files in a horizontal window with the same behavior as the
  595. # preview window (same height and winfixheight enabled). Automatically
  596. # watch for changes. Set the cursor position at the end of the file.
  597. options = {
  598. 'size': vimsupport.GetIntValue( '&previewheight' ),
  599. 'fix': True,
  600. 'focus': False,
  601. 'watch': True,
  602. 'position': 'end'
  603. }
  604. vimsupport.OpenFilename( logfile, options )
  605. def _CloseLogfile( self, logfile ):
  606. vimsupport.CloseBuffersForFilename( logfile )
  607. def ToggleLogs( self, *filenames ):
  608. logfiles = self.GetLogfiles()
  609. if not filenames:
  610. sorted_logfiles = sorted( list( logfiles ) )
  611. try:
  612. logfile_index = vimsupport.SelectFromList(
  613. 'Which logfile do you wish to open (or close if already open)?',
  614. sorted_logfiles )
  615. except RuntimeError as e:
  616. vimsupport.PostVimMessage( str( e ) )
  617. return
  618. logfile = logfiles[ sorted_logfiles[ logfile_index ] ]
  619. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  620. self._OpenLogfile( logfile )
  621. else:
  622. self._CloseLogfile( logfile )
  623. return
  624. for filename in set( filenames ):
  625. if filename not in logfiles:
  626. continue
  627. logfile = logfiles[ filename ]
  628. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  629. self._OpenLogfile( logfile )
  630. continue
  631. self._CloseLogfile( logfile )
  632. def CurrentFiletypeCompletionEnabled( self ):
  633. filetypes = vimsupport.CurrentFiletypes()
  634. filetype_to_disable = self._user_options[
  635. 'filetype_specific_completion_to_disable' ]
  636. if '*' in filetype_to_disable:
  637. return False
  638. else:
  639. return not any( [ x in filetype_to_disable for x in filetypes ] )
  640. def ShowDetailedDiagnostic( self ):
  641. with HandleServerException():
  642. detailed_diagnostic = BaseRequest.PostDataToHandler(
  643. BuildRequestData(), 'detailed_diagnostic' )
  644. if 'message' in detailed_diagnostic:
  645. vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ],
  646. warning = False )
  647. def ForceCompileAndDiagnostics( self ):
  648. if not self.NativeFiletypeCompletionUsable():
  649. vimsupport.PostVimMessage(
  650. 'Native filetype completion not supported for current file, '
  651. 'cannot force recompilation.', warning = False )
  652. return False
  653. vimsupport.PostVimMessage(
  654. 'Forcing compilation, this will block Vim until done.',
  655. warning = False )
  656. self.OnFileReadyToParse()
  657. self.HandleFileParseRequest( block = True )
  658. vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False )
  659. return True
  660. def ShowDiagnostics( self ):
  661. if not self.ForceCompileAndDiagnostics():
  662. return
  663. if not self._PopulateLocationListWithLatestDiagnostics():
  664. vimsupport.PostVimMessage( 'No warnings or errors detected.',
  665. warning = False )
  666. return
  667. if self._user_options[ 'open_loclist_on_ycm_diags' ]:
  668. vimsupport.OpenLocationList( focus = True )
  669. def _AddSyntaxDataIfNeeded( self, extra_data ):
  670. if not self._user_options[ 'seed_identifiers_with_syntax' ]:
  671. return
  672. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  673. if filetype in self._filetypes_with_keywords_loaded:
  674. return
  675. if self.IsServerReady():
  676. self._filetypes_with_keywords_loaded.add( filetype )
  677. extra_data[ 'syntax_keywords' ] = list(
  678. syntax_parse.SyntaxKeywordsForCurrentBuffer() )
  679. def _AddTagsFilesIfNeeded( self, extra_data ):
  680. def GetTagFiles():
  681. tag_files = vim.eval( 'tagfiles()' )
  682. return [ os.path.join( utils.GetCurrentDirectory(), tag_file )
  683. for tag_file in tag_files ]
  684. if not self._user_options[ 'collect_identifiers_from_tags_files' ]:
  685. return
  686. extra_data[ 'tag_files' ] = GetTagFiles()
  687. def _AddExtraConfDataIfNeeded( self, extra_data ):
  688. def BuildExtraConfData( extra_conf_vim_data ):
  689. extra_conf_data = {}
  690. for expr in extra_conf_vim_data:
  691. try:
  692. extra_conf_data[ expr ] = vimsupport.VimExpressionToPythonType( expr )
  693. except vim.error:
  694. message = (
  695. "Error evaluating '{expr}' in the 'g:ycm_extra_conf_vim_data' "
  696. "option.".format( expr = expr ) )
  697. vimsupport.PostVimMessage( message, truncate = True )
  698. self._logger.exception( message )
  699. return extra_conf_data
  700. extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ]
  701. if extra_conf_vim_data:
  702. extra_data[ 'extra_conf_data' ] = BuildExtraConfData(
  703. extra_conf_vim_data )
  704. def _AddUltiSnipsDataIfNeeded( self, extra_data ):
  705. # See :h UltiSnips#SnippetsInCurrentScope.
  706. try:
  707. vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' )
  708. except vim.error:
  709. return
  710. snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' )
  711. extra_data[ 'ultisnips_snippets' ] = [
  712. { 'trigger': trigger,
  713. 'description': snippet[ 'description' ] }
  714. for trigger, snippet in iteritems( snippets )
  715. ]