diff --git a/python/ycm/completers/all/identifier_completer.py b/python/ycm/completers/all/identifier_completer.py
index 196ff85a..aa3d812b 100644
--- a/python/ycm/completers/all/identifier_completer.py
+++ b/python/ycm/completers/all/identifier_completer.py
@@ -18,12 +18,10 @@
# along with YouCompleteMe. If not, see .
import os
-import vim
import ycm_core
from collections import defaultdict
from ycm.completers.general_completer import GeneralCompleter
-from ycm.completers.general import syntax_parse
-from ycm import vimsupport
+# from ycm.completers.general import syntax_parse
from ycm import utils
from ycm import server_responses
@@ -50,9 +48,9 @@ class IdentifierCompleter( GeneralCompleter ):
request_data[ 'filetypes' ][ 0 ] )
- def AddIdentifier( self, identifier ):
- filetype = vim.eval( "&filetype" )
- filepath = vim.eval( "expand('%:p')" )
+ def AddIdentifier( self, identifier, request_data ):
+ filetype = request_data[ 'filetypes' ][ 0 ]
+ filepath = request_data[ 'filepath' ]
if not filetype or not filepath or not identifier:
return
@@ -64,23 +62,20 @@ class IdentifierCompleter( GeneralCompleter ):
filepath )
- def AddPreviousIdentifier( self ):
- self.AddIdentifier( _PreviousIdentifier( self.user_options[
- 'min_num_of_chars_for_completion' ] ) )
+ def AddPreviousIdentifier( self, request_data ):
+ self.AddIdentifier(
+ _PreviousIdentifier(
+ self.user_options[ 'min_num_of_chars_for_completion' ],
+ request_data ),
+ request_data )
- def AddIdentifierUnderCursor( self ):
- cursor_identifier = vim.eval( 'expand("")' )
+ def AddIdentifierUnderCursor( self, request_data ):
+ cursor_identifier = _GetCursorIdentifier( request_data )
if not cursor_identifier:
return
- stripped_cursor_identifier = ''.join( ( x for x in
- cursor_identifier if
- utils.IsIdentifierChar( x ) ) )
- if not stripped_cursor_identifier:
- return
-
- self.AddIdentifier( stripped_cursor_identifier )
+ self.AddIdentifier( cursor_identifier, request_data )
def AddBufferIdentifiers( self, request_data ):
@@ -100,26 +95,22 @@ class IdentifierCompleter( GeneralCompleter ):
collect_from_comments_and_strings )
- def AddIdentifiersFromTagFiles( self ):
- tag_files = vim.eval( 'tagfiles()' )
- current_working_directory = os.getcwd()
+ def AddIdentifiersFromTagFiles( self, tag_files ):
absolute_paths_to_tag_files = ycm_core.StringVec()
for tag_file in tag_files:
- absolute_tag_file = os.path.join( current_working_directory,
- tag_file )
try:
- current_mtime = os.path.getmtime( absolute_tag_file )
+ current_mtime = os.path.getmtime( tag_file )
except:
continue
- last_mtime = self.tags_file_last_mtime[ absolute_tag_file ]
+ last_mtime = self.tags_file_last_mtime[ tag_file ]
# We don't want to repeatedly process the same file over and over; we only
# process if it's changed since the last time we looked at it
if current_mtime <= last_mtime:
continue
- self.tags_file_last_mtime[ absolute_tag_file ] = current_mtime
- absolute_paths_to_tag_files.append( absolute_tag_file )
+ self.tags_file_last_mtime[ tag_file ] = current_mtime
+ absolute_paths_to_tag_files.append( tag_file )
if not absolute_paths_to_tag_files:
return
@@ -128,41 +119,37 @@ class IdentifierCompleter( GeneralCompleter ):
absolute_paths_to_tag_files )
- def AddIdentifiersFromSyntax( self ):
- filetype = vim.eval( "&filetype" )
- if filetype in self.filetypes_with_keywords_loaded:
- return
+ # def AddIdentifiersFromSyntax( self ):
+ # filetype = vim.eval( "&filetype" )
+ # if filetype in self.filetypes_with_keywords_loaded:
+ # return
- self.filetypes_with_keywords_loaded.add( filetype )
+ # self.filetypes_with_keywords_loaded.add( filetype )
- keyword_set = syntax_parse.SyntaxKeywordsForCurrentBuffer()
- keywords = ycm_core.StringVec()
- for keyword in keyword_set:
- keywords.append( keyword )
+ # keyword_set = syntax_parse.SyntaxKeywordsForCurrentBuffer()
+ # keywords = ycm_core.StringVec()
+ # for keyword in keyword_set:
+ # keywords.append( keyword )
- filepath = SYNTAX_FILENAME + filetype
- self.completer.AddIdentifiersToDatabase( keywords,
- filetype,
- filepath )
+ # filepath = SYNTAX_FILENAME + filetype
+ # self.completer.AddIdentifiersToDatabase( keywords,
+ # filetype,
+ # filepath )
def OnFileReadyToParse( self, request_data ):
self.AddBufferIdentifiers( request_data )
-
- # TODO: make these work again
- # if self.user_options[ 'collect_identifiers_from_tags_files' ]:
- # self.AddIdentifiersFromTagFiles()
-
- # if self.user_options[ 'seed_identifiers_with_syntax' ]:
- # self.AddIdentifiersFromSyntax()
+ if 'tag_files' in request_data:
+ self.AddIdentifiersFromTagFiles( request_data[ 'tag_files' ] )
+ #self.AddIdentifiersFromSyntax()
- def OnInsertLeave( self ):
- self.AddIdentifierUnderCursor()
+ def OnInsertLeave( self, request_data ):
+ self.AddIdentifierUnderCursor( request_data )
- def OnCurrentIdentifierFinished( self ):
- self.AddPreviousIdentifier()
+ def OnCurrentIdentifierFinished( self, request_data ):
+ self.AddPreviousIdentifier( request_data )
def CandidatesFromStoredRequest( self ):
@@ -177,10 +164,13 @@ class IdentifierCompleter( GeneralCompleter ):
return [ server_responses.BuildCompletionData( x ) for x in completions ]
-def _PreviousIdentifier( min_num_completion_start_chars ):
- line_num, column_num = vimsupport.CurrentLineAndColumn()
- buffer = vim.current.buffer
- line = buffer[ line_num ]
+def _PreviousIdentifier( min_num_completion_start_chars, request_data ):
+ line_num = request_data[ 'line_num' ]
+ column_num = request_data[ 'column_num' ]
+ filepath = request_data[ 'filepath' ]
+ contents_per_line = (
+ request_data[ 'file_data' ][ filepath ][ 'contents' ].split( '\n' ) )
+ line = contents_per_line[ line_num ]
end_column = column_num
@@ -190,7 +180,7 @@ def _PreviousIdentifier( min_num_completion_start_chars ):
# Look at the previous line if we reached the end of the current one
if end_column == 0:
try:
- line = buffer[ line_num - 1]
+ line = contents_per_line[ line_num - 1 ]
except:
return ""
end_column = len( line )
@@ -214,3 +204,40 @@ def _RemoveSmallCandidates( candidates, min_num_candidate_size_chars ):
return [ x for x in candidates if len( x ) >= min_num_candidate_size_chars ]
+
+# This is meant to behave like 'expand(" 0 and utils.IsIdentifierChar( line[
+ identifier_start - 1 ] ):
+ identifier_start -= 1
+ return identifier_start
+
+
+ def FindIdentifierEnd( line, valid_char_column ):
+ identifier_end = valid_char_column
+ while identifier_end < len( line ) - 1 and utils.IsIdentifierChar( line[
+ identifier_end + 1 ] ):
+ identifier_end += 1
+ return identifier_end + 1
+
+ column_num = request_data[ 'column_num' ]
+ line = request_data[ 'line_value' ]
+
+ try:
+ valid_char_column = FindFirstValidChar( line, column_num )
+ return line[ FindIdentifierStart( line, valid_char_column ) :
+ FindIdentifierEnd( line, valid_char_column ) ]
+ except:
+ return ''
+
diff --git a/python/ycm/completers/all/tests/identifier_completer_test.py b/python/ycm/completers/all/tests/identifier_completer_test.py
new file mode 100644
index 00000000..532773cd
--- /dev/null
+++ b/python/ycm/completers/all/tests/identifier_completer_test.py
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 Strahinja Val Markovic
+#
+# 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 .
+
+from nose.tools import eq_
+from ycm.completers.all import identifier_completer
+
+
+def GetCursorIdentifier_StartOfLine_test():
+ eq_( 'foo',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 0,
+ 'line_value': 'foo'
+ } ) )
+
+ eq_( 'fooBar',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 0,
+ 'line_value': 'fooBar'
+ } ) )
+
+
+def GetCursorIdentifier_EndOfLine_test():
+ eq_( 'foo',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 2,
+ 'line_value': 'foo'
+ } ) )
+
+
+def GetCursorIdentifier_PastEndOfLine_test():
+ eq_( '',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 10,
+ 'line_value': 'foo'
+ } ) )
+
+
+def GetCursorIdentifier_NegativeColumn_test():
+ eq_( '',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': -10,
+ 'line_value': 'foo'
+ } ) )
+
+
+def GetCursorIdentifier_StartOfLine_StopsAtNonIdentifierChar_test():
+ eq_( 'foo',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 0,
+ 'line_value': 'foo(goo)'
+ } ) )
+
+
+def GetCursorIdentifier_AtNonIdentifier_test():
+ eq_( 'goo',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 3,
+ 'line_value': 'foo(goo)'
+ } ) )
+
+
+def GetCursorIdentifier_WalksForwardForIdentifier_test():
+ eq_( 'foo',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 0,
+ 'line_value': ' foo'
+ } ) )
+
+
+def GetCursorIdentifier_FindsNothingForward_test():
+ eq_( '',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 4,
+ 'line_value': 'foo ()***()'
+ } ) )
+
+
+def GetCursorIdentifier_SingleCharIdentifier_test():
+ eq_( 'f',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 0,
+ 'line_value': ' f '
+ } ) )
+
+
+def GetCursorIdentifier_StartsInMiddleOfIdentifier_test():
+ eq_( 'foobar',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 3,
+ 'line_value': 'foobar'
+ } ) )
+
+
+def GetCursorIdentifier_LineEmpty_test():
+ eq_( '',
+ identifier_completer._GetCursorIdentifier(
+ {
+ 'column_num': 11,
+ 'line_value': ''
+ } ) )
diff --git a/python/ycm/completers/completer.py b/python/ycm/completers/completer.py
index ec5732e5..321c8e1c 100644
--- a/python/ycm/completers/completer.py
+++ b/python/ycm/completers/completer.py
@@ -265,19 +265,19 @@ class Completer( object ):
pass
- def OnBufferVisit( self ):
+ def OnBufferVisit( self, request_data ):
pass
- def OnBufferUnload( self, deleted_buffer_file ):
+ def OnBufferUnload( self, request_data ):
pass
- def OnInsertLeave( self ):
+ def OnInsertLeave( self, request_data ):
pass
- def OnVimLeave( self ):
+ def OnVimLeave( self, request_data ):
pass
@@ -285,7 +285,7 @@ class Completer( object ):
raise NotImplementedError( NO_USER_COMMANDS )
- def OnCurrentIdentifierFinished( self ):
+ def OnCurrentIdentifierFinished( self, request_data ):
pass
diff --git a/python/ycm/completers/cpp/clang_completer.py b/python/ycm/completers/cpp/clang_completer.py
index 73e90eef..98d9f5a0 100644
--- a/python/ycm/completers/cpp/clang_completer.py
+++ b/python/ycm/completers/cpp/clang_completer.py
@@ -245,8 +245,8 @@ class ClangCompleter( Completer ):
self.extra_parse_desired = False
- def OnBufferUnload( self, deleted_buffer_file ):
- self.completer.DeleteCachesForFileAsync( deleted_buffer_file )
+ def OnBufferUnload( self, request_data ):
+ self.completer.DeleteCachesForFileAsync( request_data[ 'unloaded_buffer' ] )
def DiagnosticsForCurrentFileReady( self ):
@@ -271,7 +271,7 @@ class ClangCompleter( Completer ):
self.parse_future = None
if self.extra_parse_desired:
- self.OnFileReadyToParse()
+ self.OnFileReadyToParse( request_data )
return self.last_prepared_diagnostics
diff --git a/python/ycm/completers/general/general_completer_store.py b/python/ycm/completers/general/general_completer_store.py
index fcfe5f76..3269bc97 100644
--- a/python/ycm/completers/general/general_completer_store.py
+++ b/python/ycm/completers/general/general_completer_store.py
@@ -100,29 +100,29 @@ class GeneralCompleterStore( Completer ):
completer.OnFileReadyToParse( request_data )
- def OnBufferVisit( self ):
+ def OnBufferVisit( self, request_data ):
for completer in self._all_completers:
- completer.OnBufferVisit()
+ completer.OnBufferVisit( request_data )
- def OnBufferUnload( self, deleted_buffer_file ):
+ def OnBufferUnload( self, request_data ):
for completer in self._all_completers:
- completer.OnBufferUnload( deleted_buffer_file )
+ completer.OnBufferUnload( request_data )
- def OnInsertLeave( self ):
+ def OnInsertLeave( self, request_data ):
for completer in self._all_completers:
- completer.OnInsertLeave()
+ completer.OnInsertLeave( request_data )
- def OnVimLeave( self ):
+ def OnVimLeave( self, request_data ):
for completer in self._all_completers:
- completer.OnVimLeave()
+ completer.OnVimLeave( request_data )
- def OnCurrentIdentifierFinished( self ):
+ def OnCurrentIdentifierFinished( self, request_data ):
for completer in self._all_completers:
- completer.OnCurrentIdentifierFinished()
+ completer.OnCurrentIdentifierFinished( request_data )
def GettingCompletions( self ):
diff --git a/python/ycm/completers/general/ultisnips_completer.py b/python/ycm/completers/general/ultisnips_completer.py
index d16b174f..a66c6fdc 100644
--- a/python/ycm/completers/general/ultisnips_completer.py
+++ b/python/ycm/completers/general/ultisnips_completer.py
@@ -51,7 +51,9 @@ class UltiSnipsCompleter( GeneralCompleter ):
return self._filtered_candidates if self._filtered_candidates else []
- def OnBufferVisit( self ):
+ def OnBufferVisit( self, request_data ):
+ # TODO: _GetCandidates should be called on the client and it should send
+ # the snippets to the server
self._candidates = _GetCandidates()
diff --git a/python/ycm/youcompleteme.py b/python/ycm/youcompleteme.py
index 1e8d8efb..a7f8e276 100644
--- a/python/ycm/youcompleteme.py
+++ b/python/ycm/youcompleteme.py
@@ -31,8 +31,27 @@ from ycm.completers.general.general_completer_store import GeneralCompleterStore
# TODO: Put the Request classes in separate files
-class CompletionRequest( object ):
+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 )
@@ -70,7 +89,7 @@ class CompletionRequest( object ):
-class CommandRequest( object ):
+class CommandRequest( BaseRequest ):
class ServerResponse( object ):
def __init__( self ):
pass
@@ -79,6 +98,8 @@ class CommandRequest( object ):
return True
def __init__( self, ycm_state, arguments, completer_target = None ):
+ super( CommandRequest, self ).__init__()
+
if not completer_target:
completer_target = 'filetpe_default'
@@ -95,9 +116,6 @@ class CommandRequest( object ):
self._completer.OnUserCommand( self._arguments,
_BuildRequestData() )
- def Done( self ):
- return True
-
def Response( self ):
# TODO: Call vimsupport.JumpToLocation if the user called a GoTo command...
@@ -117,6 +135,31 @@ class CommandRequest( object ):
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 ):
+ getattr( self._ycm_state.GetGeneralCompleter(),
+ 'On' + self._event_name )( self._request_data )
+
+ if self._ycm_state.FiletypeCompletionUsable():
+ getattr( self._ycm_state.GetFiletypeCompleter(),
+ 'On' + self._event_name )( self._request_data )
+
+
+
+def SendEventNotificationAsync( event_name, ycm_state, extra_data = None ):
+ event = EventNotification( event_name, ycm_state, extra_data )
+ event.Start()
+
class YouCompleteMe( object ):
def __init__( self, user_options ):
@@ -235,38 +278,36 @@ class YouCompleteMe( object ):
def OnFileReadyToParse( self ):
- self._gencomp.OnFileReadyToParse( _BuildRequestData() )
+ extra_data = {}
+ if self._user_options[ 'collect_identifiers_from_tags_files' ]:
+ extra_data[ 'tag_files' ] = _GetTagFiles()
- if self.FiletypeCompletionUsable():
- self.GetFiletypeCompleter().OnFileReadyToParse( _BuildRequestData() )
+ # TODO: make this work again
+ # if self._user_options[ 'seed_identifiers_with_syntax' ]:
+
+ SendEventNotificationAsync( 'FileReadyToParse', self, extra_data )
def OnBufferUnload( self, deleted_buffer_file ):
- self._gencomp.OnBufferUnload( deleted_buffer_file )
-
- if self.FiletypeCompletionUsable():
- self.GetFiletypeCompleter().OnBufferUnload( deleted_buffer_file )
+ SendEventNotificationAsync( 'BufferUnload',
+ self,
+ { 'unloaded_buffer': deleted_buffer_file } )
def OnBufferVisit( self ):
- self._gencomp.OnBufferVisit()
-
- if self.FiletypeCompletionUsable():
- self.GetFiletypeCompleter().OnBufferVisit()
+ SendEventNotificationAsync( 'BufferVisit', self )
def OnInsertLeave( self ):
- self._gencomp.OnInsertLeave()
-
- if self.FiletypeCompletionUsable():
- self.GetFiletypeCompleter().OnInsertLeave()
+ SendEventNotificationAsync( 'InsertLeave', self )
def OnVimLeave( self ):
- self._gencomp.OnVimLeave()
+ SendEventNotificationAsync( 'VimLeave', self )
- if self.FiletypeCompletionUsable():
- self.GetFiletypeCompleter().OnVimLeave()
+
+ def OnCurrentIdentifierFinished( self ):
+ SendEventNotificationAsync( 'CurrentIdentifierFinished', self )
def DiagnosticsForCurrentFileReady( self ):
@@ -292,13 +333,6 @@ class YouCompleteMe( object ):
return False
- def OnCurrentIdentifierFinished( self ):
- self._gencomp.OnCurrentIdentifierFinished()
-
- if self.FiletypeCompletionUsable():
- self.GetFiletypeCompleter().OnCurrentIdentifierFinished()
-
-
def DebugInfo( self ):
completers = set( self._filetype_completers.values() )
completers.add( self._gencomp )
@@ -370,3 +404,8 @@ def _ConvertCompletionDataToVimData( 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 ]