youcompleteme.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  1. # Copyright (C) 2011-2012 Google Inc.
  2. # 2016-2017 YouCompleteMe contributors
  3. #
  4. # This file is part of YouCompleteMe.
  5. #
  6. # YouCompleteMe is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # YouCompleteMe is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  18. from __future__ import unicode_literals
  19. from __future__ import print_function
  20. from __future__ import division
  21. from __future__ import absolute_import
  22. # Not installing aliases from python-future; it's unreliable and slow.
  23. from builtins import * # noqa
  24. from future.utils import iteritems
  25. import base64
  26. import json
  27. import logging
  28. import os
  29. import signal
  30. import vim
  31. from subprocess import PIPE
  32. from tempfile import NamedTemporaryFile
  33. from ycm import base, paths, vimsupport
  34. from ycm.buffer import BufferDict
  35. from ycmd import utils
  36. from ycmd import server_utils
  37. from ycmd.request_wrap import RequestWrap
  38. from ycm.omni_completer import OmniCompleter
  39. from ycm import syntax_parse
  40. from ycm.client.ycmd_keepalive import YcmdKeepalive
  41. from ycm.client.base_request import ( BaseRequest, BuildRequestData,
  42. HandleServerException )
  43. from ycm.client.completer_available_request import SendCompleterAvailableRequest
  44. from ycm.client.command_request import SendCommandRequest
  45. from ycm.client.completion_request import ( CompletionRequest,
  46. ConvertCompletionDataToVimData )
  47. from ycm.client.debug_info_request import ( SendDebugInfoRequest,
  48. FormatDebugInfoResponse )
  49. from ycm.client.omni_completion_request import OmniCompletionRequest
  50. from ycm.client.event_notification import SendEventNotificationAsync
  51. from ycm.client.shutdown_request import SendShutdownRequest
  52. def PatchNoProxy():
  53. current_value = os.environ.get('no_proxy', '')
  54. additions = '127.0.0.1,localhost'
  55. os.environ['no_proxy'] = ( additions if not current_value
  56. else current_value + ',' + additions )
  57. # We need this so that Requests doesn't end up using the local HTTP proxy when
  58. # talking to ycmd. Users should actually be setting this themselves when
  59. # configuring a proxy server on their machine, but most don't know they need to
  60. # or how to do it, so we do it for them.
  61. # Relevant issues:
  62. # https://github.com/Valloric/YouCompleteMe/issues/641
  63. # https://github.com/kennethreitz/requests/issues/879
  64. PatchNoProxy()
  65. # Force the Python interpreter embedded in Vim (in which we are running) to
  66. # ignore the SIGINT signal. This helps reduce the fallout of a user pressing
  67. # Ctrl-C in Vim.
  68. signal.signal( signal.SIGINT, signal.SIG_IGN )
  69. HMAC_SECRET_LENGTH = 16
  70. SERVER_SHUTDOWN_MESSAGE = (
  71. "The ycmd server SHUT DOWN (restart with ':YcmRestartServer')." )
  72. EXIT_CODE_UNEXPECTED_MESSAGE = (
  73. "Unexpected exit code {code}. "
  74. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  75. CORE_UNEXPECTED_MESSAGE = (
  76. "Unexpected error while loading the YCM core library. "
  77. "Type ':YcmToggleLogs {logfile}' to check the logs." )
  78. CORE_MISSING_MESSAGE = (
  79. 'YCM core library not detected; you need to compile YCM before using it. '
  80. 'Follow the instructions in the documentation.' )
  81. CORE_PYTHON2_MESSAGE = (
  82. "YCM core library compiled for Python 2 but loaded in Python 3. "
  83. "Set the 'g:ycm_server_python_interpreter' option to a Python 2 "
  84. "interpreter path." )
  85. CORE_PYTHON3_MESSAGE = (
  86. "YCM core library compiled for Python 3 but loaded in Python 2. "
  87. "Set the 'g:ycm_server_python_interpreter' option to a Python 3 "
  88. "interpreter path." )
  89. CORE_OUTDATED_MESSAGE = (
  90. 'YCM core library too old; PLEASE RECOMPILE by running the install.py '
  91. 'script. See the documentation for more details.' )
  92. SERVER_IDLE_SUICIDE_SECONDS = 1800 # 30 minutes
  93. DIAGNOSTIC_UI_FILETYPES = set( [ 'cpp', 'cs', 'c', 'objc', 'objcpp',
  94. 'typescript' ] )
  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. class YouCompleteMe( object ):
  101. def __init__( self, user_options ):
  102. self._available_completers = {}
  103. self._user_options = user_options
  104. self._user_notified_about_crash = False
  105. self._omnicomp = OmniCompleter( user_options )
  106. self._buffers = BufferDict( user_options )
  107. self._latest_completion_request = None
  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. self._complete_done_hooks = {
  120. 'cs': lambda self: self._OnCompleteDone_Csharp()
  121. }
  122. def _SetupServer( self ):
  123. self._available_completers = {}
  124. self._user_notified_about_crash = False
  125. self._filetypes_with_keywords_loaded = set()
  126. self._server_is_ready_with_cache = False
  127. hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
  128. options_dict = dict( self._user_options )
  129. options_dict[ 'hmac_secret' ] = utils.ToUnicode(
  130. base64.b64encode( hmac_secret ) )
  131. options_dict[ 'server_keep_logfiles' ] = self._user_options[
  132. 'keep_logfiles' ]
  133. # The temp options file is deleted by ycmd during startup.
  134. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file:
  135. json.dump( options_dict, options_file )
  136. server_port = utils.GetUnusedLocalhostPort()
  137. BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
  138. BaseRequest.hmac_secret = hmac_secret
  139. try:
  140. python_interpreter = paths.PathToPythonInterpreter()
  141. except RuntimeError as error:
  142. error_message = (
  143. "Unable to start the ycmd server. {0}. "
  144. "Correct the error then restart the server "
  145. "with ':YcmRestartServer'.".format( str( error ).rstrip( '.' ) ) )
  146. self._logger.exception( error_message )
  147. vimsupport.PostVimMessage( error_message )
  148. return
  149. args = [ python_interpreter,
  150. paths.PathToServerScript(),
  151. '--port={0}'.format( server_port ),
  152. '--options_file={0}'.format( options_file.name ),
  153. '--log={0}'.format( self._user_options[ 'log_level' ] ),
  154. '--idle_suicide_seconds={0}'.format(
  155. SERVER_IDLE_SUICIDE_SECONDS ) ]
  156. self._server_stdout = utils.CreateLogfile(
  157. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) )
  158. self._server_stderr = utils.CreateLogfile(
  159. SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) )
  160. args.append( '--stdout={0}'.format( self._server_stdout ) )
  161. args.append( '--stderr={0}'.format( self._server_stderr ) )
  162. if self._user_options[ 'keep_logfiles' ]:
  163. args.append( '--keep_logfiles' )
  164. self._server_popen = utils.SafePopen( args, stdin_windows = PIPE,
  165. stdout = PIPE, stderr = PIPE )
  166. def _SetupLogging( self ):
  167. def FreeFileFromOtherProcesses( file_object ):
  168. if utils.OnWindows():
  169. from ctypes import windll
  170. import msvcrt
  171. file_handle = msvcrt.get_osfhandle( file_object.fileno() )
  172. windll.kernel32.SetHandleInformation( file_handle,
  173. HANDLE_FLAG_INHERIT,
  174. 0 )
  175. self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT )
  176. log_level = self._user_options[ 'log_level' ]
  177. numeric_level = getattr( logging, log_level.upper(), None )
  178. if not isinstance( numeric_level, int ):
  179. raise ValueError( 'Invalid log level: {0}'.format( log_level ) )
  180. self._logger.setLevel( numeric_level )
  181. handler = logging.FileHandler( self._client_logfile )
  182. # On Windows and Python prior to 3.4, file handles are inherited by child
  183. # processes started with at least one replaced standard stream, which is the
  184. # case when we start the ycmd server (we are redirecting all standard
  185. # outputs into a pipe). These files cannot be removed while the child
  186. # processes are still up. This is not desirable for a logfile because we
  187. # want to remove it at Vim exit without having to wait for the ycmd server
  188. # to be completely shut down. We need to make the logfile handle
  189. # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more
  190. # details.
  191. FreeFileFromOtherProcesses( handler.stream )
  192. formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' )
  193. handler.setFormatter( formatter )
  194. self._logger.addHandler( handler )
  195. def IsServerAlive( self ):
  196. # When the process hasn't finished yet, poll() returns None.
  197. return bool( self._server_popen ) and self._server_popen.poll() is None
  198. def CheckIfServerIsReady( self ):
  199. if not self._server_is_ready_with_cache:
  200. with HandleServerException( display = False ):
  201. self._server_is_ready_with_cache = BaseRequest.GetDataFromHandler(
  202. 'ready' )
  203. return self._server_is_ready_with_cache
  204. def IsServerReady( self ):
  205. return self._server_is_ready_with_cache
  206. def NotifyUserIfServerCrashed( self ):
  207. if ( not self._server_popen or self._user_notified_about_crash or
  208. self.IsServerAlive() ):
  209. return
  210. self._user_notified_about_crash = True
  211. return_code = self._server_popen.poll()
  212. logfile = os.path.basename( self._server_stderr )
  213. if return_code == server_utils.CORE_UNEXPECTED_STATUS:
  214. error_message = CORE_UNEXPECTED_MESSAGE.format(
  215. logfile = logfile )
  216. elif return_code == server_utils.CORE_MISSING_STATUS:
  217. error_message = CORE_MISSING_MESSAGE
  218. elif return_code == server_utils.CORE_PYTHON2_STATUS:
  219. error_message = CORE_PYTHON2_MESSAGE
  220. elif return_code == server_utils.CORE_PYTHON3_STATUS:
  221. error_message = CORE_PYTHON3_MESSAGE
  222. elif return_code == server_utils.CORE_OUTDATED_STATUS:
  223. error_message = CORE_OUTDATED_MESSAGE
  224. else:
  225. error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format(
  226. code = return_code,
  227. logfile = logfile )
  228. error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message
  229. self._logger.error( error_message )
  230. vimsupport.PostVimMessage( error_message )
  231. def ServerPid( self ):
  232. if not self._server_popen:
  233. return -1
  234. return self._server_popen.pid
  235. def _ShutdownServer( self ):
  236. SendShutdownRequest()
  237. def RestartServer( self ):
  238. vimsupport.PostVimMessage( 'Restarting ycmd server...' )
  239. self._ShutdownServer()
  240. self._SetupServer()
  241. def SendCompletionRequest( self, force_semantic = False ):
  242. request_data = BuildRequestData()
  243. request_data[ 'force_semantic' ] = force_semantic
  244. if ( not self.NativeFiletypeCompletionAvailable() and
  245. self.CurrentFiletypeCompletionEnabled() ):
  246. wrapped_request_data = RequestWrap( request_data )
  247. if self._omnicomp.ShouldUseNow( wrapped_request_data ):
  248. self._latest_completion_request = OmniCompletionRequest(
  249. self._omnicomp, wrapped_request_data )
  250. self._latest_completion_request.Start()
  251. return
  252. request_data[ 'working_dir' ] = utils.GetCurrentDirectory()
  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 SendCommandRequest( self, arguments, completer ):
  265. extra_data = {}
  266. self._AddExtraConfDataIfNeeded( extra_data )
  267. return SendCommandRequest( arguments, completer, extra_data )
  268. def GetDefinedSubcommands( self ):
  269. with HandleServerException():
  270. return BaseRequest.PostDataToHandler( BuildRequestData(),
  271. 'defined_subcommands' )
  272. return []
  273. def GetCurrentCompletionRequest( self ):
  274. return self._latest_completion_request
  275. def GetOmniCompleter( self ):
  276. return self._omnicomp
  277. def FiletypeCompleterExistsForFiletype( self, filetype ):
  278. try:
  279. return self._available_completers[ filetype ]
  280. except KeyError:
  281. pass
  282. exists_completer = SendCompleterAvailableRequest( filetype )
  283. if exists_completer is None:
  284. return False
  285. self._available_completers[ filetype ] = exists_completer
  286. return exists_completer
  287. def NativeFiletypeCompletionAvailable( self ):
  288. return any( [ self.FiletypeCompleterExistsForFiletype( x ) for x in
  289. vimsupport.CurrentFiletypes() ] )
  290. def NativeFiletypeCompletionUsable( self ):
  291. return ( self.CurrentFiletypeCompletionEnabled() and
  292. self.NativeFiletypeCompletionAvailable() )
  293. def NeedsReparse( self ):
  294. return self.CurrentBuffer().NeedsReparse()
  295. def OnFileReadyToParse( self ):
  296. if not self.IsServerAlive():
  297. self.NotifyUserIfServerCrashed()
  298. return
  299. if not self.IsServerReady():
  300. return
  301. extra_data = {}
  302. self._AddTagsFilesIfNeeded( extra_data )
  303. self._AddSyntaxDataIfNeeded( extra_data )
  304. self._AddExtraConfDataIfNeeded( extra_data )
  305. self.CurrentBuffer().SendParseRequest( extra_data )
  306. def OnBufferUnload( self, deleted_buffer_file ):
  307. SendEventNotificationAsync(
  308. 'BufferUnload',
  309. filepath = utils.ToUnicode( deleted_buffer_file ) )
  310. def OnBufferVisit( self ):
  311. extra_data = {}
  312. self._AddUltiSnipsDataIfNeeded( extra_data )
  313. SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data )
  314. def CurrentBuffer( self ):
  315. return self._buffers[ vimsupport.GetCurrentBufferNumber() ]
  316. def OnInsertLeave( self ):
  317. SendEventNotificationAsync( 'InsertLeave' )
  318. def OnCursorMoved( self ):
  319. self.CurrentBuffer().OnCursorMoved()
  320. def _CleanLogfile( self ):
  321. logging.shutdown()
  322. if not self._user_options[ 'keep_logfiles' ]:
  323. if self._client_logfile:
  324. utils.RemoveIfExists( self._client_logfile )
  325. def OnVimLeave( self ):
  326. self._ShutdownServer()
  327. self._CleanLogfile()
  328. def OnCurrentIdentifierFinished( self ):
  329. SendEventNotificationAsync( 'CurrentIdentifierFinished' )
  330. def OnCompleteDone( self ):
  331. complete_done_actions = self.GetCompleteDoneHooks()
  332. for action in complete_done_actions:
  333. action(self)
  334. def GetCompleteDoneHooks( self ):
  335. filetypes = vimsupport.CurrentFiletypes()
  336. for key, value in iteritems( self._complete_done_hooks ):
  337. if key in filetypes:
  338. yield value
  339. def GetCompletionsUserMayHaveCompleted( self ):
  340. latest_completion_request = self.GetCurrentCompletionRequest()
  341. if not latest_completion_request or not latest_completion_request.Done():
  342. return []
  343. completions = latest_completion_request.RawResponse()
  344. result = self._FilterToMatchingCompletions( completions, True )
  345. result = list( result )
  346. if result:
  347. return result
  348. if self._HasCompletionsThatCouldBeCompletedWithMoreText( completions ):
  349. # Since the way that YCM works leads to CompleteDone called on every
  350. # character, return blank if the completion might not be done. This won't
  351. # match if the completion is ended with typing a non-keyword character.
  352. return []
  353. result = self._FilterToMatchingCompletions( completions, False )
  354. return list( result )
  355. def _FilterToMatchingCompletions( self, completions, full_match_only ):
  356. """Filter to completions matching the item Vim said was completed"""
  357. completed = vimsupport.GetVariableValue( 'v:completed_item' )
  358. for completion in completions:
  359. item = ConvertCompletionDataToVimData( completion )
  360. match_keys = ( [ "word", "abbr", "menu", "info" ] if full_match_only
  361. else [ 'word' ] )
  362. def matcher( key ):
  363. return ( utils.ToUnicode( completed.get( key, "" ) ) ==
  364. utils.ToUnicode( item.get( key, "" ) ) )
  365. if all( [ matcher( i ) for i in match_keys ] ):
  366. yield completion
  367. def _HasCompletionsThatCouldBeCompletedWithMoreText( self, completions ):
  368. completed_item = vimsupport.GetVariableValue( 'v:completed_item' )
  369. if not completed_item:
  370. return False
  371. completed_word = utils.ToUnicode( completed_item[ 'word' ] )
  372. if not completed_word:
  373. return False
  374. # Sometimes CompleteDone is called after the next character is inserted.
  375. # If so, use inserted character to filter possible completions further.
  376. text = vimsupport.TextBeforeCursor()
  377. reject_exact_match = True
  378. if text and text[ -1 ] != completed_word[ -1 ]:
  379. reject_exact_match = False
  380. completed_word += text[ -1 ]
  381. for completion in completions:
  382. word = utils.ToUnicode(
  383. ConvertCompletionDataToVimData( completion )[ 'word' ] )
  384. if reject_exact_match and word == completed_word:
  385. continue
  386. if word.startswith( completed_word ):
  387. return True
  388. return False
  389. def _OnCompleteDone_Csharp( self ):
  390. completions = self.GetCompletionsUserMayHaveCompleted()
  391. namespaces = [ self._GetRequiredNamespaceImport( c )
  392. for c in completions ]
  393. namespaces = [ n for n in namespaces if n ]
  394. if not namespaces:
  395. return
  396. if len( namespaces ) > 1:
  397. choices = [ "{0} {1}".format( i + 1, n )
  398. for i, n in enumerate( namespaces ) ]
  399. choice = vimsupport.PresentDialog( "Insert which namespace:", choices )
  400. if choice < 0:
  401. return
  402. namespace = namespaces[ choice ]
  403. else:
  404. namespace = namespaces[ 0 ]
  405. vimsupport.InsertNamespace( namespace )
  406. def _GetRequiredNamespaceImport( self, completion ):
  407. if ( "extra_data" not in completion
  408. or "required_namespace_import" not in completion[ "extra_data" ] ):
  409. return None
  410. return completion[ "extra_data" ][ "required_namespace_import" ]
  411. def GetErrorCount( self ):
  412. return self.CurrentBuffer().GetErrorCount()
  413. def GetWarningCount( self ):
  414. return self.CurrentBuffer().GetWarningCount()
  415. def DiagnosticUiSupportedForCurrentFiletype( self ):
  416. return any( [ x in DIAGNOSTIC_UI_FILETYPES
  417. for x in vimsupport.CurrentFiletypes() ] )
  418. def ShouldDisplayDiagnostics( self ):
  419. return bool( self._user_options[ 'show_diagnostics_ui' ] and
  420. self.DiagnosticUiSupportedForCurrentFiletype() )
  421. def _PopulateLocationListWithLatestDiagnostics( self ):
  422. return self.CurrentBuffer().PopulateLocationList()
  423. def FileParseRequestReady( self ):
  424. # Return True if server is not ready yet, to stop repeating check timer.
  425. return ( not self.IsServerReady() or
  426. self.CurrentBuffer().FileParseRequestReady() )
  427. def HandleFileParseRequest( self, block = False ):
  428. if not self.IsServerReady():
  429. return
  430. current_buffer = self.CurrentBuffer()
  431. # Order is important here:
  432. # FileParseRequestReady has a low cost, while
  433. # NativeFiletypeCompletionUsable is a blocking server request
  434. if ( not current_buffer.IsResponseHandled() and
  435. current_buffer.FileParseRequestReady( block ) and
  436. self.NativeFiletypeCompletionUsable() ):
  437. if self.ShouldDisplayDiagnostics():
  438. current_buffer.UpdateDiagnostics()
  439. else:
  440. # YCM client has a hard-coded list of filetypes which are known
  441. # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype()
  442. #
  443. # For filetypes which don't support diagnostics, we just want to check
  444. # the _latest_file_parse_request for any exception or UnknownExtraConf
  445. # response, to allow the server to raise configuration warnings, etc.
  446. # to the user. We ignore any other supplied data.
  447. current_buffer.GetResponse()
  448. # We set the file parse request as handled because we want to prevent
  449. # repeated issuing of the same warnings/errors/prompts. Setting this
  450. # makes IsRequestHandled return True until the next request is created.
  451. #
  452. # Note: it is the server's responsibility to determine the frequency of
  453. # error/warning/prompts when receiving a FileReadyToParse event, but
  454. # it is our responsibility to ensure that we only apply the
  455. # warning/error/prompt received once (for each event).
  456. current_buffer.MarkResponseHandled()
  457. def DebugInfo( self ):
  458. debug_info = ''
  459. if self._client_logfile:
  460. debug_info += 'Client logfile: {0}\n'.format( self._client_logfile )
  461. extra_data = {}
  462. self._AddExtraConfDataIfNeeded( extra_data )
  463. debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) )
  464. debug_info += 'Server running at: {0}\n'.format(
  465. BaseRequest.server_location )
  466. if self._server_popen:
  467. debug_info += 'Server process ID: {0}\n'.format( self._server_popen.pid )
  468. if self._server_stdout and self._server_stderr:
  469. debug_info += ( 'Server logfiles:\n'
  470. ' {0}\n'
  471. ' {1}'.format( self._server_stdout,
  472. self._server_stderr ) )
  473. return debug_info
  474. def GetLogfiles( self ):
  475. logfiles_list = [ self._client_logfile,
  476. self._server_stdout,
  477. self._server_stderr ]
  478. debug_info = SendDebugInfoRequest()
  479. if debug_info:
  480. completer = debug_info[ 'completer' ]
  481. if completer:
  482. for server in completer[ 'servers' ]:
  483. logfiles_list.extend( server[ 'logfiles' ] )
  484. logfiles = {}
  485. for logfile in logfiles_list:
  486. logfiles[ os.path.basename( logfile ) ] = logfile
  487. return logfiles
  488. def _OpenLogfile( self, logfile ):
  489. # Open log files in a horizontal window with the same behavior as the
  490. # preview window (same height and winfixheight enabled). Automatically
  491. # watch for changes. Set the cursor position at the end of the file.
  492. options = {
  493. 'size': vimsupport.GetIntValue( '&previewheight' ),
  494. 'fix': True,
  495. 'focus': False,
  496. 'watch': True,
  497. 'position': 'end'
  498. }
  499. vimsupport.OpenFilename( logfile, options )
  500. def _CloseLogfile( self, logfile ):
  501. vimsupport.CloseBuffersForFilename( logfile )
  502. def ToggleLogs( self, *filenames ):
  503. logfiles = self.GetLogfiles()
  504. if not filenames:
  505. sorted_logfiles = sorted( list( logfiles ) )
  506. try:
  507. logfile_index = vimsupport.SelectFromList(
  508. 'Which logfile do you wish to open (or close if already open)?',
  509. sorted_logfiles )
  510. except RuntimeError as e:
  511. vimsupport.PostVimMessage( str( e ) )
  512. return
  513. logfile = logfiles[ sorted_logfiles[ logfile_index ] ]
  514. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  515. self._OpenLogfile( logfile )
  516. else:
  517. self._CloseLogfile( logfile )
  518. return
  519. for filename in set( filenames ):
  520. if filename not in logfiles:
  521. continue
  522. logfile = logfiles[ filename ]
  523. if not vimsupport.BufferIsVisibleForFilename( logfile ):
  524. self._OpenLogfile( logfile )
  525. continue
  526. self._CloseLogfile( logfile )
  527. def CurrentFiletypeCompletionEnabled( self ):
  528. filetypes = vimsupport.CurrentFiletypes()
  529. filetype_to_disable = self._user_options[
  530. 'filetype_specific_completion_to_disable' ]
  531. if '*' in filetype_to_disable:
  532. return False
  533. else:
  534. return not any([ x in filetype_to_disable for x in filetypes ])
  535. def ShowDetailedDiagnostic( self ):
  536. with HandleServerException():
  537. detailed_diagnostic = BaseRequest.PostDataToHandler(
  538. BuildRequestData(), 'detailed_diagnostic' )
  539. if 'message' in detailed_diagnostic:
  540. vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ],
  541. warning = False )
  542. def ForceCompileAndDiagnostics( self ):
  543. if not self.NativeFiletypeCompletionUsable():
  544. vimsupport.PostVimMessage(
  545. 'Native filetype completion not supported for current file, '
  546. 'cannot force recompilation.', warning = False )
  547. return False
  548. vimsupport.PostVimMessage(
  549. 'Forcing compilation, this will block Vim until done.',
  550. warning = False )
  551. self.OnFileReadyToParse()
  552. self.HandleFileParseRequest( block = True )
  553. vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False )
  554. return True
  555. def ShowDiagnostics( self ):
  556. if not self.ForceCompileAndDiagnostics():
  557. return
  558. if not self._PopulateLocationListWithLatestDiagnostics():
  559. vimsupport.PostVimMessage( 'No warnings or errors detected.',
  560. warning = False )
  561. return
  562. if self._user_options[ 'open_loclist_on_ycm_diags' ]:
  563. vimsupport.OpenLocationList( focus = True )
  564. def _AddSyntaxDataIfNeeded( self, extra_data ):
  565. if not self._user_options[ 'seed_identifiers_with_syntax' ]:
  566. return
  567. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  568. if filetype in self._filetypes_with_keywords_loaded:
  569. return
  570. if self.IsServerReady():
  571. self._filetypes_with_keywords_loaded.add( filetype )
  572. extra_data[ 'syntax_keywords' ] = list(
  573. syntax_parse.SyntaxKeywordsForCurrentBuffer() )
  574. def _AddTagsFilesIfNeeded( self, extra_data ):
  575. def GetTagFiles():
  576. tag_files = vim.eval( 'tagfiles()' )
  577. return [ os.path.join( utils.GetCurrentDirectory(), tag_file )
  578. for tag_file in tag_files ]
  579. if not self._user_options[ 'collect_identifiers_from_tags_files' ]:
  580. return
  581. extra_data[ 'tag_files' ] = GetTagFiles()
  582. def _AddExtraConfDataIfNeeded( self, extra_data ):
  583. def BuildExtraConfData( extra_conf_vim_data ):
  584. return dict( ( expr, vimsupport.VimExpressionToPythonType( expr ) )
  585. for expr in extra_conf_vim_data )
  586. extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ]
  587. if extra_conf_vim_data:
  588. extra_data[ 'extra_conf_data' ] = BuildExtraConfData(
  589. extra_conf_vim_data )
  590. def _AddUltiSnipsDataIfNeeded( self, extra_data ):
  591. # See :h UltiSnips#SnippetsInCurrentScope.
  592. try:
  593. vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' )
  594. except vim.error:
  595. return
  596. snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' )
  597. extra_data[ 'ultisnips_snippets' ] = [
  598. { 'trigger': trigger,
  599. 'description': snippet[ 'description' ] }
  600. for trigger, snippet in iteritems( snippets )
  601. ]