ycm_core now imported after extra conf preload

This commit is contained in:
Strahinja Val Markovic 2013-10-14 15:29:00 -07:00
parent bc607724f0
commit b903867cdd
5 changed files with 289 additions and 223 deletions

View File

@ -0,0 +1,210 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import atexit
import logging
import json
import bottle
import httplib
from bottle import request, response
import server_state
from ycm import user_options_store
from ycm.server.responses import BuildExceptionResponse
from ycm import extra_conf_store
# num bytes for the request body buffer; request.json only works if the request
# size is less than this
bottle.Request.MEMFILE_MAX = 300 * 1024
# TODO: rename these to _lower_case
SERVER_STATE = None
LOGGER = logging.getLogger( __name__ )
app = bottle.Bottle()
@app.post( '/event_notification' )
def EventNotification():
LOGGER.info( 'Received event notification' )
request_data = request.json
event_name = request_data[ 'event_name' ]
LOGGER.debug( 'Event name: %s', event_name )
event_handler = 'On' + event_name
getattr( SERVER_STATE.GetGeneralCompleter(), event_handler )( request_data )
filetypes = request_data[ 'filetypes' ]
response_data = None
if SERVER_STATE.FiletypeCompletionUsable( filetypes ):
response_data = getattr( SERVER_STATE.GetFiletypeCompleter( filetypes ),
event_handler )( request_data )
if response_data:
return _JsonResponse( response_data )
@app.post( '/run_completer_command' )
def RunCompleterCommand():
LOGGER.info( 'Received command request' )
request_data = request.json
completer = _GetCompleterForRequestData( request_data )
return _JsonResponse( completer.OnUserCommand(
request_data[ 'command_arguments' ],
request_data ) )
@app.post( '/completions' )
def GetCompletions():
LOGGER.info( 'Received completion request' )
request_data = request.json
do_filetype_completion = SERVER_STATE.ShouldUseFiletypeCompleter(
request_data )
LOGGER.debug( 'Using filetype completion: %s', do_filetype_completion )
filetypes = request_data[ 'filetypes' ]
completer = ( SERVER_STATE.GetFiletypeCompleter( filetypes ) if
do_filetype_completion else
SERVER_STATE.GetGeneralCompleter() )
return _JsonResponse( completer.ComputeCandidates( request_data ) )
@app.get( '/user_options' )
def GetUserOptions():
LOGGER.info( 'Received user options GET request' )
return _JsonResponse( dict( SERVER_STATE.user_options ) )
@app.get( '/healthy' )
def GetHealthy():
LOGGER.info( 'Received health request' )
return _JsonResponse( True )
@app.post( '/user_options' )
def SetUserOptions():
LOGGER.info( 'Received user options POST request' )
UpdateUserOptions( request.json )
@app.post( '/semantic_completion_available' )
def FiletypeCompletionAvailable():
LOGGER.info( 'Received filetype completion available request' )
return _JsonResponse( SERVER_STATE.FiletypeCompletionAvailable(
request.json[ 'filetypes' ] ) )
@app.post( '/defined_subcommands' )
def DefinedSubcommands():
LOGGER.info( 'Received defined subcommands request' )
completer = _GetCompleterForRequestData( request.json )
return _JsonResponse( completer.DefinedSubcommands() )
@app.post( '/detailed_diagnostic' )
def GetDetailedDiagnostic():
LOGGER.info( 'Received detailed diagnostic request' )
request_data = request.json
completer = _GetCompleterForRequestData( request_data )
return _JsonResponse( completer.GetDetailedDiagnostic( request_data ) )
@app.post( '/load_extra_conf_file' )
def LoadExtraConfFile():
LOGGER.info( 'Received extra conf load request' )
request_data = request.json
extra_conf_store.Load( request_data[ 'filepath' ], force = True )
@app.post( '/debug_info' )
def DebugInfo():
# This can't be at the top level because of possible extra conf preload
import ycm_core
LOGGER.info( 'Received debug info request' )
output = []
has_clang_support = ycm_core.HasClangSupport()
output.append( 'Server has Clang support compiled in: {0}'.format(
has_clang_support ) )
if has_clang_support:
output.append( 'Clang version: ' + ycm_core.ClangVersion() )
request_data = request.json
try:
output.append(
_GetCompleterForRequestData( request_data ).DebugInfo( request_data) )
except:
pass
return _JsonResponse( '\n'.join( output ) )
# The type of the param is Bottle.HTTPError
@app.error( httplib.INTERNAL_SERVER_ERROR )
def ErrorHandler( httperror ):
return _JsonResponse( BuildExceptionResponse( httperror.exception,
httperror.traceback ) )
def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' )
return json.dumps( data, default = _UniversalSerialize )
def _UniversalSerialize( obj ):
serialized = obj.__dict__.copy()
serialized[ 'TYPE' ] = type( obj ).__name__
return serialized
def _GetCompleterForRequestData( request_data ):
completer_target = request_data.get( 'completer_target', None )
if completer_target == 'identifier':
return SERVER_STATE.GetGeneralCompleter().GetIdentifierCompleter()
elif completer_target == 'filetype_default' or not completer_target:
return SERVER_STATE.GetFiletypeCompleter( request_data[ 'filetypes' ] )
else:
return SERVER_STATE.GetFiletypeCompleter( [ completer_target ] )
@atexit.register
def _ServerShutdown():
if SERVER_STATE:
SERVER_STATE.Shutdown()
extra_conf_store.Shutdown()
def UpdateUserOptions( options ):
global SERVER_STATE
if not options:
return
user_options_store.SetAll( options )
SERVER_STATE = server_state.ServerState( options )
def SetServerStateToDefaults():
global SERVER_STATE, LOGGER
LOGGER = logging.getLogger( __name__ )
user_options_store.LoadDefaults()
SERVER_STATE = server_state.ServerState( user_options_store.GetAll() )
extra_conf_store.Reset()

View File

@ -19,7 +19,6 @@
import imp
import os
from ycm import extra_conf_store
from ycm.utils import ForceSemanticCompletion
from ycm.completers.general.general_completer_store import GeneralCompleterStore
from ycm.completers.completer_utils import PathToFiletypeCompleterPluginLoader
@ -30,7 +29,6 @@ class ServerState( object ):
self._user_options = user_options
self._filetype_completers = {}
self._gencomp = GeneralCompleterStore( self._user_options )
extra_conf_store.CallGlobalExtraConfYcmCorePreloadIfExists()
@property
@ -44,7 +42,6 @@ class ServerState( object ):
completer.Shutdown()
self._gencomp.Shutdown()
extra_conf_store.Shutdown()
def _GetFiletypeCompleterForFiletype( self, filetype ):

View File

@ -0,0 +1,34 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import sys
import os
def SetUpPythonPath():
# We want to have the YouCompleteMe/python directory on the Python PATH
# because all the code already assumes that it's there. This is a relic from
# before the client/server architecture.
# TODO: Fix things so that this is not needed anymore when we split ycmd into
# a separate repository.
sys.path.insert( 0, os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'../..' ) )
from ycm import utils
utils.AddThirdPartyFoldersToSysPath()

View File

@ -20,8 +20,10 @@
import os
import httplib
import time
from ..server_utils import SetUpPythonPath
SetUpPythonPath()
from webtest import TestApp
from .. import ycmd
from .. import handlers
from ..responses import BuildCompletionData, UnknownExtraConf
from nose.tools import ok_, eq_, with_setup
from hamcrest import ( assert_that, has_items, has_entry, contains,
@ -73,7 +75,7 @@ def CompletionEntryMatcher( insertion_text ):
def Setup():
ycmd.SetServerStateToDefaults()
handlers.SetServerStateToDefaults()
def PathToTestDataDir():
@ -87,7 +89,7 @@ def PathToTestFile( test_basename ):
@with_setup( Setup )
def GetCompletions_IdentifierCompleter_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
event_data = BuildRequest( contents = 'foo foogoo ba',
event_name = 'FileReadyToParse' )
@ -104,7 +106,7 @@ def GetCompletions_IdentifierCompleter_Works_test():
@with_setup( Setup )
def GetCompletions_CsCompleter_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
filepath = PathToTestFile( 'testy/Program.cs' )
contents = open( filepath ).read()
event_data = BuildRequest( filepath = filepath,
@ -138,7 +140,7 @@ def GetCompletions_CsCompleter_Works_test():
@with_setup( Setup )
def GetCompletions_ClangCompleter_WorksWithExplicitFlags_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
contents = """
struct Foo {
int x;
@ -170,7 +172,7 @@ int main()
@with_setup( Setup )
def GetCompletions_ClangCompleter_UnknownExtraConfException_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
filepath = PathToTestFile( 'basic.cpp' )
completion_data = BuildRequest( filepath = filepath,
filetype = 'cpp',
@ -189,7 +191,7 @@ def GetCompletions_ClangCompleter_UnknownExtraConfException_test():
@with_setup( Setup )
def GetCompletions_ClangCompleter_WorksWhenExtraConfExplicitlyAllowed_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
app.post_json( '/load_extra_conf_file',
{ 'filepath': PathToTestFile( '.ycm_extra_conf.py' ) } )
@ -209,7 +211,7 @@ def GetCompletions_ClangCompleter_WorksWhenExtraConfExplicitlyAllowed_test():
@with_setup( Setup )
def GetCompletions_ClangCompleter_ForceSemantic_OnlyFileteredCompletions_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
contents = """
int main()
{
@ -241,7 +243,7 @@ int main()
@with_setup( Setup )
def GetCompletions_ForceSemantic_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
completion_data = BuildRequest( filetype = 'python',
force_semantic = True )
@ -254,7 +256,7 @@ def GetCompletions_ForceSemantic_Works_test():
@with_setup( Setup )
def GetCompletions_IdentifierCompleter_SyntaxKeywordsAdded_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
event_data = BuildRequest( event_name = 'FileReadyToParse',
syntax_keywords = ['foo', 'bar', 'zoo'] )
@ -271,7 +273,7 @@ def GetCompletions_IdentifierCompleter_SyntaxKeywordsAdded_test():
@with_setup( Setup )
def GetCompletions_UltiSnipsCompleter_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
event_data = BuildRequest(
event_name = 'BufferVisit',
ultisnips_snippets = [
@ -292,7 +294,7 @@ def GetCompletions_UltiSnipsCompleter_Works_test():
@with_setup( Setup )
def RunCompleterCommand_GoTo_Jedi_ZeroBasedLineAndColumn_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
contents = """
def foo():
pass
@ -318,7 +320,7 @@ foo()
@with_setup( Setup )
def RunCompleterCommand_GoTo_Clang_ZeroBasedLineAndColumn_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
contents = """
struct Foo {
int x;
@ -352,7 +354,7 @@ int main()
@with_setup( Setup )
def DefinedSubcommands_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
subcommands_data = BuildRequest( completer_target = 'python' )
eq_( [ 'GoToDefinition',
@ -363,7 +365,7 @@ def DefinedSubcommands_Works_test():
@with_setup( Setup )
def DefinedSubcommands_WorksWhenNoExplicitCompleterTargetSpecified_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
subcommands_data = BuildRequest( filetype = 'python' )
eq_( [ 'GoToDefinition',
@ -374,7 +376,7 @@ def DefinedSubcommands_WorksWhenNoExplicitCompleterTargetSpecified_test():
@with_setup( Setup )
def Diagnostics_ClangCompleter_ZeroBasedLineAndColumn_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
contents = """
struct Foo {
int x // semicolon missing here!
@ -399,7 +401,7 @@ struct Foo {
@with_setup( Setup )
def GetDetailedDiagnostic_ClangCompleter_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
contents = """
struct Foo {
int x // semicolon missing here!
@ -427,7 +429,7 @@ struct Foo {
@with_setup( Setup )
def FiletypeCompletionAvailable_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
request_data = {
'filetypes': ['python']
}
@ -438,7 +440,7 @@ def FiletypeCompletionAvailable_Works_test():
@with_setup( Setup )
def UserOptions_Works_test():
app = TestApp( ycmd.app )
app = TestApp( handlers.app )
options = app.get( '/user_options' ).json
ok_( len( options ) )

View File

@ -17,214 +17,24 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from server_utils import SetUpPythonPath
SetUpPythonPath()
import sys
import os
import atexit
# We want to have the YouCompleteMe/python directory on the Python PATH because
# all the code already assumes that it's there. This is a relic from before the
# client/server architecture.
# TODO: Fix things so that this is not needed anymore when we split ycmd into a
# separate repository.
sys.path.insert( 0, os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'../..' ) )
from ycm import utils
utils.AddThirdPartyFoldersToSysPath()
import logging
import json
import bottle
import argparse
import httplib
import waitress
from bottle import request, response
import server_state
from ycm import user_options_store
from ycm.server.responses import BuildExceptionResponse
from ycm import extra_conf_store
# num bytes for the request body buffer; request.json only works if the request
# size is less than this
bottle.Request.MEMFILE_MAX = 300 * 1024
# TODO: rename these to _lower_case
SERVER_STATE = None
LOGGER = None
app = bottle.Bottle()
@app.post( '/event_notification' )
def EventNotification():
LOGGER.info( 'Received event notification' )
request_data = request.json
event_name = request_data[ 'event_name' ]
LOGGER.debug( 'Event name: %s', event_name )
event_handler = 'On' + event_name
getattr( SERVER_STATE.GetGeneralCompleter(), event_handler )( request_data )
filetypes = request_data[ 'filetypes' ]
response_data = None
if SERVER_STATE.FiletypeCompletionUsable( filetypes ):
response_data = getattr( SERVER_STATE.GetFiletypeCompleter( filetypes ),
event_handler )( request_data )
if response_data:
return _JsonResponse( response_data )
@app.post( '/run_completer_command' )
def RunCompleterCommand():
LOGGER.info( 'Received command request' )
request_data = request.json
completer = _GetCompleterForRequestData( request_data )
return _JsonResponse( completer.OnUserCommand(
request_data[ 'command_arguments' ],
request_data ) )
@app.post( '/completions' )
def GetCompletions():
LOGGER.info( 'Received completion request' )
request_data = request.json
do_filetype_completion = SERVER_STATE.ShouldUseFiletypeCompleter(
request_data )
LOGGER.debug( 'Using filetype completion: %s', do_filetype_completion )
filetypes = request_data[ 'filetypes' ]
completer = ( SERVER_STATE.GetFiletypeCompleter( filetypes ) if
do_filetype_completion else
SERVER_STATE.GetGeneralCompleter() )
return _JsonResponse( completer.ComputeCandidates( request_data ) )
@app.get( '/user_options' )
def GetUserOptions():
LOGGER.info( 'Received user options GET request' )
return _JsonResponse( dict( SERVER_STATE.user_options ) )
@app.get( '/healthy' )
def GetHealthy():
LOGGER.info( 'Received health request' )
return _JsonResponse( True )
@app.post( '/user_options' )
def SetUserOptions():
LOGGER.info( 'Received user options POST request' )
_SetUserOptions( request.json )
@app.post( '/semantic_completion_available' )
def FiletypeCompletionAvailable():
LOGGER.info( 'Received filetype completion available request' )
return _JsonResponse( SERVER_STATE.FiletypeCompletionAvailable(
request.json[ 'filetypes' ] ) )
@app.post( '/defined_subcommands' )
def DefinedSubcommands():
LOGGER.info( 'Received defined subcommands request' )
completer = _GetCompleterForRequestData( request.json )
return _JsonResponse( completer.DefinedSubcommands() )
@app.post( '/detailed_diagnostic' )
def GetDetailedDiagnostic():
LOGGER.info( 'Received detailed diagnostic request' )
request_data = request.json
completer = _GetCompleterForRequestData( request_data )
return _JsonResponse( completer.GetDetailedDiagnostic( request_data ) )
@app.post( '/load_extra_conf_file' )
def LoadExtraConfFile():
LOGGER.info( 'Received extra conf load request' )
request_data = request.json
extra_conf_store.Load( request_data[ 'filepath' ], force = True )
@app.post( '/debug_info' )
def DebugInfo():
# This can't be at the top level because of possible extra conf preload
import ycm_core
LOGGER.info( 'Received debug info request' )
output = []
has_clang_support = ycm_core.HasClangSupport()
output.append( 'Server has Clang support compiled in: {0}'.format(
has_clang_support ) )
if has_clang_support:
output.append( 'Clang version: ' + ycm_core.ClangVersion() )
request_data = request.json
try:
output.append(
_GetCompleterForRequestData( request_data ).DebugInfo( request_data) )
except:
pass
return _JsonResponse( '\n'.join( output ) )
# The type of the param is Bottle.HTTPError
@app.error( httplib.INTERNAL_SERVER_ERROR )
def ErrorHandler( httperror ):
return _JsonResponse( BuildExceptionResponse( httperror.exception,
httperror.traceback ) )
def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' )
return json.dumps( data, default = _UniversalSerialize )
def _UniversalSerialize( obj ):
serialized = obj.__dict__.copy()
serialized[ 'TYPE' ] = type( obj ).__name__
return serialized
def _GetCompleterForRequestData( request_data ):
completer_target = request_data.get( 'completer_target', None )
if completer_target == 'identifier':
return SERVER_STATE.GetGeneralCompleter().GetIdentifierCompleter()
elif completer_target == 'filetype_default' or not completer_target:
return SERVER_STATE.GetFiletypeCompleter( request_data[ 'filetypes' ] )
else:
return SERVER_STATE.GetFiletypeCompleter( [ completer_target ] )
@atexit.register
def _ServerShutdown():
if SERVER_STATE:
SERVER_STATE.Shutdown()
def _SetUserOptions( options ):
global SERVER_STATE
user_options_store.SetAll( options )
SERVER_STATE = server_state.ServerState( options )
def SetServerStateToDefaults():
global SERVER_STATE, LOGGER
LOGGER = logging.getLogger( __name__ )
user_options_store.LoadDefaults()
SERVER_STATE = server_state.ServerState( user_options_store.GetAll() )
extra_conf_store.Reset()
def YcmCoreSanityCheck():
if 'ycm_core' in sys.modules:
raise RuntimeError( 'ycm_core already imported, ycmd has a bug!' )
def Main():
global LOGGER
parser = argparse.ArgumentParser()
parser.add_argument( '--host', type = str, default = 'localhost',
help = 'server hostname')
@ -237,18 +47,31 @@ def Main():
help = 'file with user options, in JSON format' )
args = parser.parse_args()
if args.options_file:
_SetUserOptions( json.load( open( args.options_file, 'r' ) ) )
numeric_level = getattr( logging, args.log.upper(), None )
if not isinstance( numeric_level, int ):
raise ValueError( 'Invalid log level: %s' % args.log )
# Has to be called before any call to logging.getLogger()
logging.basicConfig( format = '%(asctime)s - %(levelname)s - %(message)s',
level = numeric_level )
LOGGER = logging.getLogger( __name__ )
waitress.serve( app, host = args.host, port = args.port, threads = 10 )
options = None
if args.options_file:
options = json.load( open( args.options_file, 'r' ) )
user_options_store.SetAll( options )
# This ensures that ycm_core is not loaded before extra conf preload
# was run.
YcmCoreSanityCheck()
extra_conf_store.CallGlobalExtraConfYcmCorePreloadIfExists()
# This can't be a top-level import because it transitively imports ycm_core
# which we want to be imported ONLY after extra conf preload has executed.
import handlers
handlers.UpdateUserOptions( options )
waitress.serve( handlers.app,
host = args.host,
port = args.port,
threads = 10 )
if __name__ == "__main__":