ycmd.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
  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 sys
  20. import os
  21. import atexit
  22. # We want to have the YouCompleteMe/python directory on the Python PATH because
  23. # all the code already assumes that it's there. This is a relic from before the
  24. # client/server architecture.
  25. # TODO: Fix things so that this is not needed anymore when we split ycmd into a
  26. # separate repository.
  27. sys.path.insert( 0, os.path.join(
  28. os.path.dirname( os.path.abspath( __file__ ) ),
  29. '../..' ) )
  30. import logging
  31. import time
  32. import json
  33. import bottle
  34. from bottle import run, request, response
  35. import server_state
  36. from ycm import user_options_store
  37. from ycm.server.responses import BuildExceptionResponse
  38. import argparse
  39. # num bytes for the request body buffer; request.json only works if the request
  40. # size is less than this
  41. bottle.Request.MEMFILE_MAX = 300 * 1024
  42. SERVER_STATE = None
  43. LOGGER = None
  44. app = bottle.Bottle()
  45. @app.post( '/event_notification' )
  46. def EventNotification():
  47. LOGGER.info( 'Received event notification')
  48. request_data = request.json
  49. event_name = request_data[ 'event_name' ]
  50. LOGGER.debug( 'Event name: %s', event_name )
  51. event_handler = 'On' + event_name
  52. getattr( SERVER_STATE.GetGeneralCompleter(), event_handler )( request_data )
  53. filetypes = request_data[ 'filetypes' ]
  54. if SERVER_STATE.FiletypeCompletionUsable( filetypes ):
  55. getattr( SERVER_STATE.GetFiletypeCompleter( filetypes ),
  56. event_handler )( request_data )
  57. @app.post( '/run_completer_command' )
  58. def RunCompleterCommand():
  59. LOGGER.info( 'Received command request')
  60. request_data = request.json
  61. completer_target = request_data[ 'completer_target' ]
  62. if completer_target == 'identifier':
  63. completer = SERVER_STATE.GetGeneralCompleter().GetIdentifierCompleter()
  64. else:
  65. completer = SERVER_STATE.GetFiletypeCompleter( request_data[ 'filetypes' ] )
  66. return _JsonResponse( completer.OnUserCommand(
  67. request_data[ 'command_arguments' ],
  68. request_data ) )
  69. @app.post( '/get_completions' )
  70. def GetCompletions():
  71. LOGGER.info( 'Received completion request')
  72. request_data = request.json
  73. do_filetype_completion = SERVER_STATE.ShouldUseFiletypeCompleter(
  74. request_data )
  75. LOGGER.debug( 'Using filetype completion: %s', do_filetype_completion )
  76. filetypes = request_data[ 'filetypes' ]
  77. completer = ( SERVER_STATE.GetFiletypeCompleter( filetypes ) if
  78. do_filetype_completion else
  79. SERVER_STATE.GetGeneralCompleter() )
  80. # This is necessary so that general_completer_store fills up
  81. # _current_query_completers.
  82. # TODO: Fix this.
  83. completer.ShouldUseNow( request_data )
  84. # TODO: This should not be async anymore, server is multi-threaded
  85. completer.CandidatesForQueryAsync( request_data )
  86. while not completer.AsyncCandidateRequestReady():
  87. time.sleep( 0.03 )
  88. return _JsonResponse( completer.CandidatesFromStoredRequest() )
  89. @app.get( '/user_options' )
  90. def GetUserOptions():
  91. LOGGER.info( 'Received user options GET request')
  92. return _JsonResponse( dict( SERVER_STATE.user_options ) )
  93. @app.post( '/user_options' )
  94. def SetUserOptions():
  95. LOGGER.info( 'Received user options POST request')
  96. _SetUserOptions( request.json )
  97. @app.post( '/filetype_completion_available')
  98. def FiletypeCompletionAvailable():
  99. LOGGER.info( 'Received filetype completion available request')
  100. return _JsonResponse( SERVER_STATE.FiletypeCompletionAvailable(
  101. request.json[ 'filetypes' ] ) )
  102. # The type of the param is Bottle.HTTPError
  103. @app.error( 500 )
  104. def ErrorHandler( httperror ):
  105. return _JsonResponse( BuildExceptionResponse( str( httperror.exception ),
  106. httperror.traceback ) )
  107. def _JsonResponse( data ):
  108. response.set_header( 'Content-Type', 'application/json' )
  109. return json.dumps( data )
  110. @atexit.register
  111. def _ServerShutdown():
  112. if SERVER_STATE:
  113. SERVER_STATE.Shutdown()
  114. def _SetUserOptions( options ):
  115. global SERVER_STATE
  116. user_options_store.SetAll( options )
  117. SERVER_STATE = server_state.ServerState( options )
  118. def SetServerStateToDefaults():
  119. global SERVER_STATE, LOGGER
  120. LOGGER = logging.getLogger( __name__ )
  121. user_options_store.LoadDefaults()
  122. SERVER_STATE = server_state.ServerState( user_options_store.GetAll() )
  123. def Main():
  124. global LOGGER
  125. parser = argparse.ArgumentParser()
  126. parser.add_argument( '--host', type = str, default = 'localhost',
  127. help = 'server hostname')
  128. parser.add_argument( '--port', type = int, default = 6666,
  129. help = 'server port')
  130. parser.add_argument( '--log', type = str, default = 'info',
  131. help = 'log level, one of '
  132. '[debug|info|warning|error|critical]' )
  133. parser.add_argument( '--options_file', type = str, default = '',
  134. help = 'file with user options, in JSON format' )
  135. args = parser.parse_args()
  136. if args.options_file:
  137. _SetUserOptions( json.load( open( args.options_file, 'r' ) ) )
  138. numeric_level = getattr( logging, args.log.upper(), None )
  139. if not isinstance( numeric_level, int ):
  140. raise ValueError( 'Invalid log level: %s' % args.log )
  141. logging.basicConfig( format = '%(asctime)s - %(levelname)s - %(message)s',
  142. level = numeric_level )
  143. LOGGER = logging.getLogger( __name__ )
  144. run( app = app, host = args.host, port = args.port, server='cherrypy' )
  145. if __name__ == "__main__":
  146. Main()