youcompleteme.py 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. # Copyright (C) 2011-2024 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. import base64
  18. import json
  19. import logging
  20. import os
  21. import signal
  22. import vim
  23. from subprocess import PIPE
  24. from tempfile import NamedTemporaryFile
  25. from ycm import base, paths, signature_help, vimsupport
  26. from ycm.buffer import BufferDict
  27. from ycmd import utils
  28. from ycmd.request_wrap import RequestWrap
  29. from ycm.omni_completer import OmniCompleter
  30. from ycm import syntax_parse
  31. from ycm.hierarchy_tree import HierarchyTree
  32. from ycm.client.ycmd_keepalive import YcmdKeepalive
  33. from ycm.client.base_request import BaseRequest, BuildRequestData
  34. from ycm.client.completer_available_request import SendCompleterAvailableRequest
  35. from ycm.client.command_request import ( SendCommandRequest,
  36. SendCommandRequestAsync,
  37. GetCommandResponse,
  38. GetRawCommandResponse )
  39. from ycm.client.completion_request import CompletionRequest
  40. from ycm.client.resolve_completion_request import ResolveCompletionItem
  41. from ycm.client.signature_help_request import ( SignatureHelpRequest,
  42. SigHelpAvailableByFileType )
  43. from ycm.client.debug_info_request import ( SendDebugInfoRequest,
  44. FormatDebugInfoResponse )
  45. from ycm.client.omni_completion_request import OmniCompletionRequest
  46. from ycm.client.event_notification import SendEventNotificationAsync
  47. from ycm.client.shutdown_request import SendShutdownRequest
  48. from ycm.client.messages_request import MessagesPoll
  49. def PatchNoProxy():
  50. current_value = os.environ.get( 'no_proxy', '' )
  51. additions = '127.0.0.1,localhost'
  52. os.environ[ 'no_proxy' ] = ( additions if not current_value
  53. else current_value + ',' + additions )
  54. # We need this so that Requests doesn't end up using the local HTTP proxy when
  55. # talking to ycmd. Users should actually be setting this themselves when
  56. # configuring a proxy server on their machine, but most don't know they need to
  57. # or how to do it, so we do it for them.
  58. # Relevant issues:
  59. # https://github.com/Valloric/YouCompleteMe/issues/641
  60. # https://github.com/kennethreitz/requests/issues/879
  61. PatchNoProxy()
  62. # Force the Python interpreter embedded in Vim (in which we are running) to
  63. # ignore the SIGINT signal. This helps reduce the fallout of a user pressing
  64. # Ctrl-C in Vim.
  65. signal.signal( signal.SIGINT, signal.SIG_IGN )
  66. HMAC_SECRET_LENGTH = 16
  67. SERVER_SHUTDOWN_MESSAGE = (
  68. "The ycmd server SHUT DOWN (restart with ':YcmRestartServer')." )
  69. EXIT_CODE_UNEXPECTED_MESSAGE = (
  70. "Unexpected exit code {code}. "
  71. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  72. CORE_UNEXPECTED_MESSAGE = (
  73. "Unexpected error while loading the YCM core library. "
  74. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  75. CORE_MISSING_MESSAGE = (
  76. 'YCM core library not detected; you need to compile YCM before using it. '
  77. 'Follow the instructions in the documentation.' )
  78. CORE_OUTDATED_MESSAGE = (
  79. 'YCM core library too old; PLEASE RECOMPILE by running the install.py '
  80. 'script. See the documentation for more details.' )
  81. PYTHON_TOO_OLD_MESSAGE = (
  82. 'Your python is too old to run YCM server. '
  83. 'Please see troubleshooting guide on YCM GitHub wiki.'
  84. )
  85. SERVER_IDLE_SUICIDE_SECONDS = 1800 # 30 minutes
  86. CLIENT_LOGFILE_FORMAT = 'ycm_'
  87. SERVER_LOGFILE_FORMAT = 'ycmd_{port}_{std}_'
  88. # Flag to set a file handle inheritable by child processes on Windows. See
  89. # https://msdn.microsoft.com/en-us/library/ms724935.aspx
  90. HANDLE_FLAG_INHERIT = 0x00000001
  91. class YouCompleteMe:
  92. def __init__( self, default_options = {} ):
  93. self._logger = logging.getLogger( 'ycm' )
  94. self._client_logfile = None
  95. self._server_stdout = None
  96. self._server_stderr = None
  97. self._server_popen = None
  98. self._default_options = default_options
  99. self._ycmd_keepalive = YcmdKeepalive()
  100. self._SetUpLogging()
  101. self._SetUpServer()
  102. self._ycmd_keepalive.Start()
  103. self._current_hierarchy = HierarchyTree()
  104. def InitializeCurrentHierarchy( self, items, kind ):
  105. return self._current_hierarchy.SetRootNode( items, kind )
  106. def UpdateCurrentHierarchy( self, handle : int, direction : str ):
  107. if not self._current_hierarchy.UpdateChangesRoot( handle, direction ):
  108. items = self._ResolveHierarchyItem( handle, direction )
  109. self._current_hierarchy.UpdateHierarchy( handle, items, direction )
  110. if items is not None and direction == 'up':
  111. offset = sum( len( item[ 'locations' ] ) for item in items )
  112. else:
  113. offset = 0
  114. return self._current_hierarchy.HierarchyToLines(), offset
  115. else:
  116. location = self._current_hierarchy.HandleToRootLocation( handle )
  117. kind = self._current_hierarchy._kind
  118. self._current_hierarchy.Reset()
  119. items = GetRawCommandResponse(
  120. [ f'{ kind.title() }Hierarchy' ],
  121. silent = False,
  122. location = location
  123. )
  124. # [ 0 ] chooses the data for the 1st (and only) line.
  125. # [ 1 ] chooses only the handle
  126. handle = self.InitializeCurrentHierarchy( items, kind )[ 0 ][ 1 ]
  127. return self.UpdateCurrentHierarchy( handle, direction )
  128. def _ResolveHierarchyItem( self, handle : int, direction : str ):
  129. return GetRawCommandResponse(
  130. self._current_hierarchy.ResolveArguments( handle, direction ),
  131. silent = False
  132. )
  133. def ShouldResolveItem( self, handle : int, direction : str ):
  134. return self._current_hierarchy.ShouldResolveItem( handle, direction )
  135. def ResetCurrentHierarchy( self ):
  136. self._current_hierarchy.Reset()
  137. def JumpToHierarchyItem( self, handle ):
  138. self._current_hierarchy.JumpToItem(
  139. handle,
  140. self._user_options[ 'goto_buffer_command' ] )
  141. def _SetUpServer( self ):
  142. self._available_completers = {}
  143. self._user_notified_about_crash = False
  144. self._filetypes_with_keywords_loaded = set()
  145. self._server_is_ready_with_cache = False
  146. self._message_poll_requests = {}
  147. self._latest_completion_request = None
  148. self._latest_signature_help_request = None
  149. self._signature_help_available_requests = SigHelpAvailableByFileType()
  150. self._command_requests = {}
  151. self._next_command_request_id = 0
  152. self._signature_help_state = signature_help.SignatureHelpState()
  153. self._user_options = base.GetUserOptions( self._default_options )
  154. self._omnicomp = OmniCompleter( self._user_options )
  155. self._buffers = BufferDict( self._user_options )
  156. self._SetLogLevel()
  157. hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
  158. options_dict = dict( self._user_options )
  159. options_dict[ 'hmac_secret' ] = utils.ToUnicode(
  160. base64.b64encode( hmac_secret ) )
  161. options_dict[ 'server_keep_logfiles' ] = self._user_options[
  162. 'keep_logfiles' ]
  163. # The temp options file is deleted by ycmd during startup.
  164. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
  165. json.dump( options_dict, options_file )
  166. server_port = utils.GetUnusedLocalhostPort()
  167. BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
  168. BaseRequest.hmac_secret = hmac_secret
  169. try:
  170. python_interpreter = paths.PathToPythonInterpreter()
  171. except RuntimeError as error:
  172. error_message = (
  173. f"Unable to start the ycmd server. { str( error ).rstrip( '.' ) }. "
  174. "Correct the error then restart the server "
  175. "with ':YcmRestartServer'." )
  176. self._logger.exception( error_message )
  177. vimsupport.PostVimMessage( error_message )
  178. return
  179. args = [ python_interpreter,
  180. paths.PathToServerScript(),
  181. f'--port={ server_port }',
  182. f'--options_file={ options_file.name }',
  183. f'--log={ self._user_options[ "log_level" ] }',
  184. f'--idle_suicide_seconds={ SERVER_IDLE_SUICIDE_SECONDS }' ]
  185. self._server_stdout = utils.CreateLogfile(
  186. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) )
  187. self._server_stderr = utils.CreateLogfile(
  188. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) )
  189. args.append( f'--stdout={ self._server_stdout }' )
  190. args.append( f'--stderr={ self._server_stderr }' )
  191. if self._user_options[ 'keep_logfiles' ]:
  192. args.append( '--keep_logfiles' )
  193. self._server_popen = utils.SafePopen( args, stdin_windows = PIPE,
  194. stdout = PIPE, stderr = PIPE )
  195. def _SetUpLogging( self ):
  196. def FreeFileFromOtherProcesses( file_object ):
  197. if utils.OnWindows():
  198. from ctypes import windll
  199. import msvcrt
  200. file_handle = msvcrt.get_osfhandle( file_object.fileno() )
  201. windll.kernel32.SetHandleInformation( file_handle,
  202. HANDLE_FLAG_INHERIT,
  203. 0 )
  204. self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT )
  205. handler = logging.FileHandler( self._client_logfile )
  206. # On Windows and Python prior to 3.4, file handles are inherited by child
  207. # processes started with at least one replaced standard stream, which is the
  208. # case when we start the ycmd server (we are redirecting all standard
  209. # outputs into a pipe). These files cannot be removed while the child
  210. # processes are still up. This is not desirable for a logfile because we
  211. # want to remove it at Vim exit without having to wait for the ycmd server
  212. # to be completely shut down. We need to make the logfile handle
  213. # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more
  214. # details.
  215. FreeFileFromOtherProcesses( handler.stream )
  216. formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )
  217. handler.setFormatter( formatter )
  218. self._logger.addHandler( handler )
  219. def _SetLogLevel( self ):
  220. log_level = self._user_options[ 'log_level' ]
  221. numeric_level = getattr( logging, log_level.upper(), None )
  222. if not isinstance( numeric_level, int ):
  223. raise ValueError( f'Invalid log level: { log_level }' )
  224. self._logger.setLevel( numeric_level )
  225. def IsServerAlive( self ):
  226. # When the process hasn't finished yet, poll() returns None.
  227. return bool( self._server_popen ) and self._server_popen.poll() is None
  228. def CheckIfServerIsReady( self ):
  229. if not self._server_is_ready_with_cache and self.IsServerAlive():
  230. self._server_is_ready_with_cache = BaseRequest().GetDataFromHandler(
  231. 'ready', display_message = False )
  232. return self._server_is_ready_with_cache
  233. def IsServerReady( self ):
  234. return self._server_is_ready_with_cache
  235. def NotifyUserIfServerCrashed( self ):
  236. if ( not self._server_popen or self._user_notified_about_crash or
  237. self.IsServerAlive() ):
  238. return
  239. self._user_notified_about_crash = True
  240. return_code = self._server_popen.poll()
  241. logfile = os.path.basename( self._server_stderr )
  242. # See https://github.com/Valloric/ycmd#exit-codes for the list of exit
  243. # codes.
  244. if return_code == 3:
  245. error_message = CORE_UNEXPECTED_MESSAGE.format( logfile = logfile )
  246. elif return_code == 4:
  247. error_message = CORE_MISSING_MESSAGE
  248. elif return_code == 7:
  249. error_message = CORE_OUTDATED_MESSAGE
  250. elif return_code == 8:
  251. # TODO: here we could retry but discard g:ycm_server_python_interpreter
  252. error_message = PYTHON_TOO_OLD_MESSAGE
  253. else:
  254. error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format( code = return_code,
  255. logfile = logfile )
  256. if return_code != 8:
  257. error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message
  258. self._logger.error( error_message )
  259. vimsupport.PostVimMessage( error_message )
  260. def ServerPid( self ):
  261. if not self._server_popen:
  262. return -1
  263. return self._server_popen.pid
  264. def _ShutdownServer( self ):
  265. SendShutdownRequest()
  266. def RestartServer( self ):
  267. vimsupport.PostVimMessage( 'Restarting ycmd server...' )
  268. self._ShutdownServer()
  269. self._SetUpServer()
  270. def SendCompletionRequest( self, force_semantic = False ):
  271. request_data = BuildRequestData()
  272. request_data[ 'force_semantic' ] = force_semantic
  273. if not self.NativeFiletypeCompletionUsable():
  274. wrapped_request_data = RequestWrap( request_data )
  275. if self._omnicomp.ShouldUseNow( wrapped_request_data ):
  276. self._latest_completion_request = OmniCompletionRequest(
  277. self._omnicomp, wrapped_request_data )
  278. self._latest_completion_request.Start()
  279. return
  280. self._AddExtraConfDataIfNeeded( request_data )
  281. self._latest_completion_request = CompletionRequest( request_data )
  282. self._latest_completion_request.Start()
  283. def CompletionRequestReady( self ):
  284. return bool( self._latest_completion_request and
  285. self._latest_completion_request.Done() )
  286. def GetCompletionResponse( self ):
  287. return self._latest_completion_request.Response()
  288. def SignatureHelpAvailableRequestComplete( self, filetype, send_new=True ):
  289. """Triggers or polls signature help available request. Returns whether or
  290. not the request is complete. When send_new is False, won't send a new
  291. request, only return the current status (This is used by the tests)"""
  292. if not send_new and filetype not in self._signature_help_available_requests:
  293. return False
  294. return self._signature_help_available_requests[ filetype ].Done()
  295. def SendSignatureHelpRequest( self ):
  296. """Send a signature help request, if we're ready to. Return whether or not a
  297. request was sent (and should be checked later)"""
  298. if not self.NativeFiletypeCompletionUsable():
  299. return False
  300. for filetype in vimsupport.CurrentFiletypes():
  301. if not self.SignatureHelpAvailableRequestComplete( filetype ):
  302. continue
  303. sig_help_available = self._signature_help_available_requests[
  304. filetype ].Response()
  305. if sig_help_available == 'NO':
  306. continue
  307. if sig_help_available == 'PENDING':
  308. # Send another /signature_help_available request
  309. self._signature_help_available_requests[ filetype ].Start( filetype )
  310. continue
  311. if not self._latest_completion_request:
  312. return False
  313. request_data = self._latest_completion_request.request_data.copy()
  314. request_data[ 'signature_help_state' ] = (
  315. self._signature_help_state.IsActive()
  316. )
  317. self._AddExtraConfDataIfNeeded( request_data )
  318. self._latest_signature_help_request = SignatureHelpRequest( request_data )
  319. self._latest_signature_help_request.Start()
  320. return True
  321. return False
  322. def SignatureHelpRequestReady( self ):
  323. return bool( self._latest_signature_help_request and
  324. self._latest_signature_help_request.Done() )
  325. def GetSignatureHelpResponse( self ):
  326. return self._latest_signature_help_request.Response()
  327. def ClearSignatureHelp( self ):
  328. self.UpdateSignatureHelp( {} )
  329. if self._latest_signature_help_request:
  330. self._latest_signature_help_request.Reset()
  331. def UpdateSignatureHelp( self, signature_info ):
  332. self._signature_help_state = signature_help.UpdateSignatureHelp(
  333. self._signature_help_state,
  334. signature_info )
  335. def _GetCommandRequestArguments( self,
  336. arguments,
  337. has_range,
  338. start_line,
  339. end_line ):
  340. extra_data = {
  341. 'options': {
  342. 'tab_size': vimsupport.GetIntValue( 'shiftwidth()' ),
  343. 'insert_spaces': vimsupport.GetBoolValue( '&expandtab' )
  344. }
  345. }
  346. final_arguments = []
  347. for argument in arguments:
  348. if argument.startswith( 'ft=' ):
  349. extra_data[ 'completer_target' ] = argument[ 3: ]
  350. continue
  351. elif argument.startswith( '--bufnr=' ):
  352. extra_data[ 'bufnr' ] = int( argument[ len( '--bufnr=' ): ] )
  353. continue
  354. final_arguments.append( argument )
  355. if has_range:
  356. extra_data.update( vimsupport.BuildRange( start_line, end_line ) )
  357. self._AddExtraConfDataIfNeeded( extra_data )
  358. return final_arguments, extra_data
  359. def SendCommandRequest( self,
  360. arguments,
  361. modifiers,
  362. has_range,
  363. start_line,
  364. end_line ):
  365. final_arguments, extra_data = self._GetCommandRequestArguments(
  366. arguments,
  367. has_range,
  368. start_line,
  369. end_line )
  370. return SendCommandRequest(
  371. final_arguments,
  372. modifiers,
  373. self._user_options[ 'goto_buffer_command' ],
  374. extra_data )
  375. def GetCommandResponse( self, arguments ):
  376. final_arguments, extra_data = self._GetCommandRequestArguments(
  377. arguments,
  378. False,
  379. 0,
  380. 0 )
  381. return GetCommandResponse( final_arguments, extra_data )
  382. def SendCommandRequestAsync( self,
  383. arguments,
  384. silent = True,
  385. location = None ):
  386. final_arguments, extra_data = self._GetCommandRequestArguments(
  387. arguments,
  388. False,
  389. 0,
  390. 0 )
  391. request_id = self._next_command_request_id
  392. self._next_command_request_id += 1
  393. self._command_requests[ request_id ] = SendCommandRequestAsync(
  394. final_arguments,
  395. extra_data,
  396. silent,
  397. location = location )
  398. return request_id
  399. def GetCommandRequest( self, request_id ):
  400. return self._command_requests.get( request_id )
  401. def FlushCommandRequest( self, request_id ):
  402. self._command_requests.pop( request_id, None )
  403. def GetDefinedSubcommands( self ):
  404. request = BaseRequest()
  405. subcommands = request.PostDataToHandler( BuildRequestData(),
  406. 'defined_subcommands' )
  407. return subcommands if subcommands else []
  408. def GetCurrentCompletionRequest( self ):
  409. return self._latest_completion_request
  410. def GetOmniCompleter( self ):
  411. return self._omnicomp
  412. def FiletypeCompleterExistsForFiletype( self, filetype ):
  413. try:
  414. return self._available_completers[ filetype ]
  415. except KeyError:
  416. pass
  417. exists_completer = SendCompleterAvailableRequest( filetype )
  418. if exists_completer is None:
  419. return False
  420. self._available_completers[ filetype ] = exists_completer
  421. return exists_completer
  422. def NativeFiletypeCompletionAvailable( self ):
  423. return any( self.FiletypeCompleterExistsForFiletype( x ) for x in
  424. vimsupport.CurrentFiletypes() )
  425. def NativeFiletypeCompletionUsable( self ):
  426. disabled_filetypes = self._user_options[
  427. 'filetype_specific_completion_to_disable' ]
  428. return ( vimsupport.CurrentFiletypesEnabled( disabled_filetypes ) and
  429. self.NativeFiletypeCompletionAvailable() )
  430. def NeedsReparse( self ):
  431. return self.CurrentBuffer().NeedsReparse()
  432. def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ):
  433. if not self._user_options[ 'show_diagnostics_ui' ]:
  434. return
  435. bufnr = vimsupport.GetBufferNumberForFilename( filepath )
  436. if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ):
  437. # Note: We only update location lists, etc. for visible buffers, because
  438. # otherwise we default to using the current location list and the results
  439. # are that non-visible buffer errors clobber visible ones.
  440. self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics, True )
  441. else:
  442. # The project contains errors in file "filepath", but that file is not
  443. # open in any buffer. This happens for Language Server Protocol-based
  444. # completers, as they return diagnostics for the entire "project"
  445. # asynchronously (rather than per-file in the response to the parse
  446. # request).
  447. #
  448. # There are a number of possible approaches for
  449. # this, but for now we simply ignore them. Other options include:
  450. # - Use the QuickFix list to report project errors?
  451. # - Use a special buffer for project errors
  452. # - Put them in the location list of whatever the "current" buffer is
  453. # - Store them in case the buffer is opened later
  454. # - add a :YcmProjectDiags command
  455. # - Add them to errror/warning _counts_ but not any actual location list
  456. # or other
  457. # - etc.
  458. #
  459. # However, none of those options are great, and lead to their own
  460. # complexities. So for now, we just ignore these diagnostics for files not
  461. # open in any buffer.
  462. pass
  463. def OnPeriodicTick( self ):
  464. if not self.IsServerAlive():
  465. # Server has died. We'll reset when the server is started again.
  466. return False
  467. elif not self.IsServerReady():
  468. # Try again in a jiffy
  469. return True
  470. for w in vim.windows:
  471. for filetype in vimsupport.FiletypesForBuffer( w.buffer ):
  472. if filetype not in self._message_poll_requests:
  473. self._message_poll_requests[ filetype ] = MessagesPoll( w.buffer )
  474. # None means don't poll this filetype
  475. if ( self._message_poll_requests[ filetype ] and
  476. not self._message_poll_requests[ filetype ].Poll( self ) ):
  477. self._message_poll_requests[ filetype ] = None
  478. return any( self._message_poll_requests.values() )
  479. def OnFileReadyToParse( self ):
  480. if not self.IsServerAlive():
  481. self.NotifyUserIfServerCrashed()
  482. return
  483. if not self.IsServerReady():
  484. return
  485. extra_data = {}
  486. self._AddTagsFilesIfNeeded( extra_data )
  487. self._AddSyntaxDataIfNeeded( extra_data )
  488. self._AddExtraConfDataIfNeeded( extra_data )
  489. self.CurrentBuffer().SendParseRequest( extra_data )
  490. def OnFileSave( self, saved_buffer_number ):
  491. SendEventNotificationAsync( 'FileSave', saved_buffer_number )
  492. def OnBufferUnload( self, deleted_buffer_number ):
  493. SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number )
  494. def UpdateMatches( self ):
  495. self.CurrentBuffer().UpdateMatches()
  496. def OnFileTypeSet( self ):
  497. buffer_number = vimsupport.GetCurrentBufferNumber()
  498. filetypes = vimsupport.CurrentFiletypes()
  499. self._buffers[ buffer_number ].UpdateFromFileTypes( filetypes )
  500. self.OnBufferVisit()
  501. def OnBufferVisit( self ):
  502. for filetype in vimsupport.CurrentFiletypes():
  503. # Send the signature help available request for these filetypes if we need
  504. # to (as a side effect of checking if it is complete)
  505. self.SignatureHelpAvailableRequestComplete( filetype, True )
  506. extra_data = {}
  507. self._AddUltiSnipsDataIfNeeded( extra_data )
  508. SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data )
  509. def CurrentBuffer( self ):
  510. return self.Buffer( vimsupport.GetCurrentBufferNumber() )
  511. def Buffer( self, bufnr ):
  512. return self._buffers[ bufnr ]
  513. def OnInsertEnter( self ):
  514. if not self._user_options[ 'update_diagnostics_in_insert_mode' ]:
  515. self.CurrentBuffer().ClearDiagnosticsUI()
  516. def OnInsertLeave( self ):
  517. async_diags = any( self._message_poll_requests.get( filetype )
  518. for filetype in vimsupport.CurrentFiletypes() )
  519. if ( not self._user_options[ 'update_diagnostics_in_insert_mode' ] and
  520. ( async_diags or not self.CurrentBuffer().ParseRequestPending() ) ):
  521. self.CurrentBuffer().RefreshDiagnosticsUI()
  522. SendEventNotificationAsync( 'InsertLeave' )
  523. def OnCursorMoved( self ):
  524. self.CurrentBuffer().OnCursorMoved()
  525. def _CleanLogfile( self ):
  526. logging.shutdown()
  527. if not self._user_options[ 'keep_logfiles' ]:
  528. if self._client_logfile:
  529. utils.RemoveIfExists( self._client_logfile )
  530. def OnVimLeave( self ):
  531. self._ShutdownServer()
  532. self._CleanLogfile()
  533. def OnCurrentIdentifierFinished( self ):
  534. SendEventNotificationAsync( 'CurrentIdentifierFinished' )
  535. def OnCompleteDone( self ):
  536. completion_request = self.GetCurrentCompletionRequest()
  537. if completion_request:
  538. completion_request.OnCompleteDone()
  539. def ResolveCompletionItem( self, item ):
  540. # Note: As mentioned elsewhere, we replace the current completion request
  541. # with a resolve request. It's not valid to have simultaneous resolve and
  542. # completion requests, because the resolve request uses the request data
  543. # from the last completion request and is therefore dependent on it not
  544. # having changed.
  545. #
  546. # The result of this is that self.GetCurrentCompletionRequest() might return
  547. # either a completion request of a resolve request and it's the
  548. # responsibility of the vimscript code to ensure that it only does one at a
  549. # time. This is handled by re-using the same poller for completions and
  550. # resolves.
  551. completion_request = self.GetCurrentCompletionRequest()
  552. if not completion_request:
  553. return False
  554. request = ResolveCompletionItem( completion_request, item )
  555. if not request:
  556. return False
  557. self._latest_completion_request = request
  558. return True
  559. def GetErrorCount( self ):
  560. return self.CurrentBuffer().GetErrorCount()
  561. def GetWarningCount( self ):
  562. return self.CurrentBuffer().GetWarningCount()
  563. def _PopulateLocationListWithLatestDiagnostics( self ):
  564. return self.CurrentBuffer().PopulateLocationList(
  565. self._user_options[ 'open_loclist_on_ycm_diags' ] )
  566. def FileParseRequestReady( self ):
  567. # Return True if server is not ready yet, to stop repeating check timer.
  568. return ( not self.IsServerReady() or
  569. self.CurrentBuffer().FileParseRequestReady() )
  570. def HandleFileParseRequest( self, block = False ):
  571. if not self.IsServerReady():
  572. return
  573. current_buffer = self.CurrentBuffer()
  574. # Order is important here:
  575. # FileParseRequestReady has a low cost, while
  576. # NativeFiletypeCompletionUsable is a blocking server request
  577. if ( not current_buffer.IsResponseHandled() and
  578. current_buffer.FileParseRequestReady( block ) and
  579. self.NativeFiletypeCompletionUsable() ):
  580. if self._user_options[ 'show_diagnostics_ui' ]:
  581. # Forcefuly update the location list, etc. from the parse request when
  582. # doing something like :YcmDiags
  583. async_diags = any( self._message_poll_requests.get( filetype )
  584. for filetype in vimsupport.CurrentFiletypes() )
  585. current_buffer.UpdateDiagnostics( block or not async_diags )
  586. else:
  587. # If the user disabled diagnostics, we just want to check
  588. # the _latest_file_parse_request for any exception or UnknownExtraConf
  589. # response, to allow the server to raise configuration warnings, etc.
  590. # to the user. We ignore any other supplied data.
  591. current_buffer.GetResponse()
  592. # We set the file parse request as handled because we want to prevent
  593. # repeated issuing of the same warnings/errors/prompts. Setting this
  594. # makes IsRequestHandled return True until the next request is created.
  595. #
  596. # Note: it is the server's responsibility to determine the frequency of
  597. # error/warning/prompts when receiving a FileReadyToParse event, but
  598. # it is our responsibility to ensure that we only apply the
  599. # warning/error/prompt received once (for each event).
  600. current_buffer.MarkResponseHandled()
  601. def ShouldResendFileParseRequest( self ):
  602. return self.CurrentBuffer().ShouldResendParseRequest()
  603. def DebugInfo( self ):
  604. debug_info = ''
  605. if self._client_logfile:
  606. debug_info += f'Client logfile: { self._client_logfile }\n'
  607. extra_data = {}
  608. self._AddExtraConfDataIfNeeded( extra_data )
  609. debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) )
  610. debug_info += f'Server running at: { BaseRequest.server_location }\n'
  611. if self._server_popen:
  612. debug_info += f'Server process ID: { self._server_popen.pid }\n'
  613. if self._server_stdout and self._server_stderr:
  614. debug_info += ( 'Server logfiles:\n'
  615. f' { self._server_stdout }\n'
  616. f' { self._server_stderr }' )
  617. debug_info += ( '\nSemantic highlighting supported: ' +
  618. str( not vimsupport.VimIsNeovim() ) )
  619. debug_info += ( '\nVirtual text supported: ' +
  620. str( not vimsupport.VimIsNeovim() ) )
  621. debug_info += ( '\nPopup windows supported: ' +
  622. str( vimsupport.VimSupportsPopupWindows() ) )
  623. return debug_info
  624. def GetLogfiles( self ):
  625. logfiles_list = [ self._client_logfile,
  626. self._server_stdout,
  627. self._server_stderr ]
  628. extra_data = {}
  629. self._AddExtraConfDataIfNeeded( extra_data )
  630. debug_info = SendDebugInfoRequest( extra_data )
  631. if debug_info:
  632. completer = debug_info[ 'completer' ]
  633. if completer:
  634. for server in completer[ 'servers' ]:
  635. logfiles_list.extend( server[ 'logfiles' ] )
  636. logfiles = {}
  637. for logfile in logfiles_list:
  638. logfiles[ os.path.basename( logfile ) ] = logfile
  639. return logfiles
  640. def _OpenLogfile( self, size, mods, logfile ):
  641. # Open log files in a horizontal window with the same behavior as the
  642. # preview window (same height and winfixheight enabled). Automatically
  643. # watch for changes. Set the cursor position at the end of the file.
  644. if not size:
  645. size = vimsupport.GetIntValue( '&previewheight' )
  646. options = {
  647. 'size': size,
  648. 'fix': True,
  649. 'focus': False,
  650. 'watch': True,
  651. 'position': 'end',
  652. 'mods': mods
  653. }
  654. vimsupport.OpenFilename( logfile, options )
  655. def _CloseLogfile( self, logfile ):
  656. vimsupport.CloseBuffersForFilename( logfile )
  657. def ToggleLogs( self, size, mods, *filenames ):
  658. logfiles = self.GetLogfiles()
  659. if not filenames:
  660. sorted_logfiles = sorted( logfiles )
  661. try:
  662. logfile_index = vimsupport.SelectFromList(
  663. 'Which logfile do you wish to open (or close if already open)?',
  664. sorted_logfiles )
  665. except RuntimeError as e:
  666. vimsupport.PostVimMessage( str( e ) )
  667. return
  668. logfile = logfiles[ sorted_logfiles[ logfile_index ] ]
  669. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  670. self._OpenLogfile( size, mods, logfile )
  671. else:
  672. self._CloseLogfile( logfile )
  673. return
  674. for filename in set( filenames ):
  675. if filename not in logfiles:
  676. continue
  677. logfile = logfiles[ filename ]
  678. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  679. self._OpenLogfile( size, mods, logfile )
  680. continue
  681. self._CloseLogfile( logfile )
  682. def ShowDetailedDiagnostic( self, message_in_popup ):
  683. detailed_diagnostic = BaseRequest().PostDataToHandler(
  684. BuildRequestData(), 'detailed_diagnostic' )
  685. if detailed_diagnostic and 'message' in detailed_diagnostic:
  686. message = detailed_diagnostic[ 'message' ]
  687. if message_in_popup and vimsupport.VimSupportsPopupWindows():
  688. window = vim.current.window
  689. buffer_number = vimsupport.GetCurrentBufferNumber()
  690. diags_on_this_line = self._buffers[ buffer_number ].DiagnosticsForLine(
  691. window.cursor[ 0 ] )
  692. lines = message.split( '\n' )
  693. available_columns = vimsupport.GetIntValue( '&columns' )
  694. col = window.cursor[ 1 ] + 1
  695. if col > available_columns - 2: # -2 accounts for padding.
  696. col = 0
  697. options = {
  698. 'col': col,
  699. 'padding': [ 0, 1, 0, 1 ],
  700. 'maxwidth': available_columns,
  701. 'close': 'click',
  702. 'fixed': 0,
  703. 'highlight': 'YcmErrorPopup',
  704. 'border': [ 1, 1, 1, 1 ],
  705. # Close when moving cursor
  706. 'moved': 'expr',
  707. }
  708. popup_func = 'popup_atcursor'
  709. for diag in diags_on_this_line:
  710. if message == diag[ 'text' ]:
  711. popup_func = 'popup_create'
  712. prop = vimsupport.GetTextPropertyForDiag( buffer_number,
  713. window.cursor[ 0 ],
  714. diag )
  715. options.update( {
  716. 'textpropid': prop[ 'id' ],
  717. 'textprop': prop[ 'type' ],
  718. } )
  719. options.pop( 'col' )
  720. break
  721. vim.eval( f'{ popup_func }( { json.dumps( lines ) }, '
  722. f'{ json.dumps( options ) } )' )
  723. else:
  724. vimsupport.PostVimMessage( message, warning = False )
  725. def ForceCompileAndDiagnostics( self ):
  726. if not self.NativeFiletypeCompletionUsable():
  727. vimsupport.PostVimMessage(
  728. 'Native filetype completion not supported for current file, '
  729. 'cannot force recompilation.', warning = False )
  730. return False
  731. vimsupport.PostVimMessage(
  732. 'Forcing compilation, this will block Vim until done.',
  733. warning = False )
  734. self.OnFileReadyToParse()
  735. self.HandleFileParseRequest( block = True )
  736. vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False )
  737. return True
  738. def ShowDiagnostics( self ):
  739. if not self.ForceCompileAndDiagnostics():
  740. return
  741. if not self._PopulateLocationListWithLatestDiagnostics():
  742. vimsupport.PostVimMessage( 'No warnings or errors detected.',
  743. warning = False )
  744. return
  745. if self._user_options[ 'open_loclist_on_ycm_diags' ]:
  746. vimsupport.OpenLocationList( focus = True )
  747. def FilterAndSortItems( self,
  748. items,
  749. sort_property,
  750. query,
  751. max_items = 0 ):
  752. return BaseRequest().PostDataToHandler( {
  753. 'candidates': items,
  754. 'sort_property': sort_property,
  755. 'max_num_candidates': max_items,
  756. 'query': vimsupport.ToUnicode( query )
  757. }, 'filter_and_sort_candidates' )
  758. def ToggleSignatureHelp( self ):
  759. self._signature_help_state.ToggleVisibility()
  760. def _AddSyntaxDataIfNeeded( self, extra_data ):
  761. if not self._user_options[ 'seed_identifiers_with_syntax' ]:
  762. return
  763. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  764. if filetype in self._filetypes_with_keywords_loaded:
  765. return
  766. if self.IsServerReady():
  767. self._filetypes_with_keywords_loaded.add( filetype )
  768. extra_data[ 'syntax_keywords' ] = list(
  769. syntax_parse.SyntaxKeywordsForCurrentBuffer() )
  770. def _AddTagsFilesIfNeeded( self, extra_data ):
  771. def GetTagFiles():
  772. tag_files = vim.eval( 'tagfiles()' )
  773. return [ ( os.path.join( utils.GetCurrentDirectory(), tag_file )
  774. if not os.path.isabs( tag_file ) else tag_file )
  775. for tag_file in tag_files ]
  776. if not self._user_options[ 'collect_identifiers_from_tags_files' ]:
  777. return
  778. extra_data[ 'tag_files' ] = GetTagFiles()
  779. def _AddExtraConfDataIfNeeded( self, extra_data ):
  780. def BuildExtraConfData( extra_conf_vim_data ):
  781. extra_conf_data = {}
  782. for expr in extra_conf_vim_data:
  783. try:
  784. extra_conf_data[ expr ] = vimsupport.VimExpressionToPythonType( expr )
  785. except vim.error:
  786. message = (
  787. f"Error evaluating '{ expr }' in the 'g:ycm_extra_conf_vim_data' "
  788. "option." )
  789. vimsupport.PostVimMessage( message, truncate = True )
  790. self._logger.exception( message )
  791. return extra_conf_data
  792. extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ]
  793. if extra_conf_vim_data:
  794. extra_data[ 'extra_conf_data' ] = BuildExtraConfData(
  795. extra_conf_vim_data )
  796. def _AddUltiSnipsDataIfNeeded( self, extra_data ):
  797. # See :h UltiSnips#SnippetsInCurrentScope.
  798. try:
  799. vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' )
  800. except vim.error:
  801. return
  802. snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' )
  803. extra_data[ 'ultisnips_snippets' ] = [
  804. { 'trigger': trigger,
  805. 'description': snippet[ 'description' ] }
  806. for trigger, snippet in snippets.items()
  807. ]