A (barely) working version of ycmd + client
Still a lot of work to do.
This commit is contained in:
parent
02b88dccf1
commit
1730660555
@ -539,11 +539,6 @@ function! youcompleteme#Complete( findstart, base )
|
||||
endif
|
||||
|
||||
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()' )
|
||||
else
|
||||
return s:CompletionsForQuery( a:base )
|
||||
@ -628,14 +623,15 @@ function! youcompleteme#OpenGoToList()
|
||||
endfunction
|
||||
|
||||
|
||||
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()' ),
|
||||
\ "\n")
|
||||
endfunction
|
||||
" TODO: Make this work again
|
||||
" 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()' ),
|
||||
" \ "\n")
|
||||
" endfunction
|
||||
|
||||
|
||||
function! s:ForceCompile()
|
||||
|
@ -217,6 +217,18 @@ TEST( IdentifierCompleterTest, ShorterAndLowercaseWins ) {
|
||||
"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 ) {
|
||||
IdentifierCompleter completer;
|
||||
std::vector< std::string > tag_files;
|
||||
|
0
python/ycm/client/__init__.py
Normal file
0
python/ycm/client/__init__.py
Normal file
77
python/ycm/client/base_request.py
Normal file
77
python/ycm/client/base_request.py
Normal 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 ] )
|
||||
|
||||
|
67
python/ycm/client/command_request.py
Normal file
67
python/ycm/client/command_request.py
Normal 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()
|
||||
|
74
python/ycm/client/completion_request.py
Normal file
74
python/ycm/client/completion_request.py
Normal 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
|
42
python/ycm/client/event_notification.py
Normal file
42
python/ycm/client/event_notification.py
Normal 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()
|
||||
|
@ -18,12 +18,14 @@
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import logging
|
||||
import ycm_core
|
||||
from collections import defaultdict
|
||||
from ycm.completers.general_completer import GeneralCompleter
|
||||
# from ycm.completers.general import syntax_parse
|
||||
from ycm import utils
|
||||
from ycm import server_responses
|
||||
from ycm.utils import ToUtf8IfNeeded
|
||||
from ycm.server import responses
|
||||
|
||||
MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10
|
||||
SYNTAX_FILENAME = 'YCM_PLACEHOLDER_FOR_SYNTAX'
|
||||
@ -36,6 +38,7 @@ class IdentifierCompleter( GeneralCompleter ):
|
||||
self.completer.EnableThreading()
|
||||
self.tags_file_last_mtime = defaultdict( int )
|
||||
self.filetypes_with_keywords_loaded = set()
|
||||
self._logger = logging.getLogger( __name__ )
|
||||
|
||||
|
||||
def ShouldUseNow( self, request_data ):
|
||||
@ -44,8 +47,8 @@ class IdentifierCompleter( GeneralCompleter ):
|
||||
|
||||
def CandidatesForQueryAsync( self, request_data ):
|
||||
self.completions_future = self.completer.CandidatesForQueryAndTypeAsync(
|
||||
utils.SanitizeQuery( request_data[ 'query' ] ),
|
||||
request_data[ 'filetypes' ][ 0 ] )
|
||||
ToUtf8IfNeeded( utils.SanitizeQuery( request_data[ 'query' ] ) ),
|
||||
ToUtf8IfNeeded( request_data[ 'filetypes' ][ 0 ] ) )
|
||||
|
||||
|
||||
def AddIdentifier( self, identifier, request_data ):
|
||||
@ -56,10 +59,11 @@ class IdentifierCompleter( GeneralCompleter ):
|
||||
return
|
||||
|
||||
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,
|
||||
filetype,
|
||||
filepath )
|
||||
ToUtf8IfNeeded( filetype ),
|
||||
ToUtf8IfNeeded( filepath ) )
|
||||
|
||||
|
||||
def AddPreviousIdentifier( self, request_data ):
|
||||
@ -88,10 +92,11 @@ class IdentifierCompleter( GeneralCompleter ):
|
||||
return
|
||||
|
||||
text = request_data[ 'file_data' ][ filepath ][ 'contents' ]
|
||||
self._logger.info( 'Adding buffer identifiers for file: %s', filepath )
|
||||
self.completer.AddIdentifiersToDatabaseFromBufferAsync(
|
||||
text,
|
||||
filetype,
|
||||
filepath,
|
||||
ToUtf8IfNeeded( text ),
|
||||
ToUtf8IfNeeded( filetype ),
|
||||
ToUtf8IfNeeded( filepath ),
|
||||
collect_from_comments_and_strings )
|
||||
|
||||
|
||||
@ -110,7 +115,7 @@ class IdentifierCompleter( GeneralCompleter ):
|
||||
continue
|
||||
|
||||
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:
|
||||
return
|
||||
@ -161,7 +166,7 @@ class IdentifierCompleter( GeneralCompleter ):
|
||||
completions = _RemoveSmallCandidates(
|
||||
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 ):
|
||||
|
@ -20,7 +20,7 @@
|
||||
from collections import defaultdict
|
||||
import ycm_core
|
||||
import logging
|
||||
from ycm import server_responses
|
||||
from ycm.server import responses
|
||||
from ycm import extra_conf_store
|
||||
from ycm.utils import ToUtf8IfNeeded
|
||||
from ycm.completers.completer import Completer
|
||||
@ -72,9 +72,10 @@ class ClangCompleter( Completer ):
|
||||
continue
|
||||
|
||||
unsaved_file = ycm_core.UnsavedFile()
|
||||
unsaved_file.contents_ = contents
|
||||
unsaved_file.length_ = len( contents )
|
||||
unsaved_file.filename_ = filename
|
||||
utf8_contents = ToUtf8IfNeeded( contents )
|
||||
unsaved_file.contents_ = utf8_contents
|
||||
unsaved_file.length_ = len( utf8_contents )
|
||||
unsaved_file.filename_ = ToUtf8IfNeeded( filename )
|
||||
|
||||
files.append( unsaved_file )
|
||||
return files
|
||||
@ -86,17 +87,17 @@ class ClangCompleter( Completer ):
|
||||
if not filename:
|
||||
return
|
||||
|
||||
if self.completer.UpdatingTranslationUnit( filename ):
|
||||
if self.completer.UpdatingTranslationUnit( ToUtf8IfNeeded( filename ) ):
|
||||
self.completions_future = None
|
||||
self._logger.info( PARSING_FILE_MESSAGE )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
PARSING_FILE_MESSAGE )
|
||||
|
||||
flags = self.flags.FlagsForFile( filename )
|
||||
if not flags:
|
||||
self.completions_future = None
|
||||
self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
NO_COMPILE_FLAGS_MESSAGE )
|
||||
|
||||
# TODO: sanitize query, probably in C++ code
|
||||
@ -142,11 +143,11 @@ class ClangCompleter( Completer ):
|
||||
|
||||
command = arguments[ 0 ]
|
||||
if command == 'GoToDefinition':
|
||||
self._GoToDefinition( request_data )
|
||||
return self._GoToDefinition( request_data )
|
||||
elif command == 'GoToDeclaration':
|
||||
self._GoToDeclaration( request_data )
|
||||
return self._GoToDeclaration( request_data )
|
||||
elif command == 'GoToDefinitionElseDeclaration':
|
||||
self._GoToDefinitionElseDeclaration( request_data )
|
||||
return self._GoToDefinitionElseDeclaration( request_data )
|
||||
elif command == 'ClearCompilationFlagCache':
|
||||
self._ClearCompilationFlagCache( request_data )
|
||||
|
||||
@ -155,20 +156,20 @@ class ClangCompleter( Completer ):
|
||||
filename = request_data[ 'filepath' ]
|
||||
if not filename:
|
||||
self._logger.warning( INVALID_FILE_MESSAGE )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
INVALID_FILE_MESSAGE )
|
||||
|
||||
flags = self.flags.FlagsForFile( filename )
|
||||
if not flags:
|
||||
self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
NO_COMPILE_FLAGS_MESSAGE )
|
||||
|
||||
files = self.GetUnsavedFilesVector()
|
||||
files = self.GetUnsavedFilesVector( request_data )
|
||||
line = request_data[ 'line_num' ] + 1
|
||||
column = request_data[ 'start_column' ] + 1
|
||||
return getattr( self.completer, goto_function )(
|
||||
filename,
|
||||
ToUtf8IfNeeded( filename ),
|
||||
line,
|
||||
column,
|
||||
files,
|
||||
@ -180,7 +181,7 @@ class ClangCompleter( Completer ):
|
||||
if not location or not location.IsValid():
|
||||
raise RuntimeError( 'Can\'t jump to definition.' )
|
||||
|
||||
return server_responses.BuildGoToResponse( location.filename_,
|
||||
return responses.BuildGoToResponse( location.filename_,
|
||||
location.line_number_,
|
||||
location.column_number_ )
|
||||
|
||||
@ -190,7 +191,7 @@ class ClangCompleter( Completer ):
|
||||
if not location or not location.IsValid():
|
||||
raise RuntimeError( 'Can\'t jump to declaration.' )
|
||||
|
||||
return server_responses.BuildGoToResponse( location.filename_,
|
||||
return responses.BuildGoToResponse( location.filename_,
|
||||
location.line_number_,
|
||||
location.column_number_ )
|
||||
|
||||
@ -202,7 +203,7 @@ class ClangCompleter( Completer ):
|
||||
if not location or not location.IsValid():
|
||||
raise RuntimeError( 'Can\'t jump to definition or declaration.' )
|
||||
|
||||
return server_responses.BuildGoToResponse( location.filename_,
|
||||
return responses.BuildGoToResponse( location.filename_,
|
||||
location.line_number_,
|
||||
location.column_number_ )
|
||||
|
||||
@ -223,10 +224,10 @@ class ClangCompleter( Completer ):
|
||||
|
||||
if not filename:
|
||||
self._logger.warning( INVALID_FILE_MESSAGE )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
INVALID_FILE_MESSAGE )
|
||||
|
||||
if self.completer.UpdatingTranslationUnit( filename ):
|
||||
if self.completer.UpdatingTranslationUnit( ToUtf8IfNeeded( filename ) ):
|
||||
self.extra_parse_desired = True
|
||||
return
|
||||
|
||||
@ -234,11 +235,11 @@ class ClangCompleter( Completer ):
|
||||
if not flags:
|
||||
self.parse_future = None
|
||||
self._logger.info( NO_COMPILE_FLAGS_MESSAGE )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
NO_COMPILE_FLAGS_MESSAGE )
|
||||
|
||||
self.parse_future = self.completer.UpdateTranslationUnitAsync(
|
||||
filename,
|
||||
ToUtf8IfNeeded( filename ),
|
||||
self.GetUnsavedFilesVector( request_data ),
|
||||
flags )
|
||||
|
||||
@ -246,7 +247,8 @@ class ClangCompleter( Completer ):
|
||||
|
||||
|
||||
def OnBufferUnload( self, request_data ):
|
||||
self.completer.DeleteCachesForFileAsync( request_data[ 'unloaded_buffer' ] )
|
||||
self.completer.DeleteCachesForFileAsync(
|
||||
ToUtf8IfNeeded( request_data[ 'unloaded_buffer' ] ) )
|
||||
|
||||
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
@ -257,16 +259,18 @@ class ClangCompleter( Completer ):
|
||||
|
||||
|
||||
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 ):
|
||||
filename = request_data[ 'filepath' ]
|
||||
if self.DiagnosticsForCurrentFileReady():
|
||||
diagnostics = self.completer.DiagnosticsForFile( filename )
|
||||
diagnostics = self.completer.DiagnosticsForFile(
|
||||
ToUtf8IfNeeded( filename ) )
|
||||
self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
|
||||
self.last_prepared_diagnostics = [
|
||||
server_responses.BuildDiagnosticData( x ) for x in
|
||||
responses.BuildDiagnosticData( x ) for x in
|
||||
diagnostics[ : self.max_diagnostics_to_display ] ]
|
||||
self.parse_future = None
|
||||
|
||||
@ -282,12 +286,12 @@ class ClangCompleter( Completer ):
|
||||
current_file = request_data[ 'filepath' ]
|
||||
|
||||
if not self.diagnostic_store:
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
NO_DIAGNOSTIC_MESSAGE )
|
||||
|
||||
diagnostics = self.diagnostic_store[ current_file ][ current_line ]
|
||||
if not diagnostics:
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
NO_DIAGNOSTIC_MESSAGE )
|
||||
|
||||
closest_diagnostic = None
|
||||
@ -299,7 +303,7 @@ class ClangCompleter( Completer ):
|
||||
distance_to_closest_diagnostic = distance
|
||||
closest_diagnostic = diagnostic
|
||||
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
closest_diagnostic.long_formatted_text_ )
|
||||
|
||||
|
||||
@ -314,7 +318,7 @@ class ClangCompleter( Completer ):
|
||||
return ''
|
||||
flags = self.flags.FlagsForFile( filename ) or []
|
||||
source = extra_conf_store.ModuleFileForSourceFile( filename )
|
||||
return server_responses.BuildDisplayMessageResponse(
|
||||
return responses.BuildDisplayMessageResponse(
|
||||
'Flags for {0} loaded from {1}:\n{2}'.format( filename,
|
||||
source,
|
||||
list( flags ) ) )
|
||||
@ -348,7 +352,7 @@ class ClangCompleter( Completer ):
|
||||
|
||||
|
||||
def ConvertCompletionData( completion_data ):
|
||||
return server_responses.BuildCompletionData(
|
||||
return responses.BuildCompletionData(
|
||||
insertion_text = completion_data.TextToInsertInBuffer(),
|
||||
menu_text = completion_data.MainCompletionText(),
|
||||
extra_menu_info = completion_data.ExtraMenuInfo(),
|
||||
|
@ -22,13 +22,13 @@ import os
|
||||
from sys import platform
|
||||
import glob
|
||||
from ycm.completers.threaded_completer import ThreadedCompleter
|
||||
from ycm import server_responses
|
||||
from ycm.server import responses
|
||||
from ycm import utils
|
||||
import urllib2
|
||||
import urllib
|
||||
import urlparse
|
||||
import json
|
||||
import subprocess
|
||||
import tempfile
|
||||
import logging
|
||||
|
||||
|
||||
@ -44,7 +44,7 @@ class CsharpCompleter( ThreadedCompleter ):
|
||||
def __init__( self, user_options ):
|
||||
super( CsharpCompleter, self ).__init__( user_options )
|
||||
self._omnisharp_port = None
|
||||
self._logger = logging.getLogger(__name__)
|
||||
self._logger = logging.getLogger( __name__ )
|
||||
|
||||
# if self.user_options[ 'auto_start_csharp_server' ]:
|
||||
# self._StartServer()
|
||||
@ -62,7 +62,7 @@ class CsharpCompleter( ThreadedCompleter ):
|
||||
|
||||
|
||||
def ComputeCandidates( self, request_data ):
|
||||
return [ server_responses.BuildCompletionData(
|
||||
return [ responses.BuildCompletionData(
|
||||
completion[ 'CompletionText' ],
|
||||
completion[ 'DisplayText' ],
|
||||
completion[ 'Description' ] )
|
||||
@ -135,8 +135,8 @@ class CsharpCompleter( ThreadedCompleter ):
|
||||
command = [ omnisharp + ' -p ' + str( self._omnisharp_port ) + ' -s ' +
|
||||
path_to_solutionfile ]
|
||||
|
||||
filename_format = ( tempfile.gettempdir() +
|
||||
'/omnisharp_{port}_{sln}_{std}.log' )
|
||||
filename_format = os.path.join( utils.PathToTempDir(),
|
||||
'omnisharp_{port}_{sln}_{std}.log' )
|
||||
|
||||
self._filename_stdout = filename_format.format(
|
||||
port=self._omnisharp_port, sln=solutionfile, std='stdout' )
|
||||
@ -169,7 +169,7 @@ class CsharpCompleter( ThreadedCompleter ):
|
||||
definition = self._GetResponse( '/gotodefinition',
|
||||
self._DefaultParameters( request_data ) )
|
||||
if definition[ 'FileName' ] != None:
|
||||
return server_responses.BuildGoToResponse( definition[ 'FileName' ],
|
||||
return responses.BuildGoToResponse( definition[ 'FileName' ],
|
||||
definition[ 'Line' ],
|
||||
definition[ 'Column' ] )
|
||||
else:
|
||||
|
@ -22,7 +22,7 @@ import re
|
||||
from ycm.completers.threaded_completer import ThreadedCompleter
|
||||
from ycm.completers.cpp.clang_completer import InCFamilyFile
|
||||
from ycm.completers.cpp.flags import Flags
|
||||
from ycm import server_responses
|
||||
from ycm.server import responses
|
||||
|
||||
class FilenameCompleter( ThreadedCompleter ):
|
||||
"""
|
||||
@ -146,7 +146,7 @@ def _GenerateCandidatesForPaths( absolute_paths ):
|
||||
|
||||
is_dir = os.path.isdir( absolute_path )
|
||||
completion_dicts.append(
|
||||
server_responses.BuildCompletionData( basename,
|
||||
responses.BuildCompletionData( basename,
|
||||
'[Dir]' if is_dir else '[File]' ) )
|
||||
|
||||
return completion_dicts
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
from ycm.completers.general_completer import GeneralCompleter
|
||||
from UltiSnips import UltiSnips_Manager
|
||||
from ycm import server_responses
|
||||
from ycm.server import responses
|
||||
|
||||
|
||||
class UltiSnipsCompleter( GeneralCompleter ):
|
||||
@ -64,7 +64,7 @@ def _GetCandidates():
|
||||
# UltiSnips_Manager._snips() returns a class instance where:
|
||||
# class.trigger - name of snippet trigger word ( e.g. defn or testcase )
|
||||
# class.description - description of the snippet
|
||||
return [ server_responses.BuildCompletionData(
|
||||
return [ responses.BuildCompletionData(
|
||||
str( snip.trigger ),
|
||||
str( '<snip> ' + snip.description.encode( 'utf-8' ) ) )
|
||||
for snip in rawsnips ]
|
||||
|
@ -20,7 +20,7 @@
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ycm.completers.threaded_completer import ThreadedCompleter
|
||||
from ycm import server_responses
|
||||
from ycm.server import responses
|
||||
|
||||
import sys
|
||||
from os.path import join, abspath, dirname
|
||||
@ -65,7 +65,7 @@ class JediCompleter( ThreadedCompleter ):
|
||||
|
||||
def ComputeCandidates( self, request_data ):
|
||||
script = self._GetJediScript( request_data )
|
||||
return [ server_responses.BuildCompletionData(
|
||||
return [ responses.BuildCompletionData(
|
||||
str( completion.name ),
|
||||
str( completion.description ),
|
||||
str( completion.doc ) )
|
||||
@ -140,7 +140,7 @@ class JediCompleter( ThreadedCompleter ):
|
||||
else:
|
||||
raise RuntimeError( 'Builtin modules cannot be displayed.' )
|
||||
else:
|
||||
return server_responses.BuildGoToResponse( definition.module_path,
|
||||
return responses.BuildGoToResponse( definition.module_path,
|
||||
definition.line -1,
|
||||
definition.column )
|
||||
else:
|
||||
@ -148,11 +148,11 @@ class JediCompleter( ThreadedCompleter ):
|
||||
defs = []
|
||||
for definition in definition_list:
|
||||
if definition.in_builtin_module():
|
||||
defs.append( server_responses.BuildDescriptionOnlyGoToResponse(
|
||||
defs.append( responses.BuildDescriptionOnlyGoToResponse(
|
||||
'Builting ' + definition.description ) )
|
||||
else:
|
||||
defs.append(
|
||||
server_responses.BuildGoToResponse( definition.module_path,
|
||||
responses.BuildGoToResponse( definition.module_path,
|
||||
definition.line -1,
|
||||
definition.column,
|
||||
definition.description ) )
|
||||
|
0
python/ycm/server/__init__.py
Normal file
0
python/ycm/server/__init__.py
Normal file
1
python/ycm/server/default_settings.json
Normal file
1
python/ycm/server/default_settings.json
Normal 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"
}
}
|
@ -18,6 +18,8 @@
|
||||
# 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 ):
|
||||
response = {
|
||||
'filepath': filepath,
|
168
python/ycm/server/server.py
Executable file
168
python/ycm/server/server.py
Executable 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()
|
||||
|
108
python/ycm/server/server_state.py
Normal file
108
python/ycm/server/server_state.py
Normal 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' )
|
||||
|
||||
|
0
python/ycm/server/tests/__init__.py
Normal file
0
python/ycm/server/tests/__init__.py
Normal file
74
python/ycm/server/tests/basic_test.py
Normal file
74
python/ycm/server/tests/basic_test.py
Normal 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 )
|
@ -17,6 +17,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import json
|
||||
import os
|
||||
from ycm.frozendict import frozendict
|
||||
|
||||
_USER_OPTIONS = {}
|
||||
@ -32,3 +34,16 @@ def GetAll():
|
||||
|
||||
def Value( 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() )
|
||||
|
||||
|
@ -17,6 +17,9 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
def IsIdentifierChar( char ):
|
||||
return char.isalnum() or char == '_'
|
||||
|
||||
@ -29,3 +32,7 @@ def ToUtf8IfNeeded( string_or_unicode ):
|
||||
if isinstance( string_or_unicode, unicode ):
|
||||
return string_or_unicode.encode( 'utf8' )
|
||||
return string_or_unicode
|
||||
|
||||
|
||||
def PathToTempDir():
|
||||
return os.path.join( tempfile.gettempdir(), 'ycm_temp' )
|
||||
|
@ -17,176 +17,53 @@
|
||||
# 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
|
||||
import time
|
||||
import vim
|
||||
import ycm_core
|
||||
import logging
|
||||
import tempfile
|
||||
import subprocess
|
||||
from ycm import vimsupport
|
||||
from ycm import base
|
||||
from ycm import extra_conf_store
|
||||
from ycm import utils
|
||||
from ycm.completers.all.omni_completer import OmniCompleter
|
||||
from ycm.completers.general.general_completer_store import GeneralCompleterStore
|
||||
|
||||
|
||||
# TODO: Put the Request classes in separate files
|
||||
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()
|
||||
from ycm.client.base_request import BaseRequest
|
||||
from ycm.client.command_request import CommandRequest
|
||||
from ycm.client.completion_request import CompletionRequest
|
||||
from ycm.client.event_notification import SendEventNotificationAsync
|
||||
|
||||
SERVER_PORT_RANGE_START = 10000
|
||||
|
||||
class YouCompleteMe( object ):
|
||||
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._gencomp = GeneralCompleterStore( user_options )
|
||||
self._omnicomp = OmniCompleter( user_options )
|
||||
self._filetype_completers = {}
|
||||
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 ):
|
||||
# We have to store a reference to the newly created CompletionRequest
|
||||
# because VimScript can't store a reference to a Python object across
|
||||
# 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
|
||||
|
||||
|
||||
@ -204,72 +81,19 @@ class YouCompleteMe( object ):
|
||||
return self._current_completion_request
|
||||
|
||||
|
||||
def GetGeneralCompleter( self ):
|
||||
return self._gencomp
|
||||
|
||||
|
||||
def GetOmniCompleter( self ):
|
||||
return self._omnicomp
|
||||
|
||||
|
||||
def GetFiletypeCompleter( self ):
|
||||
filetypes = vimsupport.CurrentFiletypes()
|
||||
|
||||
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 )
|
||||
def NativeFiletypeCompletionAvailable( self ):
|
||||
# TODO: Talk to server about this.
|
||||
return False
|
||||
|
||||
|
||||
def NativeFiletypeCompletionAvailable( self ):
|
||||
completer = self.GetFiletypeCompleter()
|
||||
return bool( completer ) and completer is not self._omnicomp
|
||||
|
||||
|
||||
def FiletypeCompletionAvailable( self ):
|
||||
return bool( self.GetFiletypeCompleter() )
|
||||
# TODO: This may not be needed at all when the server is ready. Evaluate this
|
||||
# later.
|
||||
# def FiletypeCompletionAvailable( self ):
|
||||
# return bool( self.GetFiletypeCompleter() )
|
||||
|
||||
|
||||
def NativeFiletypeCompletionUsable( self ):
|
||||
@ -277,9 +101,11 @@ class YouCompleteMe( object ):
|
||||
self.NativeFiletypeCompletionAvailable() )
|
||||
|
||||
|
||||
def FiletypeCompletionUsable( self ):
|
||||
return ( self.CurrentFiletypeCompletionEnabled() and
|
||||
self.FiletypeCompletionAvailable() )
|
||||
# TODO: This may not be needed at all when the server is ready. Evaluate this
|
||||
# later.
|
||||
# def FiletypeCompletionUsable( self ):
|
||||
# return ( self.CurrentFiletypeCompletionEnabled() and
|
||||
# self.FiletypeCompletionAvailable() )
|
||||
|
||||
|
||||
def OnFileReadyToParse( self ):
|
||||
@ -290,51 +116,55 @@ class YouCompleteMe( object ):
|
||||
# TODO: make this work again
|
||||
# if self._user_options[ 'seed_identifiers_with_syntax' ]:
|
||||
|
||||
SendEventNotificationAsync( 'FileReadyToParse', self, extra_data )
|
||||
SendEventNotificationAsync( 'FileReadyToParse', extra_data )
|
||||
|
||||
|
||||
def OnBufferUnload( self, deleted_buffer_file ):
|
||||
SendEventNotificationAsync( 'BufferUnload',
|
||||
self,
|
||||
{ 'unloaded_buffer': deleted_buffer_file } )
|
||||
|
||||
|
||||
def OnBufferVisit( self ):
|
||||
SendEventNotificationAsync( 'BufferVisit', self )
|
||||
SendEventNotificationAsync( 'BufferVisit' )
|
||||
|
||||
|
||||
def OnInsertLeave( self ):
|
||||
SendEventNotificationAsync( 'InsertLeave', self )
|
||||
SendEventNotificationAsync( 'InsertLeave' )
|
||||
|
||||
|
||||
def OnVimLeave( self ):
|
||||
SendEventNotificationAsync( 'VimLeave', self )
|
||||
SendEventNotificationAsync( 'VimLeave' )
|
||||
|
||||
|
||||
def OnCurrentIdentifierFinished( self ):
|
||||
SendEventNotificationAsync( 'CurrentIdentifierFinished', self )
|
||||
SendEventNotificationAsync( 'CurrentIdentifierFinished' )
|
||||
|
||||
|
||||
# TODO: Make this work again.
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
if self.FiletypeCompletionUsable():
|
||||
return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
|
||||
# if self.FiletypeCompletionUsable():
|
||||
# return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
|
||||
return False
|
||||
|
||||
|
||||
# TODO: Make this work again.
|
||||
def GetDiagnosticsForCurrentFile( self ):
|
||||
if self.FiletypeCompletionUsable():
|
||||
return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
|
||||
# if self.FiletypeCompletionUsable():
|
||||
# return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
|
||||
return []
|
||||
|
||||
|
||||
# TODO: Make this work again.
|
||||
def GetDetailedDiagnostic( self ):
|
||||
if self.FiletypeCompletionUsable():
|
||||
return self.GetFiletypeCompleter().GetDetailedDiagnostic()
|
||||
# if self.FiletypeCompletionUsable():
|
||||
# return self.GetFiletypeCompleter().GetDetailedDiagnostic()
|
||||
pass
|
||||
|
||||
|
||||
# TODO: Make this work again.
|
||||
def GettingCompletions( self ):
|
||||
if self.FiletypeCompletionUsable():
|
||||
return self.GetFiletypeCompleter().GettingCompletions()
|
||||
# if self.FiletypeCompletionUsable():
|
||||
# return self.GetFiletypeCompleter().GettingCompletions()
|
||||
return False
|
||||
|
||||
|
||||
@ -366,51 +196,13 @@ class YouCompleteMe( object ):
|
||||
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():
|
||||
tag_files = vim.eval( 'tagfiles()' )
|
||||
current_working_directory = os.getcwd()
|
||||
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' )
|
||||
|
Loading…
Reference in New Issue
Block a user