youcompleteme.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2011, 2012 Google Inc.
  4. #
  5. # This file is part of YouCompleteMe.
  6. #
  7. # YouCompleteMe is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # YouCompleteMe is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  19. import os
  20. import vim
  21. import tempfile
  22. import json
  23. import signal
  24. import base64
  25. from subprocess import PIPE
  26. from ycm import vimsupport
  27. from ycmd import utils
  28. from ycmd.request_wrap import RequestWrap
  29. from ycm.diagnostic_interface import DiagnosticInterface
  30. from ycm.omni_completer import OmniCompleter
  31. from ycm import syntax_parse
  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. from ycm.client.completion_request import CompletionRequest
  37. from ycm.client.omni_completion_request import OmniCompletionRequest
  38. from ycm.client.event_notification import ( SendEventNotificationAsync,
  39. EventNotification )
  40. from ycmd.responses import ServerError
  41. try:
  42. from UltiSnips import UltiSnips_Manager
  43. USE_ULTISNIPS_DATA = True
  44. except ImportError:
  45. USE_ULTISNIPS_DATA = False
  46. def PatchNoProxy():
  47. current_value = os.environ.get('no_proxy', '')
  48. additions = '127.0.0.1,localhost'
  49. os.environ['no_proxy'] = ( additions if not current_value
  50. else current_value + ',' + additions )
  51. # We need this so that Requests doesn't end up using the local HTTP proxy when
  52. # talking to ycmd. Users should actually be setting this themselves when
  53. # configuring a proxy server on their machine, but most don't know they need to
  54. # or how to do it, so we do it for them.
  55. # Relevant issues:
  56. # https://github.com/Valloric/YouCompleteMe/issues/641
  57. # https://github.com/kennethreitz/requests/issues/879
  58. PatchNoProxy()
  59. # Force the Python interpreter embedded in Vim (in which we are running) to
  60. # ignore the SIGINT signal. This helps reduce the fallout of a user pressing
  61. # Ctrl-C in Vim.
  62. signal.signal( signal.SIGINT, signal.SIG_IGN )
  63. HMAC_SECRET_LENGTH = 16
  64. NUM_YCMD_STDERR_LINES_ON_CRASH = 30
  65. SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED = (
  66. 'The ycmd server SHUT DOWN (restart with :YcmRestartServer). '
  67. 'Logfile was deleted; set g:ycm_server_keep_logfiles to see errors '
  68. 'in the future.' )
  69. SERVER_CRASH_MESSAGE_STDERR_FILE = (
  70. 'The ycmd server SHUT DOWN (restart with :YcmRestartServer). ' +
  71. 'Stderr (last {0} lines):\n\n'.format( NUM_YCMD_STDERR_LINES_ON_CRASH ) )
  72. SERVER_CRASH_MESSAGE_SAME_STDERR = (
  73. 'The ycmd server SHUT DOWN (restart with :YcmRestartServer). '
  74. ' check console output for logs!' )
  75. SERVER_IDLE_SUICIDE_SECONDS = 10800 # 3 hours
  76. class YouCompleteMe( object ):
  77. def __init__( self, user_options ):
  78. self._user_options = user_options
  79. self._user_notified_about_crash = False
  80. self._diag_interface = DiagnosticInterface( user_options )
  81. self._omnicomp = OmniCompleter( user_options )
  82. self._latest_file_parse_request = None
  83. self._latest_completion_request = None
  84. self._server_stdout = None
  85. self._server_stderr = None
  86. self._server_popen = None
  87. self._filetypes_with_keywords_loaded = set()
  88. self._ycmd_keepalive = YcmdKeepalive()
  89. self._SetupServer()
  90. self._ycmd_keepalive.Start()
  91. def _SetupServer( self ):
  92. self._available_completers = {}
  93. server_port = utils.GetUnusedLocalhostPort()
  94. # The temp options file is deleted by ycmd during startup
  95. with tempfile.NamedTemporaryFile( delete = False ) as options_file:
  96. hmac_secret = os.urandom( HMAC_SECRET_LENGTH )
  97. options_dict = dict( self._user_options )
  98. options_dict[ 'hmac_secret' ] = base64.b64encode( hmac_secret )
  99. json.dump( options_dict, options_file )
  100. options_file.flush()
  101. args = [ utils.PathToPythonInterpreter(),
  102. _PathToServerScript(),
  103. '--port={0}'.format( server_port ),
  104. '--options_file={0}'.format( options_file.name ),
  105. '--log={0}'.format( self._user_options[ 'server_log_level' ] ),
  106. '--idle_suicide_seconds={0}'.format(
  107. SERVER_IDLE_SUICIDE_SECONDS )]
  108. if not self._user_options[ 'server_use_vim_stdout' ]:
  109. filename_format = os.path.join( utils.PathToTempDir(),
  110. 'server_{port}_{std}.log' )
  111. self._server_stdout = filename_format.format( port = server_port,
  112. std = 'stdout' )
  113. self._server_stderr = filename_format.format( port = server_port,
  114. std = 'stderr' )
  115. args.append('--stdout={0}'.format( self._server_stdout ))
  116. args.append('--stderr={0}'.format( self._server_stderr ))
  117. if self._user_options[ 'server_keep_logfiles' ]:
  118. args.append('--keep_logfiles')
  119. self._server_popen = utils.SafePopen( args, stdin_windows = PIPE,
  120. stdout = PIPE, stderr = PIPE)
  121. BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port )
  122. BaseRequest.hmac_secret = hmac_secret
  123. self._NotifyUserIfServerCrashed()
  124. def IsServerAlive( self ):
  125. returncode = self._server_popen.poll()
  126. # When the process hasn't finished yet, poll() returns None.
  127. return returncode is None
  128. def _NotifyUserIfServerCrashed( self ):
  129. if self._user_notified_about_crash or self.IsServerAlive():
  130. return
  131. self._user_notified_about_crash = True
  132. if self._server_stderr:
  133. try:
  134. with open( self._server_stderr, 'r' ) as server_stderr_file:
  135. error_output = ''.join( server_stderr_file.readlines()[
  136. : - NUM_YCMD_STDERR_LINES_ON_CRASH ] )
  137. vimsupport.PostMultiLineNotice( SERVER_CRASH_MESSAGE_STDERR_FILE +
  138. error_output )
  139. except IOError:
  140. vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_STDERR_FILE_DELETED )
  141. else:
  142. vimsupport.PostVimMessage( SERVER_CRASH_MESSAGE_SAME_STDERR )
  143. def ServerPid( self ):
  144. if not self._server_popen:
  145. return -1
  146. return self._server_popen.pid
  147. def _ServerCleanup( self ):
  148. if self.IsServerAlive():
  149. self._server_popen.terminate()
  150. def RestartServer( self ):
  151. vimsupport.PostVimMessage( 'Restarting ycmd server...' )
  152. self._user_notified_about_crash = False
  153. self._ServerCleanup()
  154. self._SetupServer()
  155. def CreateCompletionRequest( self, force_semantic = False ):
  156. request_data = BuildRequestData()
  157. if ( not self.NativeFiletypeCompletionAvailable() and
  158. self.CurrentFiletypeCompletionEnabled() ):
  159. wrapped_request_data = RequestWrap( request_data )
  160. if self._omnicomp.ShouldUseNow( wrapped_request_data ):
  161. self._latest_completion_request = OmniCompletionRequest(
  162. self._omnicomp, wrapped_request_data )
  163. return self._latest_completion_request
  164. self._AddExtraConfDataIfNeeded( request_data )
  165. if force_semantic:
  166. request_data[ 'force_semantic' ] = True
  167. self._latest_completion_request = CompletionRequest( request_data )
  168. return self._latest_completion_request
  169. def SendCommandRequest( self, arguments, completer ):
  170. if self.IsServerAlive():
  171. return SendCommandRequest( arguments, completer )
  172. def GetDefinedSubcommands( self ):
  173. if self.IsServerAlive():
  174. try:
  175. return BaseRequest.PostDataToHandler( BuildRequestData(),
  176. 'defined_subcommands' )
  177. except ServerError:
  178. return []
  179. else:
  180. return []
  181. def GetCurrentCompletionRequest( self ):
  182. return self._latest_completion_request
  183. def GetOmniCompleter( self ):
  184. return self._omnicomp
  185. def FiletypeCompleterExistsForFiletype( self, filetype ):
  186. try:
  187. return self._available_completers[ filetype ]
  188. except KeyError:
  189. pass
  190. exists_completer = ( self.IsServerAlive() and
  191. bool( SendCompleterAvailableRequest( filetype ) ) )
  192. self._available_completers[ filetype ] = exists_completer
  193. return exists_completer
  194. def NativeFiletypeCompletionAvailable( self ):
  195. return any( [ self.FiletypeCompleterExistsForFiletype( x ) for x in
  196. vimsupport.CurrentFiletypes() ] )
  197. def NativeFiletypeCompletionUsable( self ):
  198. return ( self.CurrentFiletypeCompletionEnabled() and
  199. self.NativeFiletypeCompletionAvailable() )
  200. def OnFileReadyToParse( self ):
  201. self._omnicomp.OnFileReadyToParse( None )
  202. if not self.IsServerAlive():
  203. self._NotifyUserIfServerCrashed()
  204. extra_data = {}
  205. self._AddTagsFilesIfNeeded( extra_data )
  206. self._AddSyntaxDataIfNeeded( extra_data )
  207. self._AddExtraConfDataIfNeeded( extra_data )
  208. self._latest_file_parse_request = EventNotification( 'FileReadyToParse',
  209. extra_data )
  210. self._latest_file_parse_request.Start()
  211. def OnBufferUnload( self, deleted_buffer_file ):
  212. if not self.IsServerAlive():
  213. return
  214. SendEventNotificationAsync( 'BufferUnload',
  215. { 'unloaded_buffer': deleted_buffer_file } )
  216. def OnBufferVisit( self ):
  217. if not self.IsServerAlive():
  218. return
  219. extra_data = {}
  220. _AddUltiSnipsDataIfNeeded( extra_data )
  221. SendEventNotificationAsync( 'BufferVisit', extra_data )
  222. def OnInsertLeave( self ):
  223. if not self.IsServerAlive():
  224. return
  225. SendEventNotificationAsync( 'InsertLeave' )
  226. def OnCursorMoved( self ):
  227. self._diag_interface.OnCursorMoved()
  228. def OnVimLeave( self ):
  229. self._ServerCleanup()
  230. def OnCurrentIdentifierFinished( self ):
  231. if not self.IsServerAlive():
  232. return
  233. SendEventNotificationAsync( 'CurrentIdentifierFinished' )
  234. def OnCompleteDone( self ):
  235. if not self.HasPostCompletionAction():
  236. return
  237. latest_completion_request = self.GetCurrentCompletionRequest()
  238. if not latest_completion_request.Done():
  239. return
  240. completions = latest_completion_request.RawResponse()
  241. completions = list( self.FilterMatchingCompletions( completions ) )
  242. if not completions:
  243. return
  244. namespaces = [ self.GetRequiredNamespaceImport( c )
  245. for c in completions ]
  246. namespaces = [ n for n in namespaces if n ]
  247. if not namespaces:
  248. return
  249. if len( namespaces ) > 1:
  250. choices = [ "{0}: {1}".format( i + 1, n )
  251. for i,n in enumerate( namespaces ) ]
  252. choice = vimsupport.PresentDialog(
  253. "Insert which namespace:", choices )
  254. if choice < 0:
  255. return
  256. namespace = namespaces[ choice ]
  257. else:
  258. namespace = namespaces[ 0 ]
  259. vimsupport.InsertNamespace( namespace )
  260. def HasPostCompletionAction( self ):
  261. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  262. return filetype == 'cs'
  263. def FilterMatchingCompletions( self, completions ):
  264. text = vimsupport.TextBeforeCursor() # No support for multiple line completions
  265. for completion in completions:
  266. word = completion[ "insertion_text" ]
  267. for i in [ None, -1 ]:
  268. if text[ -1 * len( word ) + ( i or 0 ) : i ] == word:
  269. yield completion
  270. break
  271. def GetRequiredNamespaceImport( self, completion ):
  272. if ( "extra_data" not in completion
  273. or "required_namespace_import" not in completion[ "extra_data" ] ):
  274. return ""
  275. return completion[ "extra_data" ][ "required_namespace_import" ]
  276. def DiagnosticsForCurrentFileReady( self ):
  277. return bool( self._latest_file_parse_request and
  278. self._latest_file_parse_request.Done() )
  279. def GetDiagnosticsFromStoredRequest( self, qflist_format = False ):
  280. if self.DiagnosticsForCurrentFileReady():
  281. diagnostics = self._latest_file_parse_request.Response()
  282. # We set the diagnostics request to None because we want to prevent
  283. # repeated refreshing of the buffer with the same diags. Setting this to
  284. # None makes DiagnosticsForCurrentFileReady return False until the next
  285. # request is created.
  286. self._latest_file_parse_request = None
  287. if qflist_format:
  288. return vimsupport.ConvertDiagnosticsToQfList( diagnostics )
  289. else:
  290. return diagnostics
  291. return []
  292. def UpdateDiagnosticInterface( self ):
  293. if ( self.DiagnosticsForCurrentFileReady() and
  294. self.NativeFiletypeCompletionUsable() ):
  295. self._diag_interface.UpdateWithNewDiagnostics(
  296. self.GetDiagnosticsFromStoredRequest() )
  297. def ShowDetailedDiagnostic( self ):
  298. if not self.IsServerAlive():
  299. return
  300. try:
  301. debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
  302. 'detailed_diagnostic' )
  303. if 'message' in debug_info:
  304. vimsupport.EchoText( debug_info[ 'message' ] )
  305. except ServerError as e:
  306. vimsupport.PostVimMessage( str( e ) )
  307. def DebugInfo( self ):
  308. if self.IsServerAlive():
  309. debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
  310. 'debug_info' )
  311. else:
  312. debug_info = 'Server crashed, no debug info from server'
  313. debug_info += '\nServer running at: {0}'.format(
  314. BaseRequest.server_location )
  315. debug_info += '\nServer process ID: {0}'.format( self._server_popen.pid )
  316. if self._server_stderr or self._server_stdout:
  317. debug_info += '\nServer logfiles:\n {0}\n {1}'.format(
  318. self._server_stdout,
  319. self._server_stderr )
  320. return debug_info
  321. def CurrentFiletypeCompletionEnabled( self ):
  322. filetypes = vimsupport.CurrentFiletypes()
  323. filetype_to_disable = self._user_options[
  324. 'filetype_specific_completion_to_disable' ]
  325. if '*' in filetype_to_disable:
  326. return False
  327. else:
  328. return not any([ x in filetype_to_disable for x in filetypes ])
  329. def _AddSyntaxDataIfNeeded( self, extra_data ):
  330. if not self._user_options[ 'seed_identifiers_with_syntax' ]:
  331. return
  332. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  333. if filetype in self._filetypes_with_keywords_loaded:
  334. return
  335. self._filetypes_with_keywords_loaded.add( filetype )
  336. extra_data[ 'syntax_keywords' ] = list(
  337. syntax_parse.SyntaxKeywordsForCurrentBuffer() )
  338. def _AddTagsFilesIfNeeded( self, extra_data ):
  339. def GetTagFiles():
  340. tag_files = vim.eval( 'tagfiles()' )
  341. # getcwd() throws an exception when the CWD has been deleted.
  342. try:
  343. current_working_directory = os.getcwd()
  344. except OSError:
  345. return []
  346. return [ os.path.join( current_working_directory, x ) for x in tag_files ]
  347. if not self._user_options[ 'collect_identifiers_from_tags_files' ]:
  348. return
  349. extra_data[ 'tag_files' ] = GetTagFiles()
  350. def _AddExtraConfDataIfNeeded( self, extra_data ):
  351. def BuildExtraConfData( extra_conf_vim_data ):
  352. return dict( ( expr, vimsupport.VimExpressionToPythonType( expr ) )
  353. for expr in extra_conf_vim_data )
  354. extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ]
  355. if extra_conf_vim_data:
  356. extra_data[ 'extra_conf_data' ] = BuildExtraConfData(
  357. extra_conf_vim_data )
  358. def _PathToServerScript():
  359. dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
  360. return os.path.join( dir_of_current_script, '../../third_party/ycmd/ycmd' )
  361. def _AddUltiSnipsDataIfNeeded( extra_data ):
  362. if not USE_ULTISNIPS_DATA:
  363. return
  364. try:
  365. rawsnips = UltiSnips_Manager._snips( '', 1 )
  366. except:
  367. return
  368. # UltiSnips_Manager._snips() returns a class instance where:
  369. # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
  370. # class.description - description of the snippet
  371. extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger,
  372. 'description': x.description
  373. } for x in rawsnips ]