2012-08-04 20:46:54 -04:00
|
|
|
#!/usr/bin/env python
|
|
|
|
#
|
2014-01-13 14:08:43 -05:00
|
|
|
# Copyright (C) 2011, 2012, 2013 Google Inc.
|
2012-08-04 20:46:54 -04:00
|
|
|
#
|
|
|
|
# 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 abc
|
2014-01-28 19:31:32 -05:00
|
|
|
import threading
|
2013-12-02 19:36:53 -05:00
|
|
|
from collections import defaultdict
|
2013-10-28 15:17:18 -04:00
|
|
|
from ycm.utils import ToUtf8IfNeeded, ForceSemanticCompletion, RunningInsideVim
|
|
|
|
|
|
|
|
if RunningInsideVim():
|
|
|
|
from ycm_client_support import FilterAndSortCandidates
|
|
|
|
else:
|
|
|
|
from ycm_core import FilterAndSortCandidates
|
|
|
|
|
2013-06-29 14:53:16 -04:00
|
|
|
from ycm.completers.completer_utils import TriggersForFiletype
|
2014-01-02 17:22:27 -05:00
|
|
|
from ycm.server.responses import NoDiagnosticSupport
|
2013-02-10 22:55:05 -05:00
|
|
|
|
2013-02-25 09:14:01 -05:00
|
|
|
NO_USER_COMMANDS = 'This completer does not define any commands.'
|
2013-02-10 22:55:05 -05:00
|
|
|
|
2012-08-04 20:46:54 -04:00
|
|
|
class Completer( object ):
|
2013-02-12 23:01:22 -05:00
|
|
|
"""A base class for all Completers in YCM.
|
|
|
|
|
|
|
|
Here's several important things you need to know if you're writing a custom
|
2013-02-16 16:30:18 -05:00
|
|
|
Completer. The following are functions that the Vim part of YCM will be
|
|
|
|
calling on your Completer:
|
2013-02-12 23:01:22 -05:00
|
|
|
|
|
|
|
ShouldUseNow() is called with the start column of where a potential completion
|
2013-09-01 23:17:37 -04:00
|
|
|
string should start and the current line (string) the cursor is on. For
|
|
|
|
instance, if the user's input is 'foo.bar' and the cursor is on the 'r' in
|
|
|
|
'bar', start_column will be the 0-based index of 'b' in the line. Your
|
|
|
|
implementation of ShouldUseNow() should return True if your semantic completer
|
|
|
|
should be used and False otherwise.
|
2013-02-12 23:01:22 -05:00
|
|
|
|
|
|
|
This is important to get right. You want to return False if you can't provide
|
|
|
|
completions because then the identifier completer will kick in, and that's
|
|
|
|
better than nothing.
|
|
|
|
|
|
|
|
Note that it's HIGHLY likely that you want to override the ShouldUseNowInner()
|
2013-02-16 16:26:56 -05:00
|
|
|
function instead of ShouldUseNow() directly (although chances are that you
|
|
|
|
probably won't have any need to override either). ShouldUseNow() will call
|
|
|
|
your *Inner version of the function and will also make sure that the
|
|
|
|
completion cache is taken into account. You'll see this pattern repeated
|
|
|
|
throughout the Completer API; YCM calls the "main" version of the function and
|
|
|
|
that function calls the *Inner version while taking into account the cache.
|
2013-02-12 23:01:22 -05:00
|
|
|
|
|
|
|
The cache is important and is a nice performance boost. When the user types in
|
|
|
|
"foo.", your completer will return a list of all member functions and
|
|
|
|
variables that can be accessed on the "foo" object. The Completer API caches
|
|
|
|
this list. The user will then continue typing, let's say "foo.ba". On every
|
|
|
|
keystroke after the dot, the Completer API will take the cache into account
|
|
|
|
and will NOT re-query your completer but will in fact provide fuzzy-search on
|
|
|
|
the candidate strings that were stored in the cache.
|
|
|
|
|
2013-09-30 16:40:25 -04:00
|
|
|
ComputeCandidates() is the main entry point when the user types. For
|
2013-02-12 23:01:22 -05:00
|
|
|
"foo.bar", the user query is "bar" and completions matching this string should
|
2013-09-30 16:40:25 -04:00
|
|
|
be shown. It should return the list of candidates. The format of the result
|
|
|
|
can be a list of strings or a more complicated list of dictionaries. Use
|
|
|
|
ycm.server.responses.BuildCompletionData to build the detailed response. See
|
|
|
|
clang_completer.py to see how its used in practice.
|
2013-02-12 23:01:22 -05:00
|
|
|
|
2013-09-30 16:40:25 -04:00
|
|
|
Again, you probably want to override ComputeCandidatesInner().
|
2013-02-12 23:01:22 -05:00
|
|
|
|
|
|
|
You also need to implement the SupportedFiletypes() function which should
|
|
|
|
return a list of strings, where the strings are Vim filetypes your completer
|
|
|
|
supports.
|
|
|
|
|
2013-10-18 15:35:40 -04:00
|
|
|
clang_completer.py is a good example of a "complicated" completer. A good
|
2013-09-30 16:40:25 -04:00
|
|
|
example of a simple completer is ultisnips_completer.py.
|
2013-02-12 23:01:22 -05:00
|
|
|
|
|
|
|
The On* functions are provided for your convenience. They are called when
|
|
|
|
their specific events occur. For instance, the identifier completer collects
|
|
|
|
all the identifiers in the file in OnFileReadyToParse() which gets called when
|
|
|
|
the user stops typing for 2 seconds (Vim's CursorHold and CursorHoldI events).
|
2013-02-25 09:14:01 -05:00
|
|
|
|
|
|
|
One special function is OnUserCommand. It is called when the user uses the
|
|
|
|
command :YcmCompleter and is passed all extra arguments used on command
|
|
|
|
invocation (e.g. OnUserCommand(['first argument', 'second'])). This can be
|
2013-10-18 15:35:40 -04:00
|
|
|
used for completer-specific commands such as reloading external configuration.
|
2013-02-25 09:14:01 -05:00
|
|
|
When the command is called with no arguments you should print a short summary
|
|
|
|
of the supported commands or point the user to the help section where this
|
2013-09-23 17:33:14 -04:00
|
|
|
information can be found.
|
|
|
|
|
|
|
|
Override the Shutdown() member function if your Completer subclass needs to do
|
|
|
|
custom cleanup logic on server shutdown."""
|
2012-08-04 20:46:54 -04:00
|
|
|
|
2013-02-12 23:01:22 -05:00
|
|
|
__metaclass__ = abc.ABCMeta
|
2012-08-04 20:46:54 -04:00
|
|
|
|
2013-09-02 17:45:53 -04:00
|
|
|
def __init__( self, user_options ):
|
|
|
|
self.user_options = user_options
|
|
|
|
self.min_num_chars = user_options[ 'min_num_of_chars_for_completion' ]
|
2013-12-02 19:36:53 -05:00
|
|
|
self.triggers_for_filetype = (
|
|
|
|
TriggersForFiletype( user_options[ 'semantic_triggers' ] )
|
|
|
|
if user_options[ 'auto_trigger' ] else defaultdict( set ) )
|
2014-01-28 19:31:32 -05:00
|
|
|
self._completions_cache = CompletionsCache()
|
2013-02-10 22:55:05 -05:00
|
|
|
|
|
|
|
|
2013-02-12 23:01:22 -05:00
|
|
|
# It's highly likely you DON'T want to override this function but the *Inner
|
|
|
|
# version of it.
|
2013-09-06 02:43:14 -04:00
|
|
|
def ShouldUseNow( self, request_data ):
|
2014-01-28 19:31:32 -05:00
|
|
|
if not self.ShouldUseNowInner( request_data ):
|
|
|
|
self._completions_cache.Invalidate()
|
|
|
|
return False
|
|
|
|
|
|
|
|
# We have to do the cache valid check and get the completions as part of one
|
|
|
|
# call because we have to ensure a different thread doesn't change the cache
|
|
|
|
# data.
|
|
|
|
cache_completions = self._completions_cache.GetCompletionsIfCacheValid(
|
|
|
|
request_data[ 'line_num' ],
|
|
|
|
request_data[ 'start_column' ] )
|
2013-02-12 00:46:06 -05:00
|
|
|
|
2014-01-28 19:31:32 -05:00
|
|
|
# If None, then the cache isn't valid and we know we should return true
|
|
|
|
if cache_completions is None:
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
previous_results_were_valid = bool( cache_completions )
|
|
|
|
return previous_results_were_valid
|
2013-02-10 22:55:05 -05:00
|
|
|
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def ShouldUseNowInner( self, request_data ):
|
|
|
|
current_line = request_data[ 'line_value' ]
|
|
|
|
start_column = request_data[ 'start_column' ]
|
2013-09-01 23:17:37 -04:00
|
|
|
line_length = len( current_line )
|
2013-02-12 00:46:06 -05:00
|
|
|
if not line_length or start_column - 1 >= line_length:
|
|
|
|
return False
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
filetype = self._CurrentFiletype( request_data[ 'filetypes' ] )
|
2013-02-12 00:46:06 -05:00
|
|
|
triggers = self.triggers_for_filetype[ filetype ]
|
|
|
|
|
|
|
|
for trigger in triggers:
|
|
|
|
index = -1
|
|
|
|
trigger_length = len( trigger )
|
|
|
|
while True:
|
|
|
|
line_index = start_column + index
|
2013-09-01 23:17:37 -04:00
|
|
|
if line_index < 0 or current_line[ line_index ] != trigger[ index ]:
|
2013-02-12 00:46:06 -05:00
|
|
|
break
|
|
|
|
|
|
|
|
if abs( index ) == trigger_length:
|
|
|
|
return True
|
|
|
|
index -= 1
|
|
|
|
return False
|
2013-02-10 22:55:05 -05:00
|
|
|
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def QueryLengthAboveMinThreshold( self, request_data ):
|
|
|
|
query_length = request_data[ 'column_num' ] - request_data[ 'start_column' ]
|
2013-09-02 17:45:53 -04:00
|
|
|
return query_length >= self.min_num_chars
|
2013-04-11 07:42:36 -04:00
|
|
|
|
2013-09-01 23:17:37 -04:00
|
|
|
|
2013-02-12 23:01:22 -05:00
|
|
|
# It's highly likely you DON'T want to override this function but the *Inner
|
|
|
|
# version of it.
|
2013-09-30 16:40:25 -04:00
|
|
|
def ComputeCandidates( self, request_data ):
|
2013-10-07 16:09:34 -04:00
|
|
|
if ( not ForceSemanticCompletion( request_data ) and
|
|
|
|
not self.ShouldUseNow( request_data ) ):
|
2013-09-30 16:40:25 -04:00
|
|
|
return []
|
2013-04-09 22:25:53 -04:00
|
|
|
|
2013-10-09 23:20:34 -04:00
|
|
|
candidates = self._GetCandidatesFromSubclass( request_data )
|
|
|
|
if request_data[ 'query' ]:
|
|
|
|
candidates = self.FilterAndSortCandidates( candidates,
|
|
|
|
request_data[ 'query' ] )
|
|
|
|
return candidates
|
|
|
|
|
|
|
|
|
|
|
|
def _GetCandidatesFromSubclass( self, request_data ):
|
2014-01-28 19:31:32 -05:00
|
|
|
cache_completions = self._completions_cache.GetCompletionsIfCacheValid(
|
|
|
|
request_data[ 'line_num' ],
|
|
|
|
request_data[ 'start_column' ] )
|
|
|
|
|
|
|
|
if cache_completions:
|
|
|
|
return cache_completions
|
2013-02-10 22:55:05 -05:00
|
|
|
else:
|
2014-01-28 19:31:32 -05:00
|
|
|
raw_completions = self.ComputeCandidatesInner( request_data )
|
|
|
|
self._completions_cache.Update(
|
|
|
|
request_data[ 'line_num' ],
|
|
|
|
request_data[ 'start_column' ],
|
|
|
|
raw_completions )
|
|
|
|
return raw_completions
|
2013-09-30 16:40:25 -04:00
|
|
|
|
|
|
|
|
|
|
|
def ComputeCandidatesInner( self, request_data ):
|
|
|
|
pass
|
2013-02-10 22:55:05 -05:00
|
|
|
|
|
|
|
|
2013-05-08 16:38:56 -04:00
|
|
|
def DefinedSubcommands( self ):
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def UserCommandsHelpMessage( self ):
|
2013-05-09 23:28:04 -04:00
|
|
|
subcommands = self.DefinedSubcommands()
|
|
|
|
if subcommands:
|
2013-09-06 02:43:14 -04:00
|
|
|
return ( 'Supported commands are:\n' +
|
|
|
|
'\n'.join( subcommands ) +
|
|
|
|
'\nSee the docs for information on what they do.' )
|
2013-05-08 16:38:56 -04:00
|
|
|
else:
|
2013-09-27 16:52:04 -04:00
|
|
|
return 'This Completer has no supported subcommands.'
|
2013-05-09 23:28:04 -04:00
|
|
|
|
2013-05-08 16:38:56 -04:00
|
|
|
|
2013-02-10 22:55:05 -05:00
|
|
|
def FilterAndSortCandidates( self, candidates, query ):
|
|
|
|
if not candidates:
|
|
|
|
return []
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
# We need to handle both an omni_completer style completer and a server
|
|
|
|
# style completer
|
|
|
|
if 'words' in candidates:
|
|
|
|
candidates = candidates[ 'words' ]
|
|
|
|
|
|
|
|
sort_property = ''
|
|
|
|
if 'word' in candidates[ 0 ]:
|
|
|
|
sort_property = 'word'
|
|
|
|
elif 'insertion_text' in candidates[ 0 ]:
|
|
|
|
sort_property = 'insertion_text'
|
2013-02-10 22:55:05 -05:00
|
|
|
|
2013-10-28 15:17:18 -04:00
|
|
|
matches = FilterAndSortCandidates( candidates,
|
|
|
|
sort_property,
|
|
|
|
ToUtf8IfNeeded( query ) )
|
2013-04-11 07:42:36 -04:00
|
|
|
|
|
|
|
return matches
|
2013-02-10 22:55:05 -05:00
|
|
|
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def OnFileReadyToParse( self, request_data ):
|
2012-08-04 20:46:54 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-09-07 20:39:52 -04:00
|
|
|
def OnBufferVisit( self, request_data ):
|
2012-08-04 20:46:54 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-09-07 20:39:52 -04:00
|
|
|
def OnBufferUnload( self, request_data ):
|
2013-03-14 23:39:44 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-09-07 20:39:52 -04:00
|
|
|
def OnInsertLeave( self, request_data ):
|
2012-08-04 20:46:54 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def OnUserCommand( self, arguments, request_data ):
|
|
|
|
raise NotImplementedError( NO_USER_COMMANDS )
|
2013-02-25 09:14:01 -05:00
|
|
|
|
|
|
|
|
2013-09-07 20:39:52 -04:00
|
|
|
def OnCurrentIdentifierFinished( self, request_data ):
|
2012-08-04 20:46:54 -04:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-10-03 13:14:31 -04:00
|
|
|
def GetDiagnosticsForCurrentFile( self, request_data ):
|
2014-01-02 17:22:27 -05:00
|
|
|
raise NoDiagnosticSupport
|
2012-08-04 20:46:54 -04:00
|
|
|
|
|
|
|
|
2013-10-22 16:46:24 -04:00
|
|
|
def GetDetailedDiagnostic( self, request_data ):
|
2014-01-02 17:22:27 -05:00
|
|
|
raise NoDiagnosticSupport
|
2012-08-15 22:39:03 -04:00
|
|
|
|
2013-04-25 18:36:56 -04:00
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def _CurrentFiletype( self, filetypes ):
|
2013-03-03 00:52:56 -05:00
|
|
|
supported = self.SupportedFiletypes()
|
|
|
|
|
|
|
|
for filetype in filetypes:
|
|
|
|
if filetype in supported:
|
|
|
|
return filetype
|
|
|
|
|
|
|
|
return filetypes[0]
|
|
|
|
|
|
|
|
|
2012-08-04 20:46:54 -04:00
|
|
|
@abc.abstractmethod
|
|
|
|
def SupportedFiletypes( self ):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-09-27 20:13:43 -04:00
|
|
|
def DebugInfo( self, request_data ):
|
2013-01-26 14:44:42 -05:00
|
|
|
return ''
|
2013-02-12 00:46:06 -05:00
|
|
|
|
|
|
|
|
2013-09-23 17:33:14 -04:00
|
|
|
def Shutdown( self ):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2013-02-12 23:01:22 -05:00
|
|
|
class CompletionsCache( object ):
|
|
|
|
def __init__( self ):
|
2014-01-28 19:31:32 -05:00
|
|
|
self._access_lock = threading.Lock()
|
|
|
|
self.Invalidate()
|
|
|
|
|
|
|
|
|
|
|
|
def Invalidate( self ):
|
|
|
|
with self._access_lock:
|
|
|
|
self._line = -1
|
|
|
|
self._column = -1
|
|
|
|
self._completions = []
|
|
|
|
|
|
|
|
|
|
|
|
def Update( self, line, column, completions ):
|
|
|
|
with self._access_lock:
|
|
|
|
self._line = line
|
|
|
|
self._column = column
|
|
|
|
self._completions = completions
|
|
|
|
|
|
|
|
|
|
|
|
def GetCompletions( self ):
|
|
|
|
with self._access_lock:
|
|
|
|
return self._completions
|
|
|
|
|
|
|
|
|
|
|
|
def GetCompletionsIfCacheValid( self, current_line, start_column ):
|
|
|
|
with self._access_lock:
|
|
|
|
if not self._CacheValidNoLock( current_line, start_column ):
|
|
|
|
return None
|
|
|
|
return self._completions
|
2013-02-12 23:01:22 -05:00
|
|
|
|
|
|
|
|
2013-09-06 02:43:14 -04:00
|
|
|
def CacheValid( self, current_line, start_column ):
|
2014-01-28 19:31:32 -05:00
|
|
|
with self._access_lock:
|
|
|
|
return self._CacheValidNoLock( current_line, start_column )
|
|
|
|
|
2013-02-12 23:01:22 -05:00
|
|
|
|
2014-01-28 19:31:32 -05:00
|
|
|
def _CacheValidNoLock( self, current_line, start_column ):
|
|
|
|
return current_line == self._line and start_column == self._column
|
2013-02-12 23:01:22 -05:00
|
|
|
|