Initial version of omnifunc-based omni_completer

Still a work in progress (needs better triggering and bug fixes)
This commit is contained in:
Strahinja Val Markovic 2013-02-10 19:55:05 -08:00
parent 051fc85be4
commit 786e6182ff
12 changed files with 343 additions and 26 deletions

View File

@ -191,7 +191,7 @@ function! s:SetCompleteFunc()
let &completefunc = 'youcompleteme#Complete' let &completefunc = 'youcompleteme#Complete'
let &l:completefunc = 'youcompleteme#Complete' let &l:completefunc = 'youcompleteme#Complete'
if pyeval( 'ycm_state.FiletypeCompletionEnabled()' ) if pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
let &omnifunc = 'youcompleteme#OmniComplete' let &omnifunc = 'youcompleteme#OmniComplete'
let &l:omnifunc = 'youcompleteme#OmniComplete' let &l:omnifunc = 'youcompleteme#OmniComplete'
endif endif
@ -299,7 +299,7 @@ endfunction
function! s:UpdateDiagnosticNotifications() function! s:UpdateDiagnosticNotifications()
if get( g:, 'loaded_syntastic_plugin', 0 ) && if get( g:, 'loaded_syntastic_plugin', 0 ) &&
\ pyeval( 'ycm_state.FiletypeCompletionEnabled()' ) && \ pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' ) &&
\ pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' ) \ pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' )
SyntasticCheck SyntasticCheck
endif endif
@ -408,6 +408,8 @@ function! youcompleteme#Complete( findstart, base )
return -2 return -2
endif endif
" TODO: make this a function-local variable instead of a script-local one
let s:completion_start_column = pyeval( 'ycm.CompletionStartColumn()' ) let s:completion_start_column = pyeval( 'ycm.CompletionStartColumn()' )
let s:should_use_filetype_completion = let s:should_use_filetype_completion =
\ pyeval( 'ycm_state.ShouldUseFiletypeCompleter(' . \ pyeval( 'ycm_state.ShouldUseFiletypeCompleter(' .
@ -465,7 +467,7 @@ command! YcmDebugInfo call s:DebugInfo()
function! s:ForceCompile() function! s:ForceCompile()
if !pyeval( 'ycm_state.FiletypeCompletionEnabled()' ) if !pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
echom "Filetype completion not supported for current file, " echom "Filetype completion not supported for current file, "
\ . "cannot force recompilation." \ . "cannot force recompilation."
endif endif

View File

@ -55,6 +55,7 @@ extern const unsigned int MIN_ASYNC_THREADS;
namespace { namespace {
// TODO: replace this with ResultAnd from Result.h
struct CompletionDataAndResult { struct CompletionDataAndResult {
CompletionDataAndResult( const CompletionData *completion_data, CompletionDataAndResult( const CompletionData *completion_data,
const Result &result ) const Result &result )
@ -419,7 +420,7 @@ std::vector< CompletionData > ClangCompleter::SortCandidatesForQuery(
if ( result.IsSubsequence() ) { if ( result.IsSubsequence() ) {
CompletionDataAndResult data_and_result( &completion_datas[ i ], result ); CompletionDataAndResult data_and_result( &completion_datas[ i ], result );
data_and_results.push_back( data_and_result ); data_and_results.push_back( boost::move( data_and_result ) );
} }
} }

96
cpp/ycm/PythonSupport.cpp Normal file
View File

@ -0,0 +1,96 @@
// Copyright (C) 2011, 2012, 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/>.
#include "PythonSupport.h"
#include "standard.h"
#include "Result.h"
#include "Candidate.h"
#include "CandidateRepository.h"
#include <vector>
using boost::python::len;
using boost::python::extract;
using boost::python::object;
typedef boost::python::list pylist;
namespace YouCompleteMe {
namespace {
std::vector< const Candidate * > CandidatesFromObjectList(
const pylist &candidates,
const std::string &candidate_property ) {
int num_candidates = len( candidates );
std::vector< std::string > candidate_strings;
candidate_strings.reserve( num_candidates );
for ( int i = 0; i < num_candidates; ++i ) {
if ( candidate_property.empty() ) {
candidate_strings.push_back( extract< std::string >( candidates[ i ] ) );
} else {
object holder = extract< object >( candidates[ i ] );
candidate_strings.push_back( extract< std::string >(
holder[ candidate_property.c_str() ] ) );
}
}
return CandidateRepository::Instance().GetCandidatesForStrings(
candidate_strings );
}
} // unnamed namespace
boost::python::list FilterAndSortCandidates(
const boost::python::list &candidates,
const std::string &candidate_property,
const std::string &query ) {
pylist filtered_candidates;
if ( query.empty() )
return filtered_candidates;
std::vector< const Candidate * > repository_candidates =
CandidatesFromObjectList( candidates, candidate_property );
Bitset query_bitset = LetterBitsetFromString( query );
int num_candidates = len( candidates );
std::vector< ResultAnd< int > > object_and_results;
for ( int i = 0; i < num_candidates; ++i ) {
const Candidate *candidate = repository_candidates[ i ];
if ( !candidate->MatchesQueryBitset( query_bitset ) )
continue;
Result result = candidate->QueryMatchResult( query );
if ( result.IsSubsequence() ) {
ResultAnd< int > object_and_result( i, result );
object_and_results.push_back( boost::move( object_and_result ) );
}
}
std::sort( object_and_results.begin(), object_and_results.end() );
foreach ( const ResultAnd< int > &object_and_result,
object_and_results ) {
filtered_candidates.append( candidates[ object_and_result.extra_object_ ] );
}
return filtered_candidates;
}
} // namespace YouCompleteMe

37
cpp/ycm/PythonSupport.h Normal file
View File

@ -0,0 +1,37 @@
// Copyright (C) 2011, 2012, 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/>.
#ifndef PYTHONSUPPORT_H_KWGFEX0V
#define PYTHONSUPPORT_H_KWGFEX0V
#include <boost/python.hpp>
namespace YouCompleteMe {
// Given a list of python objects (that represent completion candidates) in a
// python list |candidates|, a |candidate_property| on which to filter and sort
// the candidates and a user query, returns a new sorted python list with the
// original objects that survived the filtering.
boost::python::list FilterAndSortCandidates(
const boost::python::list &candidates,
const std::string &candidate_property,
const std::string &query );
} // namespace YouCompleteMe
#endif /* end of include guard: PYTHONSUPPORT_H_KWGFEX0V */

View File

@ -86,6 +86,19 @@ private:
}; };
template< class T >
struct ResultAnd {
ResultAnd( T extra_object, const Result &result )
: extra_object_( extra_object ), result_( result ) {}
bool operator< ( const ResultAnd &other ) const {
return result_ < other.result_;
}
T extra_object_;
Result result_;
};
} // namespace YouCompleteMe } // namespace YouCompleteMe
#endif /* end of include guard: RESULT_H_CZYD2SGN */ #endif /* end of include guard: RESULT_H_CZYD2SGN */

View File

@ -16,6 +16,7 @@
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. // along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#include "IdentifierCompleter.h" #include "IdentifierCompleter.h"
#include "PythonSupport.h"
#include "Future.h" #include "Future.h"
#ifdef USE_CLANG_COMPLETER #ifdef USE_CLANG_COMPLETER
@ -47,6 +48,7 @@ BOOST_PYTHON_MODULE(ycm_core)
using namespace YouCompleteMe; using namespace YouCompleteMe;
def( "HasClangSupport", HasClangSupport ); def( "HasClangSupport", HasClangSupport );
def( "FilterAndSortCandidates", FilterAndSortCandidates );
class_< IdentifierCompleter, boost::noncopyable >( "IdentifierCompleter" ) class_< IdentifierCompleter, boost::noncopyable >( "IdentifierCompleter" )
.def( "EnableThreading", &IdentifierCompleter::EnableThreading ) .def( "EnableThreading", &IdentifierCompleter::EnableThreading )

View File

@ -30,6 +30,7 @@ MIN_NUM_CHARS = int( vimsupport.GetVariableValue(
class IdentifierCompleter( Completer ): class IdentifierCompleter( Completer ):
def __init__( self ): def __init__( self ):
super( IdentifierCompleter, self ).__init__()
self.completer = ycm_core.IdentifierCompleter() self.completer = ycm_core.IdentifierCompleter()
self.completer.EnableThreading() self.completer.EnableThreading()

View File

@ -0,0 +1,76 @@
#!/usr/bin/env python
#
# Copyright (C) 2011, 2012, 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 vimsupport
from completers.completer import Completer
class OmniCompleter( Completer ):
def __init__( self ):
super( OmniCompleter, self ).__init__()
self.omnifunc = None
self.stored_candidates = None
def SupportedFiletypes( self ):
return []
def ShouldUseNowInner( self, start_column ):
line = vim.current.line
previous_char_index = start_column - 1
if ( not len( line ) or
previous_char_index < 0 or
previous_char_index >= len( line ) ):
return False
if line[ previous_char_index ] == '.':
return True
return False
def CandidatesForQueryAsyncInner( self, query ):
if not self.omnifunc:
self.stored_candidates = None
return
return_value = vim.eval( self.omnifunc + '(1,"")' )
if return_value < 0:
self.stored_candidates = None
return
omnifunc_call = [ self.omnifunc,
"(0,'",
vimsupport.EscapeForVim( query ),
"')" ]
self.stored_candidates = vim.eval( ''.join( omnifunc_call ) )
def AsyncCandidateRequestReadyInner( self ):
return True
def OnFileReadyToParse( self ):
self.omnifunc = vim.eval( '&omnifunc' )
def CandidatesFromStoredRequestInner( self ):
return self.stored_candidates

View File

@ -18,6 +18,23 @@
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
import abc import abc
import vim
import vimsupport
import ycm_core
class CompletionsCache( object ):
def __init__( self ):
self.line = -1
self.column = -1
self.raw_completions = []
self.filtered_completions = []
def CacheValid( self ):
completion_line, _ = vimsupport.CurrentLineAndColumn()
completion_column = int( vim.eval( "s:completion_start_column" ) )
return completion_line == self.line and completion_column == self.column
class Completer( object ): class Completer( object ):
@ -26,9 +43,57 @@ class Completer( object ):
def __init__( self ): def __init__( self ):
self.completions_future = None self.completions_future = None
self.completions_cache = None
def ShouldUseNow( self, start_column ):
inner_says_yes = self.ShouldUseNowInner( start_column )
previous_results_were_empty = ( self.completions_cache and
not self.completions_cache.raw_completions )
return inner_says_yes and not previous_results_were_empty
def ShouldUseNowInner( self, start_column ):
pass
def CandidatesForQueryAsync( self, query ):
if query and self.completions_cache and self.completions_cache.CacheValid():
self.completions_cache.filtered_completions = (
self.FilterAndSortCandidates(
self.completions_cache.raw_completions,
query ) )
else:
self.completions_cache = None
self.CandidatesForQueryAsyncInner( query )
def FilterAndSortCandidates( self, candidates, query ):
if not candidates:
return []
if hasattr( candidates, 'words' ):
candidates = candidates.words
items_are_objects = 'word' in candidates[ 0 ]
return ycm_core.FilterAndSortCandidates(
candidates,
'word' if items_are_objects else '',
query )
def CandidatesForQueryAsyncInner( self, query ):
pass
def AsyncCandidateRequestReady( self ): def AsyncCandidateRequestReady( self ):
if self.completions_cache:
return True
else:
return self.AsyncCandidateRequestReadyInner()
def AsyncCandidateRequestReadyInner( self ):
if not self.completions_future: if not self.completions_future:
# We return True so that the caller can extract the default value from the # We return True so that the caller can extract the default value from the
# future # future
@ -37,6 +102,18 @@ class Completer( object ):
def CandidatesFromStoredRequest( self ): def CandidatesFromStoredRequest( self ):
if self.completions_cache:
return self.completions_cache.filtered_completions
else:
self.completions_cache = CompletionsCache()
self.completions_cache.raw_completions = self.CandidatesFromStoredRequestInner()
self.completions_cache.line, _ = vimsupport.CurrentLineAndColumn()
self.completions_cache.column = int(
vim.eval( "s:completion_start_column" ) )
return self.completions_cache.raw_completions
def CandidatesFromStoredRequestInner( self ):
if not self.completions_future: if not self.completions_future:
return [] return []
return self.completions_future.GetResults() return self.completions_future.GetResults()
@ -90,10 +167,5 @@ class Completer( object ):
pass pass
@abc.abstractmethod
def ShouldUseNow( self, start_column ):
pass
def DebugInfo( self ): def DebugInfo( self ):
return '' return ''

View File

@ -31,6 +31,7 @@ MAX_DIAGNOSTICS_TO_DISPLAY = int( vimsupport.GetVariableValue(
class ClangCompleter( Completer ): class ClangCompleter( Completer ):
def __init__( self ): def __init__( self ):
super( ClangCompleter, self ).__init__()
self.completer = ycm_core.ClangCompleter() self.completer = ycm_core.ClangCompleter()
self.completer.EnableThreading() self.completer.EnableThreading()
self.contents_holder = [] self.contents_holder = []
@ -101,6 +102,7 @@ class ClangCompleter( Completer ):
files = self.GetUnsavedFilesVector() files = self.GetUnsavedFilesVector()
line, _ = vim.current.window.cursor line, _ = vim.current.window.cursor
# TODO: this should be a function parameter
column = int( vim.eval( "s:completion_start_column" ) ) + 1 column = int( vim.eval( "s:completion_start_column" ) ) + 1
self.completions_future = ( self.completions_future = (
self.completer.CandidatesForQueryAndLocationInFileAsync( self.completer.CandidatesForQueryAndLocationInFileAsync(

View File

@ -55,12 +55,12 @@ def NumLinesInBuffer( buffer ):
def PostVimMessage( message ): def PostVimMessage( message ):
vim.command( 'echohl WarningMsg | echomsg "{0}" | echohl None' vim.command( "echohl WarningMsg | echomsg '{0}' | echohl None"
.format( message.replace( '"', '\\"' ) ) ) .format( EscapeForVim( message ) ) )
def EchoText( text ): def EchoText( text ):
vim.command( "echom '{0}'".format( text.replace( "'", r"''") ) ) vim.command( "echom '{0}'".format( EscapeForVim( text ) ) )
def EscapeForVim( text ): def EscapeForVim( text ):

View File

@ -34,6 +34,8 @@ except ImportError, e:
os.path.dirname( os.path.abspath( __file__ ) ), str( e ) ) ) os.path.dirname( os.path.abspath( __file__ ) ), str( e ) ) )
from completers.all.identifier_completer import IdentifierCompleter from completers.all.identifier_completer import IdentifierCompleter
from completers.all.omni_completer import OmniCompleter
FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval( FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval(
'g:ycm_filetype_specific_completion_to_disable' ) 'g:ycm_filetype_specific_completion_to_disable' )
@ -42,6 +44,7 @@ FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval(
class YouCompleteMe( object ): class YouCompleteMe( object ):
def __init__( self ): def __init__( self ):
self.identcomp = IdentifierCompleter() self.identcomp = IdentifierCompleter()
self.omnicomp = OmniCompleter()
self.filetype_completers = {} self.filetype_completers = {}
@ -56,7 +59,6 @@ class YouCompleteMe( object ):
completer = self.GetFiletypeCompleterForFiletype( filetype ) completer = self.GetFiletypeCompleterForFiletype( filetype )
if completer: if completer:
return completer return completer
return None return None
@ -79,6 +81,8 @@ class YouCompleteMe( object ):
completer = module.GetCompleter() completer = module.GetCompleter()
if completer: if completer:
supported_filetypes.extend( completer.SupportedFiletypes() ) supported_filetypes.extend( completer.SupportedFiletypes() )
else:
completer = self.omnicomp
for supported_filetype in supported_filetypes: for supported_filetype in supported_filetypes:
self.filetype_completers[ supported_filetype ] = completer self.filetype_completers[ supported_filetype ] = completer
@ -90,58 +94,64 @@ class YouCompleteMe( object ):
def ShouldUseFiletypeCompleter( self, start_column ): def ShouldUseFiletypeCompleter( self, start_column ):
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShouldUseNow( return self.GetFiletypeCompleter().ShouldUseNow(
start_column ) start_column )
return False return False
def NativeFiletypeCompletionAvailable( self ):
completer = self.GetFiletypeCompleter()
return bool( completer ) and completer is not self.omnicomp
def FiletypeCompletionAvailable( self ): def FiletypeCompletionAvailable( self ):
return bool( self.GetFiletypeCompleter() ) return bool( self.GetFiletypeCompleter() )
def FiletypeCompletionEnabled( self ): def NativeFiletypeCompletionUsable( self ):
filetypes = vimsupport.CurrentFiletypes() return ( _CurrentFiletypeCompletionEnabled() and
filetype_disabled = all([ x in FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE self.NativeFiletypeCompletionAvailable() )
for x in filetypes ])
return ( not filetype_disabled and
def FiletypeCompletionUsable( self ):
return ( _CurrentFiletypeCompletionEnabled() and
self.FiletypeCompletionAvailable() ) self.FiletypeCompletionAvailable() )
def OnFileReadyToParse( self ): def OnFileReadyToParse( self ):
self.identcomp.OnFileReadyToParse() self.identcomp.OnFileReadyToParse()
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnFileReadyToParse() self.GetFiletypeCompleter().OnFileReadyToParse()
def OnInsertLeave( self ): def OnInsertLeave( self ):
self.identcomp.OnInsertLeave() self.identcomp.OnInsertLeave()
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnInsertLeave() self.GetFiletypeCompleter().OnInsertLeave()
def DiagnosticsForCurrentFileReady( self ): def DiagnosticsForCurrentFileReady( self ):
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady() return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
return False return False
def GetDiagnosticsForCurrentFile( self ): def GetDiagnosticsForCurrentFile( self ):
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile() return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
return [] return []
def ShowDetailedDiagnostic( self ): def ShowDetailedDiagnostic( self ):
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().ShowDetailedDiagnostic() return self.GetFiletypeCompleter().ShowDetailedDiagnostic()
def GettingCompletions( self ): def GettingCompletions( self ):
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
return self.GetFiletypeCompleter().GettingCompletions() return self.GetFiletypeCompleter().GettingCompletions()
return False return False
@ -149,7 +159,7 @@ class YouCompleteMe( object ):
def OnCurrentIdentifierFinished( self ): def OnCurrentIdentifierFinished( self ):
self.identcomp.OnCurrentIdentifierFinished() self.identcomp.OnCurrentIdentifierFinished()
if self.FiletypeCompletionEnabled(): if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnCurrentIdentifierFinished() self.GetFiletypeCompleter().OnCurrentIdentifierFinished()
@ -174,6 +184,11 @@ class YouCompleteMe( object ):
return '\n'.join( output ) return '\n'.join( output )
def _CurrentFiletypeCompletionEnabled():
filetypes = vimsupport.CurrentFiletypes()
return not all([ x in FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE
for x in filetypes ])
def _PathToCompletersFolder(): def _PathToCompletersFolder():
dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) ) dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )