diff --git a/python/completers/all/identifier_completer.py b/python/completers/all/identifier_completer.py
index fcd3f07f..ba0e82e0 100644
--- a/python/completers/all/identifier_completer.py
+++ b/python/completers/all/identifier_completer.py
@@ -17,32 +17,24 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see .
-from completers.completer import Completer
+from completers.completer import GeneralCompleter
import vim
import vimsupport
import ycm_core
import ycm_utils as utils
MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10
-MIN_NUM_CHARS = int( vimsupport.GetVariableValue(
- "g:ycm_min_num_of_chars_for_completion" ) )
-class IdentifierCompleter( Completer ):
+class IdentifierCompleter( GeneralCompleter ):
def __init__( self ):
super( IdentifierCompleter, self ).__init__()
self.completer = ycm_core.IdentifierCompleter()
self.completer.EnableThreading()
- def SupportedFiletypes( self ):
- # magic token meaning all filetypes
- return set( [ 'ycm_all' ] )
-
-
def ShouldUseNow( self, start_column ):
- query_length = vimsupport.CurrentColumn() - start_column
- return query_length >= MIN_NUM_CHARS
+ return self.QueryLengthAboveMinThreshold( start_column )
def CandidatesForQueryAsync( self, query, unused_start_column ):
diff --git a/python/completers/completer.py b/python/completers/completer.py
index e5e18675..9a3868de 100644
--- a/python/completers/completer.py
+++ b/python/completers/completer.py
@@ -21,11 +21,14 @@ import abc
import vim
import vimsupport
import ycm_core
-import ycm
from collections import defaultdict
+from threading import Event
NO_USER_COMMANDS = 'This completer does not define any commands.'
+MIN_NUM_CHARS = int( vimsupport.GetVariableValue(
+ "g:ycm_min_num_of_chars_for_completion" ) )
+
class Completer( object ):
"""A base class for all Completers in YCM.
@@ -154,6 +157,11 @@ class Completer( object ):
return False
+ def QueryLengthAboveMinThreshold( self, start_column ):
+ query_length = vimsupport.CurrentColumn() - start_column
+ return query_length >= MIN_NUM_CHARS
+
+
# It's highly likely you DON'T want to override this function but the *Inner
# version of it.
def CandidatesForQueryAsync( self, query, start_column ):
@@ -178,10 +186,15 @@ class Completer( object ):
candidates = candidates.words
items_are_objects = 'word' in candidates[ 0 ]
- return ycm_core.FilterAndSortCandidates(
- candidates,
- 'word' if items_are_objects else '',
- query )
+ try:
+ matches = ycm_core.FilterAndSortCandidates(
+ candidates,
+ 'word' if items_are_objects else '',
+ query )
+ except:
+ matches = []
+
+ return matches
def CandidatesForQueryAsyncInner( self, query, start_column ):
@@ -295,6 +308,31 @@ class Completer( object ):
return ''
+class GeneralCompleter( Completer ):
+ """
+ A base class for General completers in YCM.
+
+ Because this is a subclass of Completer class, you should refer to the
+ dpcumentation of Completer API.
+
+ Only exception is that GeneralCompleterStore class that collects and controls
+ all general completers already adds threading for completers, so there
+ is no need to add a threading to new general completers.
+
+ added __init__ fields are for GeneralCompleterStore internal use only.
+ """
+ def __init__( self ):
+ super( GeneralCompleter, self ).__init__()
+ self._should_start = Event()
+ self._should_use = False
+ self._finished = Event()
+ self._results = []
+
+
+ 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
new file mode 100644
index 00000000..f1d1d158
--- /dev/null
+++ b/python/completers/general/filename_completer.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2013 Stanislav Golovanov
+#
+#
+# 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 completers.completer import GeneralCompleter, CompletionsCache
+import vimsupport
+import ycm_core
+import vim
+import os
+import re
+
+
+class FilenameCompleter( GeneralCompleter ):
+ """
+ General completer that provides filename and filepath completions.
+
+ It maintains a cache of completions which is invalidated on each '/' symbol.
+ """
+ def __init__(self):
+ super( FilenameCompleter, self ).__init__()
+ self._candidates = []
+ self._query = None
+ self._should_use = False
+
+ # TODO look into vim-style path globbing, NCC has a nice implementation
+ self._path_regex = re.compile( """(?:[A-z]+:/|[/~]|\./|\.+/)+ # 1 or more 'D:/'-like token or '/' or '~' or './' or '../'
+ (?:[ /a-zA-Z0-9()$+_~.\x80-\xff-\[\]]| # any alphanumeric symbal and space literal
+ [^\x20-\x7E]| # skip any special symbols
+ \\.)* # backslash and 1 char after it. + matches 1 or more of whole group
+ """, re.X )
+
+
+ def ShouldUseNowInner( self, start_column ):
+ token = vim.current.line[ start_column - 1 ]
+ if token == '/' or self._should_use:
+ self._should_use = True
+ return True
+ else:
+ return False
+
+
+ def CandidatesForQueryAsyncInner( self, query, start_column ):
+ self._candidates = []
+ self._query = query
+ self._completions_ready = False
+ self.line = str( vim.current.line.strip() )
+ self.SetCandidates()
+
+
+ def AsyncCandidateRequestReadyInner( self ):
+ return self._completions_ready
+
+
+ def OnInsertLeave( self ):
+ # TODO this a hackish way to keep results when typing 2-3rd char after slash
+ # because identifier completer will kick in and replace results for 1 char.
+ # Need to do something better
+ self._should_use = False
+
+
+ def CandidatesFromStoredRequestInner( self ):
+ return self._candidates
+
+
+ def SetCandidates( self ):
+ path = self._path_regex.search( self.line )
+ self._working_dir = os.path.expanduser( path.group() ) if path else ''
+
+ try:
+ paths = os.listdir( self._working_dir )
+ except:
+ paths = []
+
+ self._candidates = [ {'word': path,
+ 'dup': 1,
+ 'menu': '[Dir]' if os.path.isdir( self._working_dir + \
+ '/' + path ) else '[File]'
+ } for path in paths ]
+
+ self._completions_ready = True
diff --git a/python/completers/general/general_completer.py b/python/completers/general/general_completer.py
index 25a0c243..d625c8fe 100644
--- a/python/completers/general/general_completer.py
+++ b/python/completers/general/general_completer.py
@@ -19,7 +19,7 @@
from completers.completer import Completer
from completers.all.identifier_completer import IdentifierCompleter
-from threading import Thread, Event
+from threading import Thread
import vimsupport
import inspect
import fnmatch
@@ -58,11 +58,10 @@ class GeneralCompleterStore( Completer ):
def InitCompleters( self ):
# This method creates objects of main completers class.
completers = []
- modules = [ module for module in os.listdir( '.' )
+ modules = [ module for module in os.listdir( os.path.dirname(__file__) )
if fnmatch.fnmatch(module, '*.py')
and not 'general_completer' in module
- and not '__init__' in module
- and not 'hook' in module ]
+ and not '__init__' in module ]
for module in modules:
@@ -79,20 +78,21 @@ class GeneralCompleterStore( Completer ):
for _, ClassObject in inspect.getmembers( module, inspect.isclass ):
# Iterate over all classes in a module and select main class
- if hasattr( ClassObject, 'CandidatesForQueryAsyncInner' ):
+ if not __name__ in str(ClassObject) and 'general' in str(ClassObject):
+
classInstance = ClassObject
- # Init selected class and store class object
- completers.append( GeneralCompleterInstance( classInstance() ) )
- # append IdentifierCompleter
- completers.append( GeneralCompleterInstance( IdentifierCompleter() ) )
+ # Init selected class and store class object
+ completers.append( classInstance() )
+
+ completers.append( IdentifierCompleter() )
+
return completers
def SupportedFiletypes( self ):
- # magic token meaning all filetypes
- return set( [ 'ycm_all' ] )
+ return set()
def ShouldUseNow( self, start_column ):
@@ -100,9 +100,9 @@ class GeneralCompleterStore( Completer ):
# True. Also update flags in completers classes
flag = False
for completer in self.completers:
- ShouldUse = completer.completer.ShouldUseNow( start_column )
- completer.ShouldUse = ShouldUse
- if ShouldUse:
+ _should_use = completer.ShouldUseNow( start_column )
+ completer._should_use = _should_use
+ if _should_use:
flag = True
return flag
@@ -114,23 +114,23 @@ class GeneralCompleterStore( Completer ):
# if completer should be used start thread by setting Event flag
for completer in self.completers:
- completer.finished.clear()
- if completer.ShouldUse and not completer.should_start.is_set():
- completer.should_start.set()
+ completer._finished.clear()
+ if completer._should_use and not completer._should_start.is_set():
+ completer._should_start.set()
def AsyncCandidateRequestReady( self ):
# Return True when all completers that should be used are finished their work.
for completer in self.completers:
- if not completer.finished.is_set() and completer.ShouldUse:
+ if not completer._finished.is_set() and completer._should_use:
return False
return True
def CandidatesFromStoredRequest( self ):
for completer in self.completers:
- if completer.ShouldUse and completer.finished.is_set():
- self._candidates += completer.results.pop()
+ if completer._should_use and completer._finished.is_set():
+ self._candidates += completer._results.pop()
return self._candidates
@@ -139,17 +139,17 @@ class GeneralCompleterStore( Completer ):
while True:
# sleep until ShouldUseNow returns True
- WaitAndClear( completer.should_start )
+ WaitAndClear( completer._should_start )
- completer.completer.CandidatesForQueryAsync( self.query,
+ completer.CandidatesForQueryAsync( self.query,
self.completion_start_column )
- while not completer.completer.AsyncCandidateRequestReady():
+ while not completer.AsyncCandidateRequestReady():
continue
- completer.results.append( completer.completer.CandidatesFromStoredRequest() )
+ completer._results.append( completer.CandidatesFromStoredRequest() )
- completer.finished.set()
+ completer._finished.set()
def StartThreads( self ):
@@ -161,50 +161,38 @@ class GeneralCompleterStore( Completer ):
# Process all parsing methods of completers. Needed by identifier completer
for completer in self.completers:
# clear all stored completion results
- completer.results = []
- completer.completer.OnFileReadyToParse()
+ completer._results = []
+ completer.OnFileReadyToParse()
def OnCursorMovedInsertMode( self ):
for completer in self.completers:
- completer.completer.OnCursorMovedInsertMode()
+ completer.OnCursorMovedInsertMode()
def OnCursorMovedNormalMode( self ):
for completer in self.completers:
- completer.completer.OnCursorMovedNormalMode()
+ completer.OnCursorMovedNormalMode()
def OnBufferVisit( self ):
for completer in self.completers:
- completer.completer.OnBufferVisit()
+ completer.OnBufferVisit()
def OnBufferDelete( self, deleted_buffer_file ):
for completer in self.completers:
- completer.completer.OnBufferDelete( deleted_buffer_file )
+ completer.OnBufferDelete( deleted_buffer_file )
def OnCursorHold( self ):
for completer in self.completers:
- completer.completer.OnCursorHold()
+ completer.OnCursorHold()
def OnInsertLeave( self ):
for completer in self.completers:
- completer.completer.OnInsertLeave()
-
-
-class GeneralCompleterInstance( object ):
- """
- Class that holds all meta information about specific general completer
- """
- def __init__( self, completer ):
- self.completer = completer
- self.should_start = Event()
- self.ShouldUse = False
- self.finished = Event()
- self.results = []
+ completer.OnInsertLeave()
def WaitAndClear( event, timeout=None ):
diff --git a/python/completers/general/hook.py b/python/completers/general/hook.py
deleted file mode 100644
index e84cf950..00000000
--- a/python/completers/general/hook.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2013 Stanislav Golovanov
-#
-# 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 general_completer import GeneralCompleterStore
-
-def GetCompleter():
- return GeneralCompleterStore()
diff --git a/python/completers/general/ultisnips_completer.py b/python/completers/general/ultisnips_completer.py
index 367e4b08..1c31f7de 100644
--- a/python/completers/general/ultisnips_completer.py
+++ b/python/completers/general/ultisnips_completer.py
@@ -17,14 +17,12 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see .
-from completers.completer import Completer, CompletionsCache
+from completers.completer import GeneralCompleter, CompletionsCache
from UltiSnips import UltiSnips_Manager
import vimsupport
-MIN_NUM_CHARS = int( vimsupport.GetVariableValue(
- "g:ycm_min_num_of_chars_for_completion" ) )
-class UltiSnipsCompleter( Completer ):
+class UltiSnipsCompleter( GeneralCompleter ):
"""
General completer that provides UltiSnips snippet names in completions.
@@ -47,15 +45,11 @@ class UltiSnipsCompleter( Completer ):
def ShouldUseNowInner( self, start_column ):
- query_length = vimsupport.CurrentColumn() - start_column
- return query_length >= MIN_NUM_CHARS
-
-
- def SupportedFiletypes( self ):
- # magic token meaning all filetypes
- return set( [ 'ycm_all' ] )
+ return self.QueryLengthAboveMinThreshold( start_column )
+ # We need to override this because Completer version invalidates cache on
+ # empty query and we want to invalidate cache only on buffer switch.
def CandidatesForQueryAsync( self, query, start_column ):
self.completion_start_column = start_column
@@ -77,27 +71,17 @@ class UltiSnipsCompleter( Completer ):
return self.flag
- # We need to override this because we need to store all snippets but return
- # filtered results on first call.
- 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 = self.completion_start_column
- return self.FilterAndSortCandidates( self._candidates, self._query )
-
-
def CandidatesFromStoredRequestInner( self ):
return self._candidates
def SetCandidates( self ):
try:
- # get all snippets for filetype
rawsnips = UltiSnips_Manager._snips( '', 1 )
+
+ # UltiSnips_Manager._snips() returns a class instance where:
+ # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
+ # class.description - description of the snippet
self._candidates = [ { 'word': str( snip.trigger ),
'menu': str( ' ' + snip.description ) }
for snip in rawsnips ]
@@ -108,5 +92,6 @@ class UltiSnipsCompleter( Completer ):
def OnFileReadyToParse( self ):
# Invalidate cache on buffer switch
- self.completions_cache = None
+ self.completions_cache = CompletionsCache()
self.SetCandidates()
+ self.completions_cache.raw_completions = self._candidates