youcompleteme.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  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, signature_help, vimsupport
  33. from ycm.buffer import BufferDict
  34. from ycmd import utils
  35. from ycmd.request_wrap import RequestWrap
  36. from ycm.omni_completer import OmniCompleter
  37. from ycm import syntax_parse
  38. from ycm.client.ycmd_keepalive import YcmdKeepalive
  39. from ycm.client.base_request import BaseRequest, BuildRequestData
  40. from ycm.client.completer_available_request import SendCompleterAvailableRequest
  41. from ycm.client.command_request import SendCommandRequest
  42. from ycm.client.completion_request import CompletionRequest
  43. from ycm.client.signature_help_request import SignatureHelpRequest
  44. from ycm.client.signature_help_request import SigHelpAvailableByFileType
  45. from ycm.client.debug_info_request import ( SendDebugInfoRequest,
  46. FormatDebugInfoResponse )
  47. from ycm.client.omni_completion_request import OmniCompletionRequest
  48. from ycm.client.event_notification import SendEventNotificationAsync
  49. from ycm.client.shutdown_request import SendShutdownRequest
  50. from ycm.client.messages_request import MessagesPoll
  51. def PatchNoProxy():
  52. current_value = os.environ.get( 'no_proxy', '' )
  53. additions = '127.0.0.1,localhost'
  54. os.environ[ 'no_proxy' ] = ( additions if not current_value
  55. else current_value + ',' + additions )
  56. # We need this so that Requests doesn't end up using the local HTTP proxy when
  57. # talking to ycmd. Users should actually be setting this themselves when
  58. # configuring a proxy server on their machine, but most don't know they need to
  59. # or how to do it, so we do it for them.
  60. # Relevant issues:
  61. # https://github.com/Valloric/YouCompleteMe/issues/641
  62. # https://github.com/kennethreitz/requests/issues/879
  63. PatchNoProxy()
  64. # Force the Python interpreter embedded in Vim (in which we are running) to
  65. # ignore the SIGINT signal. This helps reduce the fallout of a user pressing
  66. # Ctrl-C in Vim.
  67. signal.signal( signal.SIGINT, signal.SIG_IGN )
  68. HMAC_SECRET_LENGTH = 16
  69. SERVER_SHUTDOWN_MESSAGE = (
  70. "The ycmd server SHUT DOWN (restart with ':YcmRestartServer')." )
  71. EXIT_CODE_UNEXPECTED_MESSAGE = (
  72. "Unexpected exit code {code}. "
  73. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  74. CORE_UNEXPECTED_MESSAGE = (
  75. "Unexpected error while loading the YCM core library. "
  76. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  77. CORE_MISSING_MESSAGE = (
  78. 'YCM core library not detected; you need to compile YCM before using it. '
  79. 'Follow the instructions in the documentation.' )
  80. CORE_PYTHON2_MESSAGE = (
  81. "YCM core library compiled for Python 2 but loaded in Python 3. "
  82. "Set the 'g:ycm_server_python_interpreter' option to a Python 2 "
  83. "interpreter path." )
  84. CORE_PYTHON3_MESSAGE = (
  85. "YCM core library compiled for Python 3 but loaded in Python 2. "
  86. "Set the 'g:ycm_server_python_interpreter' option to a Python 3 "
  87. "interpreter path." )
  88. CORE_OUTDATED_MESSAGE = (
  89. 'YCM core library too old; PLEASE RECOMPILE by running the install.py '
  90. 'script. See the documentation for more details.' )
  91. SERVER_IDLE_SUICIDE_SECONDS = 1800 # 30 minutes
  92. CLIENT_LOGFILE_FORMAT = 'ycm_'
  93. SERVER_LOGFILE_FORMAT = 'ycmd_{port}_{std}_'
  94. # Flag to set a file handle inheritable by child processes on Windows. See
  95. # https://msdn.microsoft.com/en-us/library/ms724935.aspx
  96. HANDLE_FLAG_INHERIT = 0x00000001
  97. class YouCompleteMe( object ):
  98. def __init__( self ):
  99. self._available_completers = {}
  100. self._user_options = None
  101. self._user_notified_about_crash = False
  102. self._omnicomp = None
  103. self._buffers = None
  104. self._latest_completion_request = None
  105. self._latest_signature_help_request = None
  106. self._signature_help_available_requests = SigHelpAvailableByFileType()
  107. self._signature_help_state = signature_help.SignatureHelpState()
  108. self._logger = logging.getLogger( 'ycm' )
  109. self._client_logfile = None
  110. self._server_stdout = None
  111. self._server_stderr = None
  112. self._server_popen = None
  113. self._filetypes_with_keywords_loaded = set()
  114. self._ycmd_keepalive = YcmdKeepalive()
  115. self._server_is_ready_with_cache = False
  116. self._SetUpLogging()
  117. self._SetUpServer()
  118. self._ycmd_keepalive.Start()
  119. def _SetUpServer( self ):
  120. self._available_completers = {}
  121. self._user_notified_about_crash = False
  122. self._filetypes_with_keywords_loaded = set()
  123. self._server_is_ready_with_cache = False
  124. self._message_poll_request = None
  125. self._user_options = base.GetUserOptions()
  126. self._omnicomp = OmniCompleter( self._user_options )
  127. self._buffers = BufferDict( self._user_options )
  128. self._SetLogLevel()
  129. hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
  130. options_dict = dict( self._user_options )
  131. options_dict[ 'hmac_secret' ] = utils.ToUnicode(
  132. base64.b64encode( hmac_secret ) )
  133. options_dict[ 'server_keep_logfiles' ] = self._user_options[
  134. 'keep_logfiles' ]
  135. # The temp options file is deleted by ycmd during startup.
  136. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
  137. json.dump( options_dict, options_file )
  138. server_port = utils.GetUnusedLocalhostPort()
  139. BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
  140. BaseRequest.hmac_secret = hmac_secret
  141. try:
  142. python_interpreter = paths.PathToPythonInterpreter()
  143. except RuntimeError as error:
  144. error_message = (
  145. "Unable to start the ycmd server. {0}. "
  146. "Correct the error then restart the server "
  147. "with ':YcmRestartServer'.".format( str( error ).rstrip( '.' ) ) )
  148. self._logger.exception( error_message )
  149. vimsupport.PostVimMessage( error_message )
  150. return
  151. args = [ python_interpreter,
  152. paths.PathToServerScript(),
  153. '--port={0}'.format( server_port ),
  154. '--options_file={0}'.format( options_file.name ),
  155. '--log={0}'.format( self._user_options[ 'log_level' ] ),
  156. '--idle_suicide_seconds={0}'.format(
  157. SERVER_IDLE_SUICIDE_SECONDS ) ]
  158. self._server_stdout = utils.CreateLogfile(
  159. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) )
  160. self._server_stderr = utils.CreateLogfile(
  161. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) )
  162. args.append( '--stdout={0}'.format( self._server_stdout ) )
  163. args.append( '--stderr={0}'.format( self._server_stderr ) )
  164. if self._user_options[ 'keep_logfiles' ]:
  165. args.append( '--keep_logfiles' )
  166. self._server_popen = utils.SafePopen( args, stdin_windows = PIPE,
  167. stdout = PIPE, stderr = PIPE )
  168. def _SetUpLogging( self ):
  169. def FreeFileFromOtherProcesses( file_object ):
  170. if utils.OnWindows():
  171. from ctypes import windll
  172. import msvcrt
  173. file_handle = msvcrt.get_osfhandle( file_object.fileno() )
  174. windll.kernel32.SetHandleInformation( file_handle,
  175. HANDLE_FLAG_INHERIT,
  176. 0 )
  177. self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT )
  178. handler = logging.FileHandler( self._client_logfile )
  179. # On Windows and Python prior to 3.4, file handles are inherited by child
  180. # processes started with at least one replaced standard stream, which is the
  181. # case when we start the ycmd server (we are redirecting all standard
  182. # outputs into a pipe). These files cannot be removed while the child
  183. # processes are still up. This is not desirable for a logfile because we
  184. # want to remove it at Vim exit without having to wait for the ycmd server
  185. # to be completely shut down. We need to make the logfile handle
  186. # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more
  187. # details.
  188. FreeFileFromOtherProcesses( handler.stream )
  189. formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )
  190. handler.setFormatter( formatter )
  191. self._logger.addHandler( handler )
  192. def _SetLogLevel( self ):
  193. log_level = self._user_options[ 'log_level' ]
  194. numeric_level = getattr( logging, log_level.upper(), None )
  195. if not isinstance( numeric_level, int ):
  196. raise ValueError( 'Invalid log level: {0}'.format( log_level ) )
  197. self._logger.setLevel( numeric_level )
  198. def IsServerAlive( self ):
  199. # When the process hasn't finished yet, poll() returns None.
  200. return bool( self._server_popen ) and self._server_popen.poll() is None
  201. def CheckIfServerIsReady( self ):
  202. if not self._server_is_ready_with_cache and self.IsServerAlive():
  203. self._server_is_ready_with_cache = BaseRequest().GetDataFromHandler(
  204. 'ready', display_message = False )
  205. return self._server_is_ready_with_cache
  206. def IsServerReady( self ):
  207. return self._server_is_ready_with_cache
  208. def NotifyUserIfServerCrashed( self ):
  209. if ( not self._server_popen or self._user_notified_about_crash or
  210. self.IsServerAlive() ):
  211. return
  212. self._user_notified_about_crash = True
  213. return_code = self._server_popen.poll()
  214. logfile = os.path.basename( self._server_stderr )
  215. # See https://github.com/Valloric/ycmd#exit-codes for the list of exit
  216. # codes.
  217. if return_code == 3:
  218. error_message = CORE_UNEXPECTED_MESSAGE.format( logfile = logfile )
  219. elif return_code == 4:
  220. error_message = CORE_MISSING_MESSAGE
  221. elif return_code == 5:
  222. error_message = CORE_PYTHON2_MESSAGE
  223. elif return_code == 6:
  224. error_message = CORE_PYTHON3_MESSAGE
  225. elif return_code == 7:
  226. error_message = CORE_OUTDATED_MESSAGE
  227. else:
  228. error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format( code = return_code,
  229. logfile = logfile )
  230. error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message
  231. self._logger.error( error_message )
  232. vimsupport.PostVimMessage( error_message )
  233. def ServerPid( self ):
  234. if not self._server_popen:
  235. return -1
  236. return self._server_popen.pid
  237. def _ShutdownServer( self ):
  238. SendShutdownRequest()
  239. def RestartServer( self ):
  240. vimsupport.PostVimMessage( 'Restarting ycmd server...' )
  241. self._ShutdownServer()
  242. self._SetUpServer()
  243. def SendCompletionRequest( self, force_semantic = False ):
  244. request_data = BuildRequestData()
  245. request_data[ 'force_semantic' ] = force_semantic
  246. if not self.NativeFiletypeCompletionUsable():
  247. wrapped_request_data = RequestWrap( request_data )
  248. if self._omnicomp.ShouldUseNow( wrapped_request_data ):
  249. self._latest_completion_request = OmniCompletionRequest(
  250. self._omnicomp, wrapped_request_data )
  251. self._latest_completion_request.Start()
  252. return
  253. self._AddExtraConfDataIfNeeded( request_data )
  254. self._latest_completion_request = CompletionRequest( request_data )
  255. self._latest_completion_request.Start()
  256. def CompletionRequestReady( self ):
  257. return bool( self._latest_completion_request and
  258. self._latest_completion_request.Done() )
  259. def GetCompletionResponse( self ):
  260. response = self._latest_completion_request.Response()
  261. response[ 'completions' ] = base.AdjustCandidateInsertionText(
  262. response[ 'completions' ] )
  263. return response
  264. def SendSignatureHelpRequest( self ):
  265. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  266. if not self._signature_help_available_requests[ filetype ].Done():
  267. return
  268. sig_help_available = self._signature_help_available_requests[
  269. filetype ].Response()
  270. if sig_help_available == 'NO':
  271. return
  272. if sig_help_available == 'PENDING':
  273. # Send another /signature_help_available request
  274. self._signature_help_available_requests[ filetype ].Start( filetype )
  275. return
  276. if not self.NativeFiletypeCompletionUsable():
  277. return
  278. if not self._latest_completion_request:
  279. return
  280. request_data = self._latest_completion_request.request_data.copy()
  281. request_data[ 'signature_help_state' ] = self._signature_help_state.state
  282. self._AddExtraConfDataIfNeeded( request_data )
  283. self._latest_signature_help_request = SignatureHelpRequest(
  284. request_data )
  285. self._latest_signature_help_request.Start()
  286. def SignatureHelpRequestReady( self ):
  287. return bool( self._latest_signature_help_request and
  288. self._latest_signature_help_request.Done() )
  289. def GetSignatureHelpResponse( self ):
  290. return self._latest_signature_help_request.Response()
  291. def ClearSignatureHelp( self ):
  292. self.UpdateSignatureHelp( {} )
  293. if self._latest_signature_help_request:
  294. self._latest_signature_help_request.Reset()
  295. def UpdateSignatureHelp( self, signature_info ):
  296. self._signature_help_state = signature_help.UpdateSignatureHelp(
  297. self._signature_help_state,
  298. signature_info )
  299. def SendCommandRequest( self,
  300. arguments,
  301. modifiers,
  302. has_range,
  303. start_line,
  304. end_line ):
  305. final_arguments = []
  306. for argument in arguments:
  307. # The ft= option which specifies the completer when running a command is
  308. # ignored because it has not been working for a long time. The option is
  309. # still parsed to not break users that rely on it.
  310. if argument.startswith( 'ft=' ):
  311. continue
  312. final_arguments.append( argument )
  313. extra_data = {
  314. 'options': {
  315. 'tab_size': vimsupport.GetIntValue( 'shiftwidth()' ),
  316. 'insert_spaces': vimsupport.GetBoolValue( '&expandtab' )
  317. }
  318. }
  319. if has_range:
  320. extra_data.update( vimsupport.BuildRange( start_line, end_line ) )
  321. self._AddExtraConfDataIfNeeded( extra_data )
  322. return SendCommandRequest( final_arguments,
  323. modifiers,
  324. self._user_options[ 'goto_buffer_command' ],
  325. extra_data )
  326. def GetDefinedSubcommands( self ):
  327. subcommands = BaseRequest().PostDataToHandler( BuildRequestData(),
  328. 'defined_subcommands' )
  329. return subcommands if subcommands else []
  330. def GetCurrentCompletionRequest( self ):
  331. return self._latest_completion_request
  332. def GetOmniCompleter( self ):
  333. return self._omnicomp
  334. def FiletypeCompleterExistsForFiletype( self, filetype ):
  335. try:
  336. return self._available_completers[ filetype ]
  337. except KeyError:
  338. pass
  339. exists_completer = SendCompleterAvailableRequest( filetype )
  340. if exists_completer is None:
  341. return False
  342. self._available_completers[ filetype ] = exists_completer
  343. return exists_completer
  344. def NativeFiletypeCompletionAvailable( self ):
  345. return any( self.FiletypeCompleterExistsForFiletype( x ) for x in
  346. vimsupport.CurrentFiletypes() )
  347. def NativeFiletypeCompletionUsable( self ):
  348. disabled_filetypes = self._user_options[
  349. 'filetype_specific_completion_to_disable' ]
  350. return ( vimsupport.CurrentFiletypesEnabled( disabled_filetypes ) and
  351. self.NativeFiletypeCompletionAvailable() )
  352. def NeedsReparse( self ):
  353. return self.CurrentBuffer().NeedsReparse()
  354. def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ):
  355. if not self._user_options[ 'show_diagnostics_ui' ]:
  356. return
  357. bufnr = vimsupport.GetBufferNumberForFilename( filepath )
  358. if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ):
  359. # Note: We only update location lists, etc. for visible buffers, because
  360. # otherwise we default to using the current location list and the results
  361. # are that non-visible buffer errors clobber visible ones.
  362. self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics )
  363. else:
  364. # The project contains errors in file "filepath", but that file is not
  365. # open in any buffer. This happens for Language Server Protocol-based
  366. # completers, as they return diagnostics for the entire "project"
  367. # asynchronously (rather than per-file in the response to the parse
  368. # request).
  369. #
  370. # There are a number of possible approaches for
  371. # this, but for now we simply ignore them. Other options include:
  372. # - Use the QuickFix list to report project errors?
  373. # - Use a special buffer for project errors
  374. # - Put them in the location list of whatever the "current" buffer is
  375. # - Store them in case the buffer is opened later
  376. # - add a :YcmProjectDiags command
  377. # - Add them to errror/warning _counts_ but not any actual location list
  378. # or other
  379. # - etc.
  380. #
  381. # However, none of those options are great, and lead to their own
  382. # complexities. So for now, we just ignore these diagnostics for files not
  383. # open in any buffer.
  384. pass
  385. def OnPeriodicTick( self ):
  386. if not self.IsServerAlive():
  387. # Server has died. We'll reset when the server is started again.
  388. return False
  389. elif not self.IsServerReady():
  390. # Try again in a jiffy
  391. return True
  392. if not self._message_poll_request:
  393. self._message_poll_request = MessagesPoll()
  394. if not self._message_poll_request.Poll( self ):
  395. # Don't poll again until some event which might change the server's mind
  396. # about whether to provide messages for the current buffer (e.g. buffer
  397. # visit, file ready to parse, etc.)
  398. self._message_poll_request = None
  399. return False
  400. # Poll again in a jiffy
  401. return True
  402. def OnFileReadyToParse( self ):
  403. if not self.IsServerAlive():
  404. self.NotifyUserIfServerCrashed()
  405. return
  406. if not self.IsServerReady():
  407. return
  408. extra_data = {}
  409. self._AddTagsFilesIfNeeded( extra_data )
  410. self._AddSyntaxDataIfNeeded( extra_data )
  411. self._AddExtraConfDataIfNeeded( extra_data )
  412. self.CurrentBuffer().SendParseRequest( extra_data )
  413. def OnBufferUnload( self, deleted_buffer_number ):
  414. SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number )
  415. def UpdateMatches( self ):
  416. self.CurrentBuffer().UpdateMatches()
  417. def OnBufferVisit( self ):
  418. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  419. # The constructor of dictionary values starts the request,
  420. # so the line below fires a new request only if the dictionary
  421. # value is accessed for the first time.
  422. self._signature_help_available_requests[ filetype ].Done()
  423. extra_data = {}
  424. self._AddUltiSnipsDataIfNeeded( extra_data )
  425. SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data )
  426. def CurrentBuffer( self ):
  427. return self._buffers[ vimsupport.GetCurrentBufferNumber() ]
  428. def OnInsertLeave( self ):
  429. SendEventNotificationAsync( 'InsertLeave' )
  430. def OnCursorMoved( self ):
  431. self.CurrentBuffer().OnCursorMoved()
  432. def _CleanLogfile( self ):
  433. logging.shutdown()
  434. if not self._user_options[ 'keep_logfiles' ]:
  435. if self._client_logfile:
  436. utils.RemoveIfExists( self._client_logfile )
  437. def OnVimLeave( self ):
  438. self._ShutdownServer()
  439. self._CleanLogfile()
  440. def OnCurrentIdentifierFinished( self ):
  441. SendEventNotificationAsync( 'CurrentIdentifierFinished' )
  442. def OnCompleteDone( self ):
  443. completion_request = self.GetCurrentCompletionRequest()
  444. if completion_request:
  445. completion_request.OnCompleteDone()
  446. def GetErrorCount( self ):
  447. return self.CurrentBuffer().GetErrorCount()
  448. def GetWarningCount( self ):
  449. return self.CurrentBuffer().GetWarningCount()
  450. def _PopulateLocationListWithLatestDiagnostics( self ):
  451. return self.CurrentBuffer().PopulateLocationList()
  452. def FileParseRequestReady( self ):
  453. # Return True if server is not ready yet, to stop repeating check timer.
  454. return ( not self.IsServerReady() or
  455. self.CurrentBuffer().FileParseRequestReady() )
  456. def HandleFileParseRequest( self, block = False ):
  457. if not self.IsServerReady():
  458. return
  459. current_buffer = self.CurrentBuffer()
  460. # Order is important here:
  461. # FileParseRequestReady has a low cost, while
  462. # NativeFiletypeCompletionUsable is a blocking server request
  463. if ( not current_buffer.IsResponseHandled() and
  464. current_buffer.FileParseRequestReady( block ) and
  465. self.NativeFiletypeCompletionUsable() ):
  466. if self._user_options[ 'show_diagnostics_ui' ]:
  467. # Forcefuly update the location list, etc. from the parse request when
  468. # doing something like :YcmDiags
  469. current_buffer.UpdateDiagnostics( block )
  470. else:
  471. # If the user disabled diagnostics, we just want to check
  472. # the _latest_file_parse_request for any exception or UnknownExtraConf
  473. # response, to allow the server to raise configuration warnings, etc.
  474. # to the user. We ignore any other supplied data.
  475. current_buffer.GetResponse()
  476. # We set the file parse request as handled because we want to prevent
  477. # repeated issuing of the same warnings/errors/prompts. Setting this
  478. # makes IsRequestHandled return True until the next request is created.
  479. #
  480. # Note: it is the server's responsibility to determine the frequency of
  481. # error/warning/prompts when receiving a FileReadyToParse event, but
  482. # it is our responsibility to ensure that we only apply the
  483. # warning/error/prompt received once (for each event).
  484. current_buffer.MarkResponseHandled()
  485. def ShouldResendFileParseRequest( self ):
  486. return self.CurrentBuffer().ShouldResendParseRequest()
  487. def DebugInfo( self ):
  488. debug_info = ''
  489. if self._client_logfile:
  490. debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
  491. extra_data = {}
  492. self._AddExtraConfDataIfNeeded( extra_data )
  493. debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) )
  494. debug_info += 'Server running at: {0}\n'.format(
  495. BaseRequest.server_location )
  496. if self._server_popen:
  497. debug_info += 'Server process ID: {0}\n'.format( self._server_popen.pid )
  498. if self._server_stdout and self._server_stderr:
  499. debug_info += ( 'Server logfiles:\n'
  500. ' {0}\n'
  501. ' {1}'.format( self._server_stdout,
  502. self._server_stderr ) )
  503. return debug_info
  504. def GetLogfiles( self ):
  505. logfiles_list = [ self._client_logfile,
  506. self._server_stdout,
  507. self._server_stderr ]
  508. extra_data = {}
  509. self._AddExtraConfDataIfNeeded( extra_data )
  510. debug_info = SendDebugInfoRequest( extra_data )
  511. if debug_info:
  512. completer = debug_info[ 'completer' ]
  513. if completer:
  514. for server in completer[ 'servers' ]:
  515. logfiles_list.extend( server[ 'logfiles' ] )
  516. logfiles = {}
  517. for logfile in logfiles_list:
  518. logfiles[ os.path.basename( logfile ) ] = logfile
  519. return logfiles
  520. def _OpenLogfile( self, logfile ):
  521. # Open log files in a horizontal window with the same behavior as the
  522. # preview window (same height and winfixheight enabled). Automatically
  523. # watch for changes. Set the cursor position at the end of the file.
  524. options = {
  525. 'size': vimsupport.GetIntValue( '&previewheight' ),
  526. 'fix': True,
  527. 'focus': False,
  528. 'watch': True,
  529. 'position': 'end'
  530. }
  531. vimsupport.OpenFilename( logfile, options )
  532. def _CloseLogfile( self, logfile ):
  533. vimsupport.CloseBuffersForFilename( logfile )
  534. def ToggleLogs( self, *filenames ):
  535. logfiles = self.GetLogfiles()
  536. if not filenames:
  537. sorted_logfiles = sorted( list( logfiles ) )
  538. try:
  539. logfile_index = vimsupport.SelectFromList(
  540. 'Which logfile do you wish to open (or close if already open)?',
  541. sorted_logfiles )
  542. except RuntimeError as e:
  543. vimsupport.PostVimMessage( str( e ) )
  544. return
  545. logfile = logfiles[ sorted_logfiles[ logfile_index ] ]
  546. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  547. self._OpenLogfile( logfile )
  548. else:
  549. self._CloseLogfile( logfile )
  550. return
  551. for filename in set( filenames ):
  552. if filename not in logfiles:
  553. continue
  554. logfile = logfiles[ filename ]
  555. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  556. self._OpenLogfile( logfile )
  557. continue
  558. self._CloseLogfile( logfile )
  559. def ShowDetailedDiagnostic( self ):
  560. detailed_diagnostic = BaseRequest().PostDataToHandler(
  561. BuildRequestData(), 'detailed_diagnostic' )
  562. if detailed_diagnostic and 'message' in detailed_diagnostic:
  563. vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ],
  564. warning = False )
  565. def ForceCompileAndDiagnostics( self ):
  566. if not self.NativeFiletypeCompletionUsable():
  567. vimsupport.PostVimMessage(
  568. 'Native filetype completion not supported for current file, '
  569. 'cannot force recompilation.', warning = False )
  570. return False
  571. vimsupport.PostVimMessage(
  572. 'Forcing compilation, this will block Vim until done.',
  573. warning = False )
  574. self.OnFileReadyToParse()
  575. self.HandleFileParseRequest( block = True )
  576. vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False )
  577. return True
  578. def ShowDiagnostics( self ):
  579. if not self.ForceCompileAndDiagnostics():
  580. return
  581. if not self._PopulateLocationListWithLatestDiagnostics():
  582. vimsupport.PostVimMessage( 'No warnings or errors detected.',
  583. warning = False )
  584. return
  585. if self._user_options[ 'open_loclist_on_ycm_diags' ]:
  586. vimsupport.OpenLocationList( focus = True )
  587. def _AddSyntaxDataIfNeeded( self, extra_data ):
  588. if not self._user_options[ 'seed_identifiers_with_syntax' ]:
  589. return
  590. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  591. if filetype in self._filetypes_with_keywords_loaded:
  592. return
  593. if self.IsServerReady():
  594. self._filetypes_with_keywords_loaded.add( filetype )
  595. extra_data[ 'syntax_keywords' ] = list(
  596. syntax_parse.SyntaxKeywordsForCurrentBuffer() )
  597. def _AddTagsFilesIfNeeded( self, extra_data ):
  598. def GetTagFiles():
  599. tag_files = vim.eval( 'tagfiles()' )
  600. return [ os.path.join( utils.GetCurrentDirectory(), tag_file )
  601. for tag_file in tag_files ]
  602. if not self._user_options[ 'collect_identifiers_from_tags_files' ]:
  603. return
  604. extra_data[ 'tag_files' ] = GetTagFiles()
  605. def _AddExtraConfDataIfNeeded( self, extra_data ):
  606. def BuildExtraConfData( extra_conf_vim_data ):
  607. extra_conf_data = {}
  608. for expr in extra_conf_vim_data:
  609. try:
  610. extra_conf_data[ expr ] = vimsupport.VimExpressionToPythonType( expr )
  611. except vim.error:
  612. message = (
  613. "Error evaluating '{expr}' in the 'g:ycm_extra_conf_vim_data' "
  614. "option.".format( expr = expr ) )
  615. vimsupport.PostVimMessage( message, truncate = True )
  616. self._logger.exception( message )
  617. return extra_conf_data
  618. extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ]
  619. if extra_conf_vim_data:
  620. extra_data[ 'extra_conf_data' ] = BuildExtraConfData(
  621. extra_conf_vim_data )
  622. def _AddUltiSnipsDataIfNeeded( self, extra_data ):
  623. # See :h UltiSnips#SnippetsInCurrentScope.
  624. try:
  625. vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' )
  626. except vim.error:
  627. return
  628. snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' )
  629. extra_data[ 'ultisnips_snippets' ] = [
  630. { 'trigger': trigger,
  631. 'description': snippet[ 'description' ] }
  632. for trigger, snippet in iteritems( snippets )
  633. ]