From 196228217ff138aab75b327679e77a7fdf2cfbf3 Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Mon, 22 Apr 2013 22:19:26 -0700 Subject: [PATCH] Filename completer now uses threads This was done by introducing a new ThreadedCompleter class that descends from Completer. Both JediCompleter and FilenameCompleter descend from ThreadedCompleter. --- python/completers/all/identifier_completer.py | 2 +- python/completers/completer.py | 18 ---- .../completers/general/filename_completer.py | 24 ++---- ...ompleter.py => general_completer_store.py} | 0 .../completers/general/ultisnips_completer.py | 2 +- python/completers/general_completer.py | 37 +++++++++ python/completers/python/jedi_completer.py | 69 ++++----------- python/completers/threaded_completer.py | 83 +++++++++++++++++++ python/ycm.py | 2 +- python/ycm_utils.py | 1 + 10 files changed, 144 insertions(+), 94 deletions(-) rename python/completers/general/{general_completer.py => general_completer_store.py} (100%) create mode 100644 python/completers/general_completer.py create mode 100644 python/completers/threaded_completer.py diff --git a/python/completers/all/identifier_completer.py b/python/completers/all/identifier_completer.py index 820fd518..858fd34e 100644 --- a/python/completers/all/identifier_completer.py +++ b/python/completers/all/identifier_completer.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -from completers.completer import GeneralCompleter +from completers.general_completer import GeneralCompleter import vim import vimsupport import ycm_core diff --git a/python/completers/completer.py b/python/completers/completer.py index 56649f2d..7ebffb24 100644 --- a/python/completers/completer.py +++ b/python/completers/completer.py @@ -303,24 +303,6 @@ class Completer( object ): return '' -class GeneralCompleter( Completer ): - """ - A base class for General completers in YCM. A general completer is used in all - filetypes. - - Because this is a subclass of Completer class, you should refer to the - Completer class documentation. Do NOT use this class for semantic completers! - Subclass Completer directly. - - """ - def __init__( self ): - super( GeneralCompleter, self ).__init__() - - - def SupportedFiletypes( self ): - return set() - - class CompletionsCache( object ): def __init__( self ): self.line = -1 diff --git a/python/completers/general/filename_completer.py b/python/completers/general/filename_completer.py index e2c524e9..f3b1a1c4 100644 --- a/python/completers/general/filename_completer.py +++ b/python/completers/general/filename_completer.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -from completers.completer import GeneralCompleter +from completers.threaded_completer import ThreadedCompleter import vim import vimsupport import os @@ -25,14 +25,13 @@ import re USE_WORKING_DIR = vimsupport.GetBoolValue( 'g:ycm_filepath_completion_use_working_dir' ) -class FilenameCompleter( GeneralCompleter ): +class FilenameCompleter( ThreadedCompleter ): """ General completer that provides filename and filepath completions. """ def __init__(self): super( FilenameCompleter, self ).__init__() - self._candidates = [] self._path_regex = re.compile(""" # 1 or more 'D:/'-like token or '/' or '~' or './' or '../' @@ -53,20 +52,11 @@ class FilenameCompleter( GeneralCompleter ): return vim.current.line[ start_column - 1 ] == '/' - def CandidatesForQueryAsyncInner( self, query, start_column ): - self._candidates = [] - self.ComputePaths( start_column ) + def SupportedFiletypes( self ): + return [] - def AsyncCandidateRequestReadyInner( self ): - return True - - - def CandidatesFromStoredRequestInner( self ): - return self._candidates - - - def ComputePaths( self, start_column ): + def ComputeCandidates( self, unused_query, start_column ): def GenerateCandidateForPath( path, path_dir ): is_dir = os.path.isdir( os.path.join( path_dir, path ) ) return { 'word': path, @@ -86,6 +76,4 @@ class FilenameCompleter( GeneralCompleter ): except: paths = [] - self._candidates = [ GenerateCandidateForPath( path, path_dir ) for path - in paths ] - self._completions_ready = True + return [ GenerateCandidateForPath( path, path_dir ) for path in paths ] diff --git a/python/completers/general/general_completer.py b/python/completers/general/general_completer_store.py similarity index 100% rename from python/completers/general/general_completer.py rename to python/completers/general/general_completer_store.py diff --git a/python/completers/general/ultisnips_completer.py b/python/completers/general/ultisnips_completer.py index 0c58a08c..84192bdc 100644 --- a/python/completers/general/ultisnips_completer.py +++ b/python/completers/general/ultisnips_completer.py @@ -18,7 +18,7 @@ # You should have received a copy of the GNU General Public License # along with YouCompleteMe. If not, see . -from completers.completer import GeneralCompleter +from completers.general_completer import GeneralCompleter from UltiSnips import UltiSnips_Manager diff --git a/python/completers/general_completer.py b/python/completers/general_completer.py new file mode 100644 index 00000000..ba8de867 --- /dev/null +++ b/python/completers/general_completer.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# +# Copyright (C) 2011, 2012 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 completer import Completer + +class GeneralCompleter( Completer ): + """ + A base class for General completers in YCM. A general completer is used in all + filetypes. + + Because this is a subclass of Completer class, you should refer to the + Completer class documentation. Do NOT use this class for semantic completers! + Subclass Completer directly. + + """ + def __init__( self ): + super( GeneralCompleter, self ).__init__() + + + def SupportedFiletypes( self ): + return set() diff --git a/python/completers/python/jedi_completer.py b/python/completers/python/jedi_completer.py index be80268b..cec39408 100644 --- a/python/completers/python/jedi_completer.py +++ b/python/completers/python/jedi_completer.py @@ -19,8 +19,7 @@ # along with YouCompleteMe. If not, see . import vim -from threading import Thread, Event -from completers.completer import Completer +from completers.threaded_completer import ThreadedCompleter import vimsupport import sys @@ -39,7 +38,7 @@ except ImportError: sys.path.pop( 0 ) -class JediCompleter( Completer ): +class JediCompleter( ThreadedCompleter ): """ A Completer that uses the Jedi completion engine. https://jedi.readthedocs.org/en/latest/ @@ -47,16 +46,6 @@ class JediCompleter( Completer ): def __init__( self ): super( JediCompleter, self ).__init__() - self._query_ready = Event() - self._candidates_ready = Event() - self._candidates = None - self._start_completion_thread() - - - def _start_completion_thread( self ): - self._completion_thread = Thread( target=self.SetCandidates ) - self._completion_thread.daemon = True - self._completion_thread.start() def SupportedFiletypes( self ): @@ -64,47 +53,17 @@ class JediCompleter( Completer ): return [ 'python' ] - def CandidatesForQueryAsyncInner( self, unused_query, unused_start_column ): - self._candidates = None - self._candidates_ready.clear() - self._query_ready.set() + def ComputeCandidates( self, unused_query, unused_start_column ): + filename = vim.current.buffer.name + line, column = vimsupport.CurrentLineAndColumn() + # Jedi expects lines to start at 1, not 0 + line += 1 + contents = '\n'.join( vim.current.buffer ) + script = Script( contents, line, column, filename ) + + return [ { 'word': str( completion.word ), + 'menu': str( completion.description ), + 'info': str( completion.doc ) } + for completion in script.complete() ] - def AsyncCandidateRequestReadyInner( self ): - return WaitAndClear( self._candidates_ready, timeout=0.005 ) - - - def CandidatesFromStoredRequestInner( self ): - return self._candidates or [] - - - def SetCandidates( self ): - while True: - try: - WaitAndClear( self._query_ready ) - - filename = vim.current.buffer.name - line, column = vimsupport.CurrentLineAndColumn() - # Jedi expects lines to start at 1, not 0 - line += 1 - contents = '\n'.join( vim.current.buffer ) - script = Script( contents, line, column, filename ) - - self._candidates = [ { 'word': str( completion.word ), - 'menu': str( completion.description ), - 'info': str( completion.doc ) } - for completion in script.complete() ] - except: - self._query_ready.clear() - self._candidates = [] - self._candidates_ready.set() - - -def WaitAndClear( event, timeout=None ): - # We can't just do flag_is_set = event.wait( timeout ) because that breaks on - # Python 2.6 - event.wait( timeout ) - flag_is_set = event.is_set() - if flag_is_set: - event.clear() - return flag_is_set diff --git a/python/completers/threaded_completer.py b/python/completers/threaded_completer.py new file mode 100644 index 00000000..81d39ef7 --- /dev/null +++ b/python/completers/threaded_completer.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# +# Copyright (C) 2011, 2012 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 . + +import abc +from threading import Thread, Event +from completer import Completer + +class ThreadedCompleter( Completer ): + def __init__( self ): + super( ThreadedCompleter, self ).__init__() + self._query_ready = Event() + self._candidates_ready = Event() + self._candidates = None + self._start_completion_thread() + + + def _start_completion_thread( self ): + self._completion_thread = Thread( target=self.SetCandidates ) + self._completion_thread.daemon = True + self._completion_thread.start() + + + def CandidatesForQueryAsyncInner( self, query, start_column ): + self._candidates = None + self._candidates_ready.clear() + self._query = query + self._start_column = start_column + self._query_ready.set() + + + def AsyncCandidateRequestReadyInner( self ): + return WaitAndClearIfSet( self._candidates_ready, timeout=0.005 ) + + + def CandidatesFromStoredRequestInner( self ): + return self._candidates or [] + + + @abc.abstractmethod + def ComputeCandidates( self, query, start_column ): + pass + + + def SetCandidates( self ): + while True: + try: + WaitAndClearIfSet( self._query_ready ) + self._candidates = self.ComputeCandidates( self._query, + self._start_column ) + except: + self._query_ready.clear() + self._candidates = [] + self._candidates_ready.set() + + +def WaitAndClearIfSet( event, timeout=None ): + """Given an |event| and a |timeout|, waits for the event a maximum of timeout + seconds. After waiting, clears the event if it's set and returns the state of + the event before it was cleared.""" + + # We can't just do flag_is_set = event.wait( timeout ) because that breaks on + # Python 2.6 + event.wait( timeout ) + flag_is_set = event.is_set() + if flag_is_set: + event.clear() + return flag_is_set diff --git a/python/ycm.py b/python/ycm.py index 1cdfc354..72d4d4f4 100644 --- a/python/ycm.py +++ b/python/ycm.py @@ -35,7 +35,7 @@ except ImportError as e: from completers.all.omni_completer import OmniCompleter -from completers.general.general_completer import GeneralCompleterStore +from completers.general.general_completer_store import GeneralCompleterStore FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval( diff --git a/python/ycm_utils.py b/python/ycm_utils.py index 68744e50..d1fe5035 100644 --- a/python/ycm_utils.py +++ b/python/ycm_utils.py @@ -23,3 +23,4 @@ def IsIdentifierChar( char ): def SanitizeQuery( query ): return query.strip() +