A (barely) working version of ycmd + client

Still a lot of work to do.
This commit is contained in:
Strahinja Val Markovic 2013-09-20 17:24:34 -07:00
parent 02b88dccf1
commit 1730660555
23 changed files with 798 additions and 354 deletions

View File

@ -539,11 +539,6 @@ function! youcompleteme#Complete( findstart, base )
endif endif
py request = ycm_state.CreateCompletionRequest() py request = ycm_state.CreateCompletionRequest()
if !pyeval( 'request.ShouldComplete()' )
" for vim, -2 means not found but don't trigger an error message
" see :h complete-functions
return -2
endif
return pyeval( 'request.CompletionStartColumn()' ) return pyeval( 'request.CompletionStartColumn()' )
else else
return s:CompletionsForQuery( a:base ) return s:CompletionsForQuery( a:base )
@ -628,14 +623,15 @@ function! youcompleteme#OpenGoToList()
endfunction endfunction
command! -nargs=* -complete=custom,youcompleteme#SubCommandsComplete " TODO: Make this work again
\ YcmCompleter call s:CompleterCommand(<f-args>) " command! -nargs=* -complete=custom,youcompleteme#SubCommandsComplete
" \ YcmCompleter call s:CompleterCommand(<f-args>)
"
function! youcompleteme#SubCommandsComplete( arglead, cmdline, cursorpos ) "
return join( pyeval( 'ycm_state.GetFiletypeCompleter().DefinedSubcommands()' ), " function! youcompleteme#SubCommandsComplete( arglead, cmdline, cursorpos )
\ "\n") " return join( pyeval( 'ycm_state.GetFiletypeCompleter().DefinedSubcommands()' ),
endfunction " \ "\n")
" endfunction
function! s:ForceCompile() function! s:ForceCompile()

View File

@ -217,6 +217,18 @@ TEST( IdentifierCompleterTest, ShorterAndLowercaseWins ) {
"STDIN_FILENO" ) ); "STDIN_FILENO" ) );
} }
TEST( IdentifierCompleterTest, AddIdentifiersToDatabaseFromBufferWorks ) {
IdentifierCompleter completer;
completer.AddIdentifiersToDatabaseFromBuffer( "foo foogoo ba",
"foo",
"/foo/bar",
false );
EXPECT_THAT( completer.CandidatesForQueryAndType( "oo", "foo" ),
ElementsAre( "foo",
"foogoo" ) );
}
TEST( IdentifierCompleterTest, TagsEndToEndWorks ) { TEST( IdentifierCompleterTest, TagsEndToEndWorks ) {
IdentifierCompleter completer; IdentifierCompleter completer;
std::vector< std::string > tag_files; std::vector< std::string > tag_files;

View File

View File

@ -0,0 +1,77 @@
#!/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 vim
import json
import requests
from ycm import vimsupport
HEADERS = {'content-type': 'application/json'}
class BaseRequest( object ):
def __init__( self ):
pass
def Start( self ):
pass
def Done( self ):
return True
def Response( self ):
return {}
def PostDataToHandler( self, data, handler ):
response = requests.post( _BuildUri( handler ),
data = json.dumps( data ),
headers = HEADERS )
response.raise_for_status()
if response.text:
return response.json()
return None
server_location = 'http://localhost:6666'
def BuildRequestData( start_column = None, query = None ):
line, column = vimsupport.CurrentLineAndColumn()
request_data = {
'filetypes': vimsupport.CurrentFiletypes(),
'line_num': line,
'column_num': column,
'start_column': start_column,
'line_value': vim.current.line,
'filepath': vim.current.buffer.name,
'file_data': vimsupport.GetUnsavedAndCurrentBufferData()
}
if query:
request_data[ 'query' ] = query
return request_data
def _BuildUri( handler ):
return ''.join( [ BaseRequest.server_location, '/', handler ] )

View File

@ -0,0 +1,67 @@
#!/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/>.
from ycm.client.base_request import BaseRequest, BuildRequestData
class CommandRequest( BaseRequest ):
class ServerResponse( object ):
def __init__( self ):
pass
def Valid( self ):
return True
def __init__( self, arguments, completer_target = None ):
super( CommandRequest, self ).__init__()
self._arguments = arguments
self._completer_target = ( completer_target if completer_target
else 'filetype_default' )
# TODO: Handle this case.
# if completer_target == 'omni':
# completer = SERVER_STATE.GetOmniCompleter()
def Start( self ):
request_data = BuildRequestData()
request_data.update( {
'completer_target': self._completer_target,
'command_arguments': self._arguments
} )
self._response = self.PostDataToHandler( request_data,
'run_completer_command' )
def Response( self ):
# TODO: Call vimsupport.JumpToLocation if the user called a GoTo command...
# we may want to have specific subclasses of CommandRequest so that a
# GoToRequest knows it needs to jump after the data comes back.
#
# Also need to run the following on GoTo data:
#
# CAREFUL about line/column number 0-based/1-based confusion!
#
# defs = []
# defs.append( {'filename': definition.module_path.encode( 'utf-8' ),
# 'lnum': definition.line,
# 'col': definition.column + 1,
# 'text': definition.description.encode( 'utf-8' ) } )
# vim.eval( 'setqflist( %s )' % repr( defs ) )
# vim.eval( 'youcompleteme#OpenGoToList()' )
return self.ServerResponse()

View File

@ -0,0 +1,74 @@
#!/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/>.
from ycm import base
from ycm import vimsupport
from ycm.client.base_request import BaseRequest, BuildRequestData
class CompletionRequest( BaseRequest ):
def __init__( self ):
super( CompletionRequest, self ).__init__()
self._completion_start_column = base.CompletionStartColumn()
self._request_data = BuildRequestData( self._completion_start_column )
# def ShouldComplete( self ):
# return ( self._do_filetype_completion or
# self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) )
def CompletionStartColumn( self ):
return self._completion_start_column
def Start( self, query ):
self._request_data[ 'query' ] = query
self._response = self.PostDataToHandler( self._request_data,
'get_completions' )
def Results( self ):
if not self._response:
return []
try:
return [ _ConvertCompletionDataToVimData( x ) for x in self._response ]
except Exception as e:
vimsupport.PostVimMessage( str( e ) )
return []
def _ConvertCompletionDataToVimData( completion_data ):
# see :h complete-items for a description of the dictionary fields
vim_data = {
'word' : completion_data[ 'insertion_text' ],
'dup' : 1,
}
if 'menu_text' in completion_data:
vim_data[ 'abbr' ] = completion_data[ 'menu_text' ]
if 'extra_menu_info' in completion_data:
vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ]
if 'kind' in completion_data:
vim_data[ 'kind' ] = completion_data[ 'kind' ]
if 'detailed_info' in completion_data:
vim_data[ 'info' ] = completion_data[ 'detailed_info' ]
return vim_data

View File

@ -0,0 +1,42 @@
#!/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/>.
from ycm.client.base_request import BaseRequest, BuildRequestData
class EventNotification( BaseRequest ):
def __init__( self, event_name, extra_data = None ):
super( EventNotification, self ).__init__()
self._event_name = event_name
self._extra_data = extra_data
def Start( self ):
request_data = BuildRequestData()
if self._extra_data:
request_data.update( self._extra_data )
request_data[ 'event_name' ] = self._event_name
self.PostDataToHandler( request_data, 'event_notification' )
def SendEventNotificationAsync( event_name, extra_data = None ):
event = EventNotification( event_name, extra_data )
event.Start()

View File

@ -18,12 +18,14 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import os import os
import logging
import ycm_core import ycm_core
from collections import defaultdict from collections import defaultdict
from ycm.completers.general_completer import GeneralCompleter from ycm.completers.general_completer import GeneralCompleter
# from ycm.completers.general import syntax_parse # from ycm.completers.general import syntax_parse
from ycm import utils from ycm import utils
from ycm import server_responses from ycm.utils import ToUtf8IfNeeded
from ycm.server import responses
MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10 MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10
SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX' SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX'
@ -36,6 +38,7 @@ class IdentifierCompleter( GeneralCompleter ):
self.completer.EnableThreading() self.completer.EnableThreading()
self.tags_file_last_mtime = defaultdict( int ) self.tags_file_last_mtime = defaultdict( int )
self.filetypes_with_keywords_loaded = set() self.filetypes_with_keywords_loaded = set()
self._logger = logging.getLogger( __name__ )
def ShouldUseNow( self, request_data ): def ShouldUseNow( self, request_data ):
@ -44,8 +47,8 @@ class IdentifierCompleter( GeneralCompleter ):
def CandidatesForQueryAsync( self, request_data ): def CandidatesForQueryAsync( self, request_data ):
self.completions_future = self.completer.CandidatesForQueryAndTypeAsync( self.completions_future = self.completer.CandidatesForQueryAndTypeAsync(
utils.SanitizeQuery( request_data[ 'query' ] ), ToUtf8IfNeeded( utils.SanitizeQuery( request_data[ 'query' ] ) ),
request_data[ 'filetypes' ][ 0 ] ) ToUtf8IfNeeded( request_data[ 'filetypes' ][ 0 ] ) )
def AddIdentifier( self, identifier, request_data ): def AddIdentifier( self, identifier, request_data ):
@ -56,10 +59,11 @@ class IdentifierCompleter( GeneralCompleter ):
return return
vector = ycm_core.StringVec() vector = ycm_core.StringVec()
vector.append( identifier ) vector.append( ToUtf8IfNeeded( identifier ) )
self._logger.info( 'Adding ONE buffer identifier for file: %s', filepath )
self.completer.AddIdentifiersToDatabase( vector, self.completer.AddIdentifiersToDatabase( vector,
filetype, ToUtf8IfNeeded( filetype ),
filepath ) ToUtf8IfNeeded( filepath ) )
def AddPreviousIdentifier( self, request_data ): def AddPreviousIdentifier( self, request_data ):
@ -88,10 +92,11 @@ class IdentifierCompleter( GeneralCompleter ):
return return
text = request_data[ 'file_data' ][ filepath ][ 'contents' ] text = request_data[ 'file_data' ][ filepath ][ 'contents' ]
self._logger.info( 'Adding buffer identifiers for file: %s', filepath )
self.completer.AddIdentifiersToDatabaseFromBufferAsync( self.completer.AddIdentifiersToDatabaseFromBufferAsync(
text, ToUtf8IfNeeded( text ),
filetype, ToUtf8IfNeeded( filetype ),
filepath, ToUtf8IfNeeded( filepath ),
collect_from_comments_and_strings ) collect_from_comments_and_strings )
@ -110,7 +115,7 @@ class IdentifierCompleter( GeneralCompleter ):
continue continue
self.tags_file_last_mtime[ tag_file ] = current_mtime self.tags_file_last_mtime[ tag_file ] = current_mtime
absolute_paths_to_tag_files.append( tag_file ) absolute_paths_to_tag_files.append( ToUtf8IfNeeded( tag_file ) )
if not absolute_paths_to_tag_files: if not absolute_paths_to_tag_files:
return return
@ -161,7 +166,7 @@ class IdentifierCompleter( GeneralCompleter ):
completions = _RemoveSmallCandidates( completions = _RemoveSmallCandidates(
completions, self.user_options[ 'min_num_identifier_candidate_chars' ] ) completions, self.user_options[ 'min_num_identifier_candidate_chars' ] )
return [ server_responses.BuildCompletionData( x ) for x in completions ] return [ responses.BuildCompletionData( x ) for x in completions ]
def _PreviousIdentifier( min_num_completion_start_chars, request_data ): def _PreviousIdentifier( min_num_completion_start_chars, request_data ):

View File

@ -20,7 +20,7 @@
from collections import defaultdict from collections import defaultdict
import ycm_core import ycm_core
import logging import logging
from ycm import server_responses from ycm.server import responses
from ycm import extra_conf_store from ycm import extra_conf_store
from ycm.utils import ToUtf8IfNeeded from ycm.utils import ToUtf8IfNeeded
from ycm.completers.completer import Completer from ycm.completers.completer import Completer
@ -72,9 +72,10 @@ class ClangCompleter( Completer ):
continue continue
unsaved_file = ycm_core.UnsavedFile() unsaved_file = ycm_core.UnsavedFile()
unsaved_file.contents_ = contents utf8_contents = ToUtf8IfNeeded( contents )
unsaved_file.length_ = len( contents ) unsaved_file.contents_ = utf8_contents
unsaved_file.filename_ = filename unsaved_file.length_ = len( utf8_contents )
unsaved_file.filename_ = ToUtf8IfNeeded( filename )
files.append( unsaved_file ) files.append( unsaved_file )
return files return files
@ -86,17 +87,17 @@ class ClangCompleter( Completer ):
if not filename: if not filename:
return return
if self.completer.UpdatingTranslationUnit( filename ): if self.completer.UpdatingTranslationUnit( ToUtf8IfNeeded( filename ) ):
self.completions_future = None self.completions_future = None
self._logger.info( PARSING_FILE_MESSAGE ) self._logger.info( PARSING_FILE_MESSAGE )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
PARSING_FILE_MESSAGE ) PARSING_FILE_MESSAGE )
flags = self.flags.FlagsForFile( filename ) flags = self.flags.FlagsForFile( filename )
if not flags: if not flags:
self.completions_future = None self.completions_future = None
self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
NO_COMPILE_FLAGS_MESSAGE ) NO_COMPILE_FLAGS_MESSAGE )
# TODO: sanitize query, probably in C++ code # TODO: sanitize query, probably in C++ code
@ -142,11 +143,11 @@ class ClangCompleter( Completer ):
command = arguments[ 0 ] command = arguments[ 0 ]
if command == 'GoToDefinition': if command == 'GoToDefinition':
self._GoToDefinition( request_data ) return self._GoToDefinition( request_data )
elif command == 'GoToDeclaration': elif command == 'GoToDeclaration':
self._GoToDeclaration( request_data ) return self._GoToDeclaration( request_data )
elif command == 'GoToDefinitionElseDeclaration': elif command == 'GoToDefinitionElseDeclaration':
self._GoToDefinitionElseDeclaration( request_data ) return self._GoToDefinitionElseDeclaration( request_data )
elif command == 'ClearCompilationFlagCache': elif command == 'ClearCompilationFlagCache':
self._ClearCompilationFlagCache( request_data ) self._ClearCompilationFlagCache( request_data )
@ -155,20 +156,20 @@ class ClangCompleter( Completer ):
filename = request_data[ 'filepath' ] filename = request_data[ 'filepath' ]
if not filename: if not filename:
self._logger.warning( INVALID_FILE_MESSAGE ) self._logger.warning( INVALID_FILE_MESSAGE )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
INVALID_FILE_MESSAGE ) INVALID_FILE_MESSAGE )
flags = self.flags.FlagsForFile( filename ) flags = self.flags.FlagsForFile( filename )
if not flags: if not flags:
self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
NO_COMPILE_FLAGS_MESSAGE ) NO_COMPILE_FLAGS_MESSAGE )
files = self.GetUnsavedFilesVector() files = self.GetUnsavedFilesVector( request_data )
line = request_data[ 'line_num' ] + 1 line = request_data[ 'line_num' ] + 1
column = request_data[ 'start_column' ] + 1 column = request_data[ 'start_column' ] + 1
return getattr( self.completer, goto_function )( return getattr( self.completer, goto_function )(
filename, ToUtf8IfNeeded( filename ),
line, line,
column, column,
files, files,
@ -180,9 +181,9 @@ class ClangCompleter( Completer ):
if not location or not location.IsValid(): if not location or not location.IsValid():
raise RuntimeError( 'Can\'t jump to definition.' ) raise RuntimeError( 'Can\'t jump to definition.' )
return server_responses.BuildGoToResponse( location.filename_, return responses.BuildGoToResponse( location.filename_,
location.line_number_, location.line_number_,
location.column_number_ ) location.column_number_ )
def _GoToDeclaration( self, request_data ): def _GoToDeclaration( self, request_data ):
@ -190,9 +191,9 @@ class ClangCompleter( Completer ):
if not location or not location.IsValid(): if not location or not location.IsValid():
raise RuntimeError( 'Can\'t jump to declaration.' ) raise RuntimeError( 'Can\'t jump to declaration.' )
return server_responses.BuildGoToResponse( location.filename_, return responses.BuildGoToResponse( location.filename_,
location.line_number_, location.line_number_,
location.column_number_ ) location.column_number_ )
def _GoToDefinitionElseDeclaration( self, request_data ): def _GoToDefinitionElseDeclaration( self, request_data ):
@ -202,9 +203,9 @@ class ClangCompleter( Completer ):
if not location or not location.IsValid(): if not location or not location.IsValid():
raise RuntimeError( 'Can\'t jump to definition or declaration.' ) raise RuntimeError( 'Can\'t jump to definition or declaration.' )
return server_responses.BuildGoToResponse( location.filename_, return responses.BuildGoToResponse( location.filename_,
location.line_number_, location.line_number_,
location.column_number_ ) location.column_number_ )
@ -223,10 +224,10 @@ class ClangCompleter( Completer ):
if not filename: if not filename:
self._logger.warning( INVALID_FILE_MESSAGE ) self._logger.warning( INVALID_FILE_MESSAGE )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
INVALID_FILE_MESSAGE ) INVALID_FILE_MESSAGE )
if self.completer.UpdatingTranslationUnit( filename ): if self.completer.UpdatingTranslationUnit( ToUtf8IfNeeded( filename ) ):
self.extra_parse_desired = True self.extra_parse_desired = True
return return
@ -234,11 +235,11 @@ class ClangCompleter( Completer ):
if not flags: if not flags:
self.parse_future = None self.parse_future = None
self._logger.info( NO_COMPILE_FLAGS_MESSAGE ) self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
NO_COMPILE_FLAGS_MESSAGE ) NO_COMPILE_FLAGS_MESSAGE )
self.parse_future = self.completer.UpdateTranslationUnitAsync( self.parse_future = self.completer.UpdateTranslationUnitAsync(
filename, ToUtf8IfNeeded( filename ),
self.GetUnsavedFilesVector( request_data ), self.GetUnsavedFilesVector( request_data ),
flags ) flags )
@ -246,7 +247,8 @@ class ClangCompleter( Completer ):
def OnBufferUnload( self, request_data ): def OnBufferUnload( self, request_data ):
self.completer.DeleteCachesForFileAsync( request_data[ 'unloaded_buffer' ] ) self.completer.DeleteCachesForFileAsync(
ToUtf8IfNeeded( request_data[ 'unloaded_buffer' ] ) )
def DiagnosticsForCurrentFileReady( self ): def DiagnosticsForCurrentFileReady( self ):
@ -257,16 +259,18 @@ class ClangCompleter( Completer ):
def GettingCompletions( self, request_data ): def GettingCompletions( self, request_data ):
return self.completer.UpdatingTranslationUnit( request_data[ 'filepath' ] ) return self.completer.UpdatingTranslationUnit(
ToUtf8IfNeeded( request_data[ 'filepath' ] ) )
def GetDiagnosticsForCurrentFile( self, request_data ): def GetDiagnosticsForCurrentFile( self, request_data ):
filename = request_data[ 'filepath' ] filename = request_data[ 'filepath' ]
if self.DiagnosticsForCurrentFileReady(): if self.DiagnosticsForCurrentFileReady():
diagnostics = self.completer.DiagnosticsForFile( filename ) diagnostics = self.completer.DiagnosticsForFile(
ToUtf8IfNeeded( filename ) )
self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics ) self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
self.last_prepared_diagnostics = [ self.last_prepared_diagnostics = [
server_responses.BuildDiagnosticData( x ) for x in responses.BuildDiagnosticData( x ) for x in
diagnostics[ : self.max_diagnostics_to_display ] ] diagnostics[ : self.max_diagnostics_to_display ] ]
self.parse_future = None self.parse_future = None
@ -282,12 +286,12 @@ class ClangCompleter( Completer ):
current_file = request_data[ 'filepath' ] current_file = request_data[ 'filepath' ]
if not self.diagnostic_store: if not self.diagnostic_store:
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
NO_DIAGNOSTIC_MESSAGE ) NO_DIAGNOSTIC_MESSAGE )
diagnostics = self.diagnostic_store[ current_file ][ current_line ] diagnostics = self.diagnostic_store[ current_file ][ current_line ]
if not diagnostics: if not diagnostics:
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
NO_DIAGNOSTIC_MESSAGE ) NO_DIAGNOSTIC_MESSAGE )
closest_diagnostic = None closest_diagnostic = None
@ -299,7 +303,7 @@ class ClangCompleter( Completer ):
distance_to_closest_diagnostic = distance distance_to_closest_diagnostic = distance
closest_diagnostic = diagnostic closest_diagnostic = diagnostic
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
closest_diagnostic.long_formatted_text_ ) closest_diagnostic.long_formatted_text_ )
@ -314,7 +318,7 @@ class ClangCompleter( Completer ):
return '' return ''
flags = self.flags.FlagsForFile( filename ) or [] flags = self.flags.FlagsForFile( filename ) or []
source = extra_conf_store.ModuleFileForSourceFile( filename ) source = extra_conf_store.ModuleFileForSourceFile( filename )
return server_responses.BuildDisplayMessageResponse( return responses.BuildDisplayMessageResponse(
'Flags for {0} loaded from {1}:\n{2}'.format( filename, 'Flags for {0} loaded from {1}:\n{2}'.format( filename,
source, source,
list( flags ) ) ) list( flags ) ) )
@ -348,7 +352,7 @@ class ClangCompleter( Completer ):
def ConvertCompletionData( completion_data ): def ConvertCompletionData( completion_data ):
return server_responses.BuildCompletionData( return responses.BuildCompletionData(
insertion_text = completion_data.TextToInsertInBuffer(), insertion_text = completion_data.TextToInsertInBuffer(),
menu_text = completion_data.MainCompletionText(), menu_text = completion_data.MainCompletionText(),
extra_menu_info = completion_data.ExtraMenuInfo(), extra_menu_info = completion_data.ExtraMenuInfo(),

View File

@ -22,13 +22,13 @@ import os
from sys import platform from sys import platform
import glob import glob
from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.threaded_completer import ThreadedCompleter
from ycm import server_responses from ycm.server import responses
from ycm import utils
import urllib2 import urllib2
import urllib import urllib
import urlparse import urlparse
import json import json
import subprocess import subprocess
import tempfile
import logging import logging
@ -44,7 +44,7 @@ class CsharpCompleter( ThreadedCompleter ):
def __init__( self, user_options ): def __init__( self, user_options ):
super( CsharpCompleter, self ).__init__( user_options ) super( CsharpCompleter, self ).__init__( user_options )
self._omnisharp_port = None self._omnisharp_port = None
self._logger = logging.getLogger(__name__) self._logger = logging.getLogger( __name__ )
# if self.user_options[ 'auto_start_csharp_server' ]: # if self.user_options[ 'auto_start_csharp_server' ]:
# self._StartServer() # self._StartServer()
@ -62,7 +62,7 @@ class CsharpCompleter( ThreadedCompleter ):
def ComputeCandidates( self, request_data ): def ComputeCandidates( self, request_data ):
return [ server_responses.BuildCompletionData( return [ responses.BuildCompletionData(
completion[ 'CompletionText' ], completion[ 'CompletionText' ],
completion[ 'DisplayText' ], completion[ 'DisplayText' ],
completion[ 'Description' ] ) completion[ 'Description' ] )
@ -135,8 +135,8 @@ class CsharpCompleter( ThreadedCompleter ):
command = [ omnisharp + ' -p ' + str( self._omnisharp_port ) + ' -s ' + command = [ omnisharp + ' -p ' + str( self._omnisharp_port ) + ' -s ' +
path_to_solutionfile ] path_to_solutionfile ]
filename_format = ( tempfile.gettempdir() + filename_format = os.path.join( utils.PathToTempDir(),
'/omnisharp_{port}_{sln}_{std}.log' ) 'omnisharp_{port}_{sln}_{std}.log' )
self._filename_stdout = filename_format.format( self._filename_stdout = filename_format.format(
port=self._omnisharp_port, sln=solutionfile, std='stdout' ) port=self._omnisharp_port, sln=solutionfile, std='stdout' )
@ -169,9 +169,9 @@ class CsharpCompleter( ThreadedCompleter ):
definition = self._GetResponse( '/gotodefinition', definition = self._GetResponse( '/gotodefinition',
self._DefaultParameters( request_data ) ) self._DefaultParameters( request_data ) )
if definition[ 'FileName' ] != None: if definition[ 'FileName' ] != None:
return server_responses.BuildGoToResponse( definition[ 'FileName' ], return responses.BuildGoToResponse( definition[ 'FileName' ],
definition[ 'Line' ], definition[ 'Line' ],
definition[ 'Column' ] ) definition[ 'Column' ] )
else: else:
raise RuntimeError( 'Can\'t jump to definition' ) raise RuntimeError( 'Can\'t jump to definition' )

View File

@ -22,7 +22,7 @@ import re
from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.threaded_completer import ThreadedCompleter
from ycm.completers.cpp.clang_completer import InCFamilyFile from ycm.completers.cpp.clang_completer import InCFamilyFile
from ycm.completers.cpp.flags import Flags from ycm.completers.cpp.flags import Flags
from ycm import server_responses from ycm.server import responses
class FilenameCompleter( ThreadedCompleter ): class FilenameCompleter( ThreadedCompleter ):
""" """
@ -146,7 +146,7 @@ def _GenerateCandidatesForPaths( absolute_paths ):
is_dir = os.path.isdir( absolute_path ) is_dir = os.path.isdir( absolute_path )
completion_dicts.append( completion_dicts.append(
server_responses.BuildCompletionData( basename, responses.BuildCompletionData( basename,
'[Dir]' if is_dir else '[File]' ) ) '[Dir]' if is_dir else '[File]' ) )
return completion_dicts return completion_dicts

View File

@ -20,7 +20,7 @@
from ycm.completers.general_completer import GeneralCompleter from ycm.completers.general_completer import GeneralCompleter
from UltiSnips import UltiSnips_Manager from UltiSnips import UltiSnips_Manager
from ycm import server_responses from ycm.server import responses
class UltiSnipsCompleter( GeneralCompleter ): class UltiSnipsCompleter( GeneralCompleter ):
@ -64,7 +64,7 @@ def _GetCandidates():
# UltiSnips_Manager._snips() returns a class instance where: # UltiSnips_Manager._snips() returns a class instance where:
# class.trigger - name of snippet trigger word ( e.g. defn or testcase ) # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
# class.description - description of the snippet # class.description - description of the snippet
return [ server_responses.BuildCompletionData( return [ responses.BuildCompletionData(
str( snip.trigger ), str( snip.trigger ),
str( '<snip> ' + snip.description.encode( 'utf-8' ) ) ) str( '<snip> ' + snip.description.encode( 'utf-8' ) ) )
for snip in rawsnips ] for snip in rawsnips ]

View File

@ -20,7 +20,7 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from ycm.completers.threaded_completer import ThreadedCompleter from ycm.completers.threaded_completer import ThreadedCompleter
from ycm import server_responses from ycm.server import responses
import sys import sys
from os.path import join, abspath, dirname from os.path import join, abspath, dirname
@ -65,7 +65,7 @@ class JediCompleter( ThreadedCompleter ):
def ComputeCandidates( self, request_data ): def ComputeCandidates( self, request_data ):
script = self._GetJediScript( request_data ) script = self._GetJediScript( request_data )
return [ server_responses.BuildCompletionData( return [ responses.BuildCompletionData(
str( completion.name ), str( completion.name ),
str( completion.description ), str( completion.description ),
str( completion.doc ) ) str( completion.doc ) )
@ -140,21 +140,21 @@ class JediCompleter( ThreadedCompleter ):
else: else:
raise RuntimeError( 'Builtin modules cannot be displayed.' ) raise RuntimeError( 'Builtin modules cannot be displayed.' )
else: else:
return server_responses.BuildGoToResponse( definition.module_path, return responses.BuildGoToResponse( definition.module_path,
definition.line -1, definition.line -1,
definition.column ) definition.column )
else: else:
# multiple definitions # multiple definitions
defs = [] defs = []
for definition in definition_list: for definition in definition_list:
if definition.in_builtin_module(): if definition.in_builtin_module():
defs.append( server_responses.BuildDescriptionOnlyGoToResponse( defs.append( responses.BuildDescriptionOnlyGoToResponse(
'Builting ' + definition.description ) ) 'Builting ' + definition.description ) )
else: else:
defs.append( defs.append(
server_responses.BuildGoToResponse( definition.module_path, responses.BuildGoToResponse( definition.module_path,
definition.line -1, definition.line -1,
definition.column, definition.column,
definition.description ) ) definition.description ) )
return defs return defs

View File

View File

@ -0,0 +1 @@
{ "filepath_completion_use_working_dir": 0, "min_num_of_chars_for_completion": 2, "semantic_triggers": {}, "collect_identifiers_from_comments_and_strings": 0, "filetype_specific_completion_to_disable": {}, "collect_identifiers_from_tags_files": 0, "extra_conf_globlist": [ "~\/repos\/*", "\/home\/strahinja\/googrepos\/*", "~\/local_googrepos\/*", "~\/.ycm_extra_conf.py" ], "global_ycm_extra_conf": "\/usr\/lib\/youcompleteme\/ycm_extra_conf.py", "confirm_extra_conf": 1, "complete_in_comments": 0, "complete_in_strings": 1, "min_num_identifier_candidate_chars": 0, "max_diagnostics_to_display": 30, "auto_stop_csharp_server": 1, "seed_identifiers_with_syntax": 0, "csharp_server_port": 2000, "filetype_whitelist": { "*": "1" }, "auto_start_csharp_server": 1, "filetype_blacklist": { "tagbar": "1", "notes": "1", "markdown": "1", "unite": "1", "text": "1" } }

View File

@ -18,6 +18,8 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
# TODO: Move this file under server/ and rename it responses.py
def BuildGoToResponse( filepath, line_num, column_num, description = None ): def BuildGoToResponse( filepath, line_num, column_num, description = None ):
response = { response = {
'filepath': filepath, 'filepath': filepath,

168
python/ycm/server/server.py Executable file
View File

@ -0,0 +1,168 @@
#!/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
# 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__ ) ),
'../..' ) )
import logging
import time
import httplib
import json
import bottle
from bottle import run, request, response
import server_state
from ycm import extra_conf_store
from ycm import user_options_store
import argparse
# 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
user_options_store.LoadDefaults()
SERVER_STATE = server_state.ServerState( user_options_store.GetAll() )
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' ]
if SERVER_STATE.FiletypeCompletionUsable( filetypes ):
getattr( SERVER_STATE.GetFiletypeCompleter( filetypes ),
event_handler )( request_data )
if hasattr( extra_conf_store, event_handler ):
getattr( extra_conf_store, event_handler )( request_data )
# TODO: shut down the server on VimClose
@app.post( '/run_completer_command' )
def RunCompleterCommand():
LOGGER.info( 'Received command request')
request_data = request.json
completer_target = request_data[ 'completer_target' ]
if completer_target == 'identifier':
completer = SERVER_STATE.GetGeneralCompleter()
else:
completer = SERVER_STATE.GetFiletypeCompleter()
return _JsonResponse(
completer.OnUserCommand( request_data[ 'command_arguments' ],
request_data ) )
@app.post( '/get_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() )
# This is necessary so that general_completer_store fills up
# _current_query_completers.
# TODO: Fix this.
completer.ShouldUseNow( request_data )
# TODO: This should not be async anymore, server is multi-threaded
completer.CandidatesForQueryAsync( request_data )
while not completer.AsyncCandidateRequestReady():
time.sleep( 0.03 )
return _JsonResponse( completer.CandidatesFromStoredRequest() )
@app.route( '/user_options' )
def UserOptions():
global SERVER_STATE
if request.method == 'GET':
LOGGER.info( 'Received user options GET request')
return SERVER_STATE.user_options
elif request.method == 'POST':
LOGGER.info( 'Received user options POST request')
data = request.json
SERVER_STATE = server_state.ServerState( data )
user_options_store.SetAll( data )
else:
response.status = httplib.BAD_REQUEST
@app.post( '/filetype_completion_available')
def FiletypeCompletionAvailable():
LOGGER.info( 'Received filetype completion available request')
return _JsonResponse( SERVER_STATE.FiletypeCompletionAvailable(
request.json[ 'filetypes' ] ) )
def _JsonResponse( data ):
response.set_header( 'Content-Type', 'application/json' )
return json.dumps( data )
def Main():
global LOGGER
parser = argparse.ArgumentParser()
parser.add_argument( '--host', type = str, default = 'localhost',
help='server hostname')
parser.add_argument( '--port', type = int, default = 6666,
help='server port')
parser.add_argument( '--log', type = str, default = 'info',
help='log level, one of '
'[debug|info|warning|error|critical]')
args = parser.parse_args()
numeric_level = getattr( logging, args.log.upper(), None )
if not isinstance( numeric_level, int ):
raise ValueError( 'Invalid log level: %s' % args.log )
logging.basicConfig( format = '%(asctime)s - %(levelname)s - %(message)s',
level = numeric_level )
LOGGER = logging.getLogger( __name__ )
run( app = app, host = args.host, port = args.port, server='cherrypy' )
if __name__ == "__main__":
Main()

View File

@ -0,0 +1,108 @@
#!/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 imp
import os
from ycm.completers.general.general_completer_store import GeneralCompleterStore
class ServerState( object ):
def __init__( self, user_options ):
self._user_options = user_options
self._filetype_completers = {}
self._gencomp = GeneralCompleterStore( self._user_options )
@property
def user_options( self ):
return self._user_options
def _GetFiletypeCompleterForFiletype( self, filetype ):
try:
return self._filetype_completers[ filetype ]
except KeyError:
pass
module_path = _PathToFiletypeCompleterPluginLoader( filetype )
completer = None
supported_filetypes = [ filetype ]
if os.path.exists( module_path ):
module = imp.load_source( filetype, module_path )
completer = module.GetCompleter( self._user_options )
if completer:
supported_filetypes.extend( completer.SupportedFiletypes() )
for supported_filetype in supported_filetypes:
self._filetype_completers[ supported_filetype ] = completer
return completer
def GetFiletypeCompleter( self, current_filetypes ):
completers = [ self._GetFiletypeCompleterForFiletype( filetype )
for filetype in current_filetypes ]
for completer in completers:
if completer:
return completer
return None
def FiletypeCompletionAvailable( self, filetypes ):
return bool( self.GetFiletypeCompleter( filetypes ) )
def FiletypeCompletionUsable( self, filetypes ):
return ( self.CurrentFiletypeCompletionEnabled( filetypes ) and
self.FiletypeCompletionAvailable( filetypes ) )
def ShouldUseGeneralCompleter( self, request_data ):
return self._gencomp.ShouldUseNow( request_data )
def ShouldUseFiletypeCompleter( self, request_data ):
filetypes = request_data[ 'filetypes' ]
if self.FiletypeCompletionUsable( filetypes ):
return self.GetFiletypeCompleter( filetypes ).ShouldUseNow( request_data )
return False
def GetGeneralCompleter( self ):
return self._gencomp
def CurrentFiletypeCompletionEnabled( self, current_filetypes ):
filetype_to_disable = self._user_options[
'filetype_specific_completion_to_disable' ]
return not all([ x in filetype_to_disable for x in current_filetypes ])
def _PathToCompletersFolder():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
return os.path.join( dir_of_current_script, '..', 'completers' )
def _PathToFiletypeCompleterPluginLoader( filetype ):
return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' )

View File

View File

@ -0,0 +1,74 @@
#!/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/>.
from webtest import TestApp
from .. import server
from ..responses import BuildCompletionData
from nose.tools import ok_, eq_
import bottle
bottle.debug( True )
def GetCompletions_IdentifierCompleterWorks_test():
app = TestApp( server.app )
event_data = {
'event_name': 'FileReadyToParse',
'filetypes': ['foo'],
'filepath': '/foo/bar',
'file_data': {
'/foo/bar': {
'contents': 'foo foogoo ba',
'filetypes': ['foo']
}
}
}
app.post_json( '/event_notification', event_data )
line_value = 'oo foo foogoo ba';
completion_data = {
'query': 'oo',
'filetypes': ['foo'],
'filepath': '/foo/bar',
'line_num': 0,
'column_num': 2,
'start_column': 0,
'line_value': line_value,
'file_data': {
'/foo/bar': {
'contents': line_value,
'filetypes': ['foo']
}
}
}
eq_( [ BuildCompletionData( 'foo' ),
BuildCompletionData( 'foogoo' ) ],
app.post_json( '/get_completions', completion_data ).json )
def FiletypeCompletionAvailable_Works_test():
app = TestApp( server.app )
request_data = {
'filetypes': ['cpp']
}
ok_( app.post_json( '/filetype_completion_available',
request_data ).json )

View File

@ -17,6 +17,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import json
import os
from ycm.frozendict import frozendict from ycm.frozendict import frozendict
_USER_OPTIONS = {} _USER_OPTIONS = {}
@ -32,3 +34,16 @@ def GetAll():
def Value( key ): def Value( key ):
return _USER_OPTIONS[ key ] return _USER_OPTIONS[ key ]
def LoadDefaults():
SetAll( _DefaultOptions() )
def _DefaultOptions():
settings_path = os.path.join(
os.path.dirname( os.path.abspath( __file__ ) ),
'server/default_settings.json' )
with open( settings_path ) as f:
return json.loads( f.read() )

View File

@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import tempfile
import os
def IsIdentifierChar( char ): def IsIdentifierChar( char ):
return char.isalnum() or char == '_' return char.isalnum() or char == '_'
@ -29,3 +32,7 @@ def ToUtf8IfNeeded( string_or_unicode ):
if isinstance( string_or_unicode, unicode ): if isinstance( string_or_unicode, unicode ):
return string_or_unicode.encode( 'utf8' ) return string_or_unicode.encode( 'utf8' )
return string_or_unicode return string_or_unicode
def PathToTempDir():
return os.path.join( tempfile.gettempdir(), 'ycm_temp' )

View File

@ -17,176 +17,53 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import imp
import os import os
import time import time
import vim import vim
import ycm_core import ycm_core
import logging import subprocess
import tempfile
from ycm import vimsupport from ycm import vimsupport
from ycm import base from ycm import utils
from ycm import extra_conf_store
from ycm.completers.all.omni_completer import OmniCompleter from ycm.completers.all.omni_completer import OmniCompleter
from ycm.completers.general.general_completer_store import GeneralCompleterStore from ycm.client.base_request import BaseRequest
from ycm.client.command_request import CommandRequest
from ycm.client.completion_request import CompletionRequest
# TODO: Put the Request classes in separate files from ycm.client.event_notification import SendEventNotificationAsync
class BaseRequest( object ):
def __init__( self ):
pass
def Start( self ):
pass
def Done( self ):
return True
def Response( self ):
return {}
class CompletionRequest( BaseRequest ):
def __init__( self, ycm_state ):
super( CompletionRequest, self ).__init__()
self._completion_start_column = base.CompletionStartColumn()
self._ycm_state = ycm_state
self._request_data = _BuildRequestData( self._completion_start_column )
self._do_filetype_completion = self._ycm_state.ShouldUseFiletypeCompleter(
self._request_data )
self._completer = ( self._ycm_state.GetFiletypeCompleter() if
self._do_filetype_completion else
self._ycm_state.GetGeneralCompleter() )
def ShouldComplete( self ):
return ( self._do_filetype_completion or
self._ycm_state.ShouldUseGeneralCompleter( self._request_data ) )
def CompletionStartColumn( self ):
return self._completion_start_column
def Start( self, query ):
self._request_data[ 'query' ] = query
self._completer.CandidatesForQueryAsync( self._request_data )
def Done( self ):
return self._completer.AsyncCandidateRequestReady()
def Results( self ):
try:
return [ _ConvertCompletionDataToVimData( x )
for x in self._completer.CandidatesFromStoredRequest() ]
except Exception as e:
vimsupport.PostVimMessage( str( e ) )
return []
class CommandRequest( BaseRequest ):
class ServerResponse( object ):
def __init__( self ):
pass
def Valid( self ):
return True
def __init__( self, ycm_state, arguments, completer_target = None ):
super( CommandRequest, self ).__init__()
if not completer_target:
completer_target = 'filetpe_default'
if completer_target == 'omni':
self._completer = ycm_state.GetOmniCompleter()
elif completer_target == 'identifier':
self._completer = ycm_state.GetGeneralCompleter()
else:
self._completer = ycm_state.GetFiletypeCompleter()
self._arguments = arguments
def Start( self ):
self._completer.OnUserCommand( self._arguments,
_BuildRequestData() )
def Response( self ):
# TODO: Call vimsupport.JumpToLocation if the user called a GoTo command...
# we may want to have specific subclasses of CommandRequest so that a
# GoToRequest knows it needs to jump after the data comes back.
#
# Also need to run the following on GoTo data:
# CAREFUL about line/column number 0-based/1-based confusion!
#
# defs = []
# defs.append( {'filename': definition.module_path.encode( 'utf-8' ),
# 'lnum': definition.line,
# 'col': definition.column + 1,
# 'text': definition.description.encode( 'utf-8' ) } )
# vim.eval( 'setqflist( %s )' % repr( defs ) )
# vim.eval( 'youcompleteme#OpenGoToList()' )
return self.ServerResponse()
class EventNotification( BaseRequest ):
def __init__( self, event_name, ycm_state, extra_data = None ):
super( EventNotification, self ).__init__()
self._ycm_state = ycm_state
self._event_name = event_name
self._request_data = _BuildRequestData()
if extra_data:
self._request_data.update( extra_data )
def Start( self ):
event_handler = 'On' + self._event_name
getattr( self._ycm_state.GetGeneralCompleter(),
event_handler )( self._request_data )
if self._ycm_state.FiletypeCompletionUsable():
getattr( self._ycm_state.GetFiletypeCompleter(),
event_handler )( self._request_data )
if hasattr( extra_conf_store, event_handler ):
getattr( extra_conf_store, event_handler )( self._request_data )
def SendEventNotificationAsync( event_name, ycm_state, extra_data = None ):
event = EventNotification( event_name, ycm_state, extra_data )
event.Start()
SERVER_PORT_RANGE_START = 10000
class YouCompleteMe( object ): class YouCompleteMe( object ):
def __init__( self, user_options ): def __init__( self, user_options ):
# TODO: This should go into the server
# TODO: Use more logging like we do in cs_completer
self._logfile = tempfile.NamedTemporaryFile()
logging.basicConfig( format='%(asctime)s - %(levelname)s - %(message)s',
filename=self._logfile.name,
level=logging.DEBUG )
self._user_options = user_options self._user_options = user_options
self._gencomp = GeneralCompleterStore( user_options )
self._omnicomp = OmniCompleter( user_options ) self._omnicomp = OmniCompleter( user_options )
self._filetype_completers = {}
self._current_completion_request = None self._current_completion_request = None
server_port = SERVER_PORT_RANGE_START + os.getpid()
command = ''.join( [ 'python ',
_PathToServerScript(),
' --port=',
str( server_port ) ] )
BaseRequest.server_location = 'http://localhost:' + str( server_port )
filename_format = os.path.join( utils.PathToTempDir(),
'server_{port}_{std}.log' )
self._server_stdout = filename_format.format( port=server_port,
std='stdout' )
self._server_stderr = filename_format.format( port=server_port,
std='stderr' )
with open( self._server_stderr, 'w' ) as fstderr:
with open( self._server_stdout, 'w' ) as fstdout:
subprocess.Popen( command, stdout=fstdout, stderr=fstderr, shell=True )
def CreateCompletionRequest( self ): def CreateCompletionRequest( self ):
# We have to store a reference to the newly created CompletionRequest # We have to store a reference to the newly created CompletionRequest
# because VimScript can't store a reference to a Python object across # because VimScript can't store a reference to a Python object across
# function calls... Thus we need to keep this request somewhere. # function calls... Thus we need to keep this request somewhere.
self._current_completion_request = CompletionRequest( self ) self._current_completion_request = CompletionRequest()
return self._current_completion_request return self._current_completion_request
@ -204,72 +81,19 @@ class YouCompleteMe( object ):
return self._current_completion_request return self._current_completion_request
def GetGeneralCompleter( self ):
return self._gencomp
def GetOmniCompleter( self ): def GetOmniCompleter( self ):
return self._omnicomp return self._omnicomp
def GetFiletypeCompleter( self ): def NativeFiletypeCompletionAvailable( self ):
filetypes = vimsupport.CurrentFiletypes() # TODO: Talk to server about this.
completers = [ self.GetFiletypeCompleterForFiletype( filetype )
for filetype in filetypes ]
if not completers:
return None
# Try to find a native completer first
for completer in completers:
if completer and completer is not self._omnicomp:
return completer
# Return the omni completer for the first filetype
return completers[ 0 ]
def GetFiletypeCompleterForFiletype( self, filetype ):
try:
return self._filetype_completers[ filetype ]
except KeyError:
pass
module_path = _PathToFiletypeCompleterPluginLoader( filetype )
completer = None
supported_filetypes = [ filetype ]
if os.path.exists( module_path ):
module = imp.load_source( filetype, module_path )
completer = module.GetCompleter( self._user_options )
if completer:
supported_filetypes.extend( completer.SupportedFiletypes() )
else:
completer = self._omnicomp
for supported_filetype in supported_filetypes:
self._filetype_completers[ supported_filetype ] = completer
return completer
def ShouldUseGeneralCompleter( self, request_data ):
return self._gencomp.ShouldUseNow( request_data )
def ShouldUseFiletypeCompleter( self, request_data ):
if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShouldUseNow( request_data )
return False return False
def NativeFiletypeCompletionAvailable( self ): # TODO: This may not be needed at all when the server is ready. Evaluate this
completer = self.GetFiletypeCompleter() # later.
return bool( completer ) and completer is not self._omnicomp # def FiletypeCompletionAvailable( self ):
# return bool( self.GetFiletypeCompleter() )
def FiletypeCompletionAvailable( self ):
return bool( self.GetFiletypeCompleter() )
def NativeFiletypeCompletionUsable( self ): def NativeFiletypeCompletionUsable( self ):
@ -277,9 +101,11 @@ class YouCompleteMe( object ):
self.NativeFiletypeCompletionAvailable() ) self.NativeFiletypeCompletionAvailable() )
def FiletypeCompletionUsable( self ): # TODO: This may not be needed at all when the server is ready. Evaluate this
return ( self.CurrentFiletypeCompletionEnabled() and # later.
self.FiletypeCompletionAvailable() ) # def FiletypeCompletionUsable( self ):
# return ( self.CurrentFiletypeCompletionEnabled() and
# self.FiletypeCompletionAvailable() )
def OnFileReadyToParse( self ): def OnFileReadyToParse( self ):
@ -290,51 +116,55 @@ class YouCompleteMe( object ):
# TODO: make this work again # TODO: make this work again
# if self._user_options[ 'seed_identifiers_with_syntax' ]: # if self._user_options[ 'seed_identifiers_with_syntax' ]:
SendEventNotificationAsync( 'FileReadyToParse', self, extra_data ) SendEventNotificationAsync( 'FileReadyToParse', extra_data )
def OnBufferUnload( self, deleted_buffer_file ): def OnBufferUnload( self, deleted_buffer_file ):
SendEventNotificationAsync( 'BufferUnload', SendEventNotificationAsync( 'BufferUnload',
self,
{ 'unloaded_buffer': deleted_buffer_file } ) { 'unloaded_buffer': deleted_buffer_file } )
def OnBufferVisit( self ): def OnBufferVisit( self ):
SendEventNotificationAsync( 'BufferVisit', self ) SendEventNotificationAsync( 'BufferVisit' )
def OnInsertLeave( self ): def OnInsertLeave( self ):
SendEventNotificationAsync( 'InsertLeave', self ) SendEventNotificationAsync( 'InsertLeave' )
def OnVimLeave( self ): def OnVimLeave( self ):
SendEventNotificationAsync( 'VimLeave', self ) SendEventNotificationAsync( 'VimLeave' )
def OnCurrentIdentifierFinished( self ): def OnCurrentIdentifierFinished( self ):
SendEventNotificationAsync( 'CurrentIdentifierFinished', self ) SendEventNotificationAsync( 'CurrentIdentifierFinished' )
# TODO: Make this work again.
def DiagnosticsForCurrentFileReady( self ): def DiagnosticsForCurrentFileReady( self ):
if self.FiletypeCompletionUsable(): # if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady() # return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
return False return False
# TODO: Make this work again.
def GetDiagnosticsForCurrentFile( self ): def GetDiagnosticsForCurrentFile( self ):
if self.FiletypeCompletionUsable(): # if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile() # return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
return [] return []
# TODO: Make this work again.
def GetDetailedDiagnostic( self ): def GetDetailedDiagnostic( self ):
if self.FiletypeCompletionUsable(): # if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GetDetailedDiagnostic() # return self.GetFiletypeCompleter().GetDetailedDiagnostic()
pass
# TODO: Make this work again.
def GettingCompletions( self ): def GettingCompletions( self ):
if self.FiletypeCompletionUsable(): # if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GettingCompletions() # return self.GetFiletypeCompleter().GettingCompletions()
return False return False
@ -366,51 +196,13 @@ class YouCompleteMe( object ):
return not all([ x in filetype_to_disable for x in filetypes ]) return not all([ x in filetype_to_disable for x in filetypes ])
def _PathToCompletersFolder():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
return os.path.join( dir_of_current_script, 'completers' )
def _PathToFiletypeCompleterPluginLoader( filetype ):
return os.path.join( _PathToCompletersFolder(), filetype, 'hook.py' )
def _BuildRequestData( start_column = None, query = None ):
line, column = vimsupport.CurrentLineAndColumn()
request_data = {
'filetypes': vimsupport.CurrentFiletypes(),
'line_num': line,
'column_num': column,
'start_column': start_column,
'line_value': vim.current.line,
'filepath': vim.current.buffer.name,
'file_data': vimsupport.GetUnsavedAndCurrentBufferData()
}
if query:
request_data[ 'query' ] = query
return request_data
def _ConvertCompletionDataToVimData( completion_data ):
# see :h complete-items for a description of the dictionary fields
vim_data = {
'word' : completion_data[ 'insertion_text' ],
'dup' : 1,
}
if 'menu_text' in completion_data:
vim_data[ 'abbr' ] = completion_data[ 'menu_text' ]
if 'extra_menu_info' in completion_data:
vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ]
if 'kind' in completion_data:
vim_data[ 'kind' ] = completion_data[ 'kind' ]
if 'detailed_info' in completion_data:
vim_data[ 'info' ] = completion_data[ 'detailed_info' ]
return vim_data
def _GetTagFiles(): def _GetTagFiles():
tag_files = vim.eval( 'tagfiles()' ) tag_files = vim.eval( 'tagfiles()' )
current_working_directory = os.getcwd() current_working_directory = os.getcwd()
return [ os.path.join( current_working_directory, x ) for x in tag_files ] return [ os.path.join( current_working_directory, x ) for x in tag_files ]
def _PathToServerScript():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
return os.path.join( dir_of_current_script, 'server/server.py' )