Completion suggestions are now fetched async

This commit is contained in:
Strahinja Val Markovic 2012-05-05 23:48:22 -07:00
parent 7468a5a21c
commit 7cf580a447
9 changed files with 343 additions and 33 deletions

View File

@ -102,13 +102,27 @@ function! youcompleteme#Complete(findstart, base)
return start_column return start_column
else else
let s:old_cursor_text = a:base let s:old_cursor_text = a:base
let results = []
if strlen( a:base ) < g:ycm_min_num_of_chars_for_completion if strlen( a:base ) < g:ycm_min_num_of_chars_for_completion
return results return []
endif endif
py csystem.CandidatesForQueryAsync( vim.eval('a:base') )
let l:results_ready = 0
while !l:results_ready
py << EOF py << EOF
results = csystem.CompletionCandidatesForQuery( vim.eval('a:base') ) results_ready = csystem.AsyncCandidateRequestReady()
if results_ready:
vim.command( 'let l:results_ready = 1' )
EOF
if complete_check()
return { 'words' : [], 'refresh' : 'always'}
endif
endwhile
let l:results = []
py << EOF
results = csystem.CandidatesFromStoredRequest()
if results: if results:
vim.command( 'let l:results = ' + str( results ) ) vim.command( 'let l:results = ' + str( results ) )
EOF EOF
@ -117,10 +131,8 @@ EOF
" keystroke. The problem is still present in vim 7.3.390 but is fixed in " keystroke. The problem is still present in vim 7.3.390 but is fixed in
" 7.3.475. It's possible that patch 404 was the one that fixed this issue, " 7.3.475. It's possible that patch 404 was the one that fixed this issue,
" but I haven't tested this assumption. " but I haven't tested this assumption.
let dict = { 'words' : results }
" A bug in vim causes the '.' register to break when we use set this... sigh " A bug in vim causes the '.' register to break when we use set this... sigh
let dict.refresh = 'always' return { 'words' : l:results, 'refresh' : 'always'}
return dict
endif endif
endfunction endfunction

View File

@ -86,7 +86,7 @@ endif()
if( CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG ) if( CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG )
# We want all warnings, and warnings should be treated as errors # We want all warnings, and warnings should be treated as errors
#add_definitions( -Wall -pedantic -Werror ) # TODO: -Wextra?
add_definitions( -Wall -Werror ) add_definitions( -Wall -Werror )
endif() endif()

View File

@ -19,14 +19,42 @@
#include "Completer.h" #include "Completer.h"
#include "Utils.h" #include "Utils.h"
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <algorithm>
using boost::python::len; using boost::python::len;
using boost::python::extract; using boost::python::extract;
using boost::packaged_task;
using boost::bind;
using boost::unique_future;
using boost::make_shared;
using boost::shared_ptr;
using boost::bind;
using boost::thread;
namespace YouCompleteMe namespace YouCompleteMe
{ {
namespace
{
const unsigned int MAX_ASYNC_THREADS = 4;
const unsigned int MIN_ASYNC_THREADS = 2;
void ThreadMain( TaskStack &task_stack )
{
while ( true )
{
( *task_stack.Pop() )();
}
}
} // unnamed namespace
Completer::Completer( const Pylist &candidates ) Completer::Completer( const Pylist &candidates )
: threading_enabled_( false )
{ {
AddCandidatesToDatabase( candidates, "", "" ); AddCandidatesToDatabase( candidates, "", "" );
} }
@ -34,7 +62,8 @@ Completer::Completer( const Pylist &candidates )
Completer::Completer( const Pylist &candidates, Completer::Completer( const Pylist &candidates,
const std::string &filetype, const std::string &filetype,
const std::string &filepath) const std::string &filepath )
: threading_enabled_( false )
{ {
AddCandidatesToDatabase( candidates, filetype, filepath ); AddCandidatesToDatabase( candidates, filetype, filepath );
} }
@ -50,6 +79,15 @@ Completer::~Completer()
} }
// We need this mostly so that we can not use it in tests. Apparently the
// GoogleTest framework goes apeshit on us if we enable threads by default.
void Completer::EnableThreading()
{
threading_enabled_ = true;
InitThreads();
}
void Completer::AddCandidatesToDatabase( const Pylist &new_candidates, void Completer::AddCandidatesToDatabase( const Pylist &new_candidates,
const std::string &filetype, const std::string &filetype,
const std::string &filepath ) const std::string &filepath )
@ -85,13 +123,57 @@ void Completer::CandidatesForQuery( const std::string &query,
void Completer::CandidatesForQueryAndType( const std::string &query, void Completer::CandidatesForQueryAndType( const std::string &query,
const std::string &filetype, const std::string &filetype,
Pylist &candidates ) const Pylist &candidates ) const
{
std::vector< Result > results;
ResultsForQueryAndType( query, filetype, results );
foreach ( const Result& result, results )
{
candidates.append( *result.Text() );
}
}
Future Completer::CandidatesForQueryAndTypeAsync(
const std::string &query,
const std::string &filetype ) const
{
// TODO: throw exception when threading is not enabled and this is called
if (!threading_enabled_)
return Future();
// Try not to look at this too hard, it may burn your eyes.
shared_ptr< packaged_task< AsyncResults > > task =
make_shared< packaged_task< AsyncResults > >(
bind( &Completer::ResultsForQueryAndType,
boost::cref( *this ),
query,
filetype ) );
unique_future< AsyncResults > future = task->get_future();
task_stack_.Push( task );
return Future( move( future ) );
}
AsyncResults Completer::ResultsForQueryAndType(
const std::string &query,
const std::string &filetype ) const
{
AsyncResults results = boost::make_shared< std::vector< Result > >();
ResultsForQueryAndType( query, filetype, *results );
return results;
}
void Completer::ResultsForQueryAndType( const std::string &query,
const std::string &filetype,
std::vector< Result > &results ) const
{ {
FiletypeMap::const_iterator it = filetype_map_.find( filetype ); FiletypeMap::const_iterator it = filetype_map_.find( filetype );
if ( it == filetype_map_.end() ) if ( it == filetype_map_.end() )
return; return;
Bitset query_bitset = LetterBitsetFromString( query ); Bitset query_bitset = LetterBitsetFromString( query );
std::vector< Result > results;
foreach ( const FilepathToCandidates::value_type &path_and_candidates, foreach ( const FilepathToCandidates::value_type &path_and_candidates,
*it->second ) *it->second )
@ -108,11 +190,6 @@ void Completer::CandidatesForQueryAndType( const std::string &query,
} }
std::sort( results.begin(), results.end() ); std::sort( results.begin(), results.end() );
foreach ( const Result& result, results )
{
candidates.append( *result.Text() );
}
} }
@ -136,4 +213,17 @@ std::vector< Candidate* >& Completer::GetCandidateVector(
} }
void Completer::InitThreads()
{
int threads_to_create =
std::max( MIN_ASYNC_THREADS,
std::min( MAX_ASYNC_THREADS, thread::hardware_concurrency() ) );
for ( int i = 0; i < threads_to_create; ++i )
{
threads_.create_thread( bind( ThreadMain, boost::ref( task_stack_ ) ) );
}
}
} // namespace YouCompleteMe } // namespace YouCompleteMe

View File

@ -19,6 +19,8 @@
#define COMPLETER_H_7AR4UGXE #define COMPLETER_H_7AR4UGXE
#include "Candidate.h" #include "Candidate.h"
#include "ConcurrentStack.h"
#include "Future.h"
#include <boost/utility.hpp> #include <boost/utility.hpp>
#include <boost/python.hpp> #include <boost/python.hpp>
@ -28,6 +30,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
namespace YouCompleteMe namespace YouCompleteMe
{ {
@ -44,10 +47,12 @@ typedef boost::unordered_map< std::string,
typedef boost::unordered_map< std::string, typedef boost::unordered_map< std::string,
boost::shared_ptr< FilepathToCandidates > > FiletypeMap; boost::shared_ptr< FilepathToCandidates > > FiletypeMap;
typedef ConcurrentStack<
boost::shared_ptr<
boost::packaged_task< AsyncResults > > > TaskStack;
// TODO: resolve problems with noncopyable
// class Completer : boost::noncopyable class Completer : boost::noncopyable
class Completer
{ {
public: public:
Completer() {} Completer() {}
@ -57,6 +62,8 @@ public:
const std::string &filepath ); const std::string &filepath );
~Completer(); ~Completer();
void EnableThreading();
void AddCandidatesToDatabase( const Pylist &new_candidates, void AddCandidatesToDatabase( const Pylist &new_candidates,
const std::string &filetype, const std::string &filetype,
const std::string &filepath ); const std::string &filepath );
@ -69,27 +76,41 @@ public:
const std::string &filetype, const std::string &filetype,
Pylist &candidates ) const; Pylist &candidates ) const;
Future CandidatesForQueryAndTypeAsync( const std::string &query,
const std::string &filetype ) const;
private: private:
AsyncResults ResultsForQueryAndType( const std::string &query,
const std::string &filetype ) const;
void ResultsForQueryAndType( const std::string &query,
const std::string &filetype,
std::vector< Result > &results ) const;
std::vector< Candidate* >& GetCandidateVector( std::vector< Candidate* >& GetCandidateVector(
const std::string &filetype, const std::string &filetype,
const std::string &filepath ); const std::string &filepath );
struct CandidatePointerLess void InitThreads();
{
bool operator() ( const Candidate *first, const Candidate *second )
{ /////////////////////////////
return first->Text() < second->Text(); // PRIVATE MEMBER VARIABLES
} /////////////////////////////
};
// This data structure owns all the Candidate pointers // This data structure owns all the Candidate pointers
CandidateRepository candidate_repository_; CandidateRepository candidate_repository_;
FiletypeMap filetype_map_; FiletypeMap filetype_map_;
mutable TaskStack task_stack_;
bool threading_enabled_;
boost::thread_group threads_;
}; };
} // namespace YouCompleteMe } // namespace YouCompleteMe
#endif /* end of include guard: COMPLETER_H_7AR4UGXE */ #endif /* end of include guard: COMPLETER_H_7AR4UGXE */

66
cpp/ConcurrentStack.h Normal file
View File

@ -0,0 +1,66 @@
// Copyright (C) 2011, 2012 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 CONCURRENTSTACK_H_SYF1JPPG
#define CONCURRENTSTACK_H_SYF1JPPG
#include <stack>
#include <boost/thread.hpp>
#include <boost/utility.hpp>
namespace YouCompleteMe
{
template <typename T>
class ConcurrentStack : boost::noncopyable
{
public:
void Push( const T& data )
{
{
boost::unique_lock< boost::mutex > lock( mutex_ );
stack_.push( data );
}
condition_variable_.notify_one();
}
T Pop()
{
boost::unique_lock< boost::mutex > lock( mutex_ );
while ( stack_.empty() )
{
condition_variable_.wait( lock );
}
T result = stack_.top();
stack_.pop();
return result;
}
private:
std::stack<T> stack_;
boost::mutex mutex_;
boost::condition_variable condition_variable_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: CONCURRENTSTACK_H_SYF1JPPG */

55
cpp/Future.cpp Normal file
View File

@ -0,0 +1,55 @@
// Copyright (C) 2011, 2012 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 "standard.h"
#include "Future.h"
#include "Result.h"
namespace YouCompleteMe
{
Future::Future( boost::shared_future< AsyncResults > future )
: future_( boost::move( future ) )
{
}
bool Future::ResultsReady()
{
return future_.is_ready();
}
void Future::GetResults( Pylist &candidates )
{
AsyncResults results;
try
{
results = future_.get();
}
catch ( boost::future_uninitialized & )
{
return;
}
foreach ( const Result& result, *results )
{
candidates.append( *result.Text() );
}
}
} // namespace YouCompleteMe

47
cpp/Future.h Normal file
View File

@ -0,0 +1,47 @@
// Copyright (C) 2011, 2012 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 FUTURE_H_NR1U6MZS
#define FUTURE_H_NR1U6MZS
#include <boost/thread.hpp>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
namespace YouCompleteMe
{
class Result;
typedef boost::python::list Pylist;
typedef boost::shared_ptr< std::vector< Result > > AsyncResults;
class Future
{
public:
Future() {}
Future( boost::shared_future< AsyncResults > future );
bool ResultsReady();
void GetResults( Pylist &candidates );
private:
boost::shared_future< AsyncResults > future_;
};
} // namespace YouCompleteMe
#endif /* end of include guard: FUTURE_H_NR1U6MZS */

View File

@ -16,15 +16,23 @@
// along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. // along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
#include "Completer.h" #include "Completer.h"
#include "Future.h"
#include <boost/python.hpp> #include <boost/python.hpp>
#include <boost/utility.hpp>
BOOST_PYTHON_MODULE(indexer) BOOST_PYTHON_MODULE(indexer)
{ {
using namespace boost::python; using namespace boost::python;
using namespace YouCompleteMe; using namespace YouCompleteMe;
class_<Completer>( "Completer" ) class_< Future >( "Future" )
.def( "ResultsReady", &Future::ResultsReady )
.def( "GetResults", &Future::GetResults );
class_< Completer, boost::noncopyable >( "Completer" )
.def( "EnableThreading", &Completer::EnableThreading )
.def( "AddCandidatesToDatabase", &Completer::AddCandidatesToDatabase ) .def( "AddCandidatesToDatabase", &Completer::AddCandidatesToDatabase )
.def( "CandidatesForQueryAndType", &Completer::CandidatesForQueryAndType ); .def( "CandidatesForQueryAndTypeAsync",
&Completer::CandidatesForQueryAndTypeAsync );
} }

View File

@ -26,15 +26,26 @@ min_num_chars = int( vim.eval( "g:ycm_min_num_of_chars_for_completion" ) )
class CompletionSystem( object ): class CompletionSystem( object ):
def __init__( self ): def __init__( self ):
self.completer = indexer.Completer() self.completer = indexer.Completer()
self.completer.EnableThreading()
self.pattern = re.compile( r"[_a-zA-Z]\w*" ) self.pattern = re.compile( r"[_a-zA-Z]\w*" )
self.future = None
def CompletionCandidatesForQuery( self, query ): def CandidatesForQueryAsync( self, query ):
candidates = []
filetype = vim.eval( "&filetype" ) filetype = vim.eval( "&filetype" )
self.completer.CandidatesForQueryAndType( SanitizeQuery( query ), self.future = self.completer.CandidatesForQueryAndTypeAsync(
filetype, SanitizeQuery( query ),
candidates ) filetype )
return candidates
def AsyncCandidateRequestReady( self ):
return self.future.ResultsReady()
def CandidatesFromStoredRequest( self ):
if not self.future:
return []
results = []
self.future.GetResults( results )
return results
def AddBufferIdentifiers( self ): def AddBufferIdentifiers( self ):
text = "\n".join( vim.current.buffer ) text = "\n".join( vim.current.buffer )