Add general filename completer & other stuff

This commit is contained in:
Stanislav Golovanov 2013-04-11 15:42:36 +04:00 committed by Strahinja Val Markovic
parent 5d37c40fea
commit bb5839dd74
6 changed files with 184 additions and 108 deletions

View File

@ -17,32 +17,24 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from completers.completer import Completer from completers.completer import GeneralCompleter
import vim import vim
import vimsupport import vimsupport
import ycm_core import ycm_core
import ycm_utils as utils import ycm_utils as utils
MAX_IDENTIFIER_COMPLETIONS_RETURNED = 10 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 ): def __init__( self ):
super( IdentifierCompleter, self ).__init__() super( IdentifierCompleter, self ).__init__()
self.completer = ycm_core.IdentifierCompleter() self.completer = ycm_core.IdentifierCompleter()
self.completer.EnableThreading() self.completer.EnableThreading()
def SupportedFiletypes( self ):
# magic token meaning all filetypes
return set( [ 'ycm_all' ] )
def ShouldUseNow( self, start_column ): def ShouldUseNow( self, start_column ):
query_length = vimsupport.CurrentColumn() - start_column return self.QueryLengthAboveMinThreshold( start_column )
return query_length >= MIN_NUM_CHARS
def CandidatesForQueryAsync( self, query, unused_start_column ): def CandidatesForQueryAsync( self, query, unused_start_column ):

View File

@ -21,11 +21,14 @@ import abc
import vim import vim
import vimsupport import vimsupport
import ycm_core import ycm_core
import ycm
from collections import defaultdict from collections import defaultdict
from threading import Event
NO_USER_COMMANDS = 'This completer does not define any commands.' 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 ): class Completer( object ):
"""A base class for all Completers in YCM. """A base class for all Completers in YCM.
@ -154,6 +157,11 @@ class Completer( object ):
return False 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 # It's highly likely you DON'T want to override this function but the *Inner
# version of it. # version of it.
def CandidatesForQueryAsync( self, query, start_column ): def CandidatesForQueryAsync( self, query, start_column ):
@ -178,10 +186,15 @@ class Completer( object ):
candidates = candidates.words candidates = candidates.words
items_are_objects = 'word' in candidates[ 0 ] items_are_objects = 'word' in candidates[ 0 ]
return ycm_core.FilterAndSortCandidates( try:
candidates, matches = ycm_core.FilterAndSortCandidates(
'word' if items_are_objects else '', candidates,
query ) 'word' if items_are_objects else '',
query )
except:
matches = []
return matches
def CandidatesForQueryAsyncInner( self, query, start_column ): def CandidatesForQueryAsyncInner( self, query, start_column ):
@ -295,6 +308,31 @@ class Completer( object ):
return '' 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 ): class CompletionsCache( object ):
def __init__( self ): def __init__( self ):
self.line = -1 self.line = -1

View File

@ -0,0 +1,94 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Stanislav Golovanov <stgolovanov@gmail.com>
#
#
# 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/>.
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

View File

@ -19,7 +19,7 @@
from completers.completer import Completer from completers.completer import Completer
from completers.all.identifier_completer import IdentifierCompleter from completers.all.identifier_completer import IdentifierCompleter
from threading import Thread, Event from threading import Thread
import vimsupport import vimsupport
import inspect import inspect
import fnmatch import fnmatch
@ -58,11 +58,10 @@ class GeneralCompleterStore( Completer ):
def InitCompleters( self ): def InitCompleters( self ):
# This method creates objects of main completers class. # This method creates objects of main completers class.
completers = [] completers = []
modules = [ module for module in os.listdir( '.' ) modules = [ module for module in os.listdir( os.path.dirname(__file__) )
if fnmatch.fnmatch(module, '*.py') if fnmatch.fnmatch(module, '*.py')
and not 'general_completer' in module and not 'general_completer' in module
and not '__init__' in module and not '__init__' in module ]
and not 'hook' in module ]
for module in modules: for module in modules:
@ -79,20 +78,21 @@ class GeneralCompleterStore( Completer ):
for _, ClassObject in inspect.getmembers( module, inspect.isclass ): for _, ClassObject in inspect.getmembers( module, inspect.isclass ):
# Iterate over all classes in a module and select main class # 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 classInstance = ClassObject
# Init selected class and store class object
completers.append( GeneralCompleterInstance( classInstance() ) )
# append IdentifierCompleter # Init selected class and store class object
completers.append( GeneralCompleterInstance( IdentifierCompleter() ) ) completers.append( classInstance() )
completers.append( IdentifierCompleter() )
return completers return completers
def SupportedFiletypes( self ): def SupportedFiletypes( self ):
# magic token meaning all filetypes return set()
return set( [ 'ycm_all' ] )
def ShouldUseNow( self, start_column ): def ShouldUseNow( self, start_column ):
@ -100,9 +100,9 @@ class GeneralCompleterStore( Completer ):
# True. Also update flags in completers classes # True. Also update flags in completers classes
flag = False flag = False
for completer in self.completers: for completer in self.completers:
ShouldUse = completer.completer.ShouldUseNow( start_column ) _should_use = completer.ShouldUseNow( start_column )
completer.ShouldUse = ShouldUse completer._should_use = _should_use
if ShouldUse: if _should_use:
flag = True flag = True
return flag return flag
@ -114,23 +114,23 @@ class GeneralCompleterStore( Completer ):
# if completer should be used start thread by setting Event flag # if completer should be used start thread by setting Event flag
for completer in self.completers: for completer in self.completers:
completer.finished.clear() completer._finished.clear()
if completer.ShouldUse and not completer.should_start.is_set(): if completer._should_use and not completer._should_start.is_set():
completer.should_start.set() completer._should_start.set()
def AsyncCandidateRequestReady( self ): def AsyncCandidateRequestReady( self ):
# Return True when all completers that should be used are finished their work. # Return True when all completers that should be used are finished their work.
for completer in self.completers: 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 False
return True return True
def CandidatesFromStoredRequest( self ): def CandidatesFromStoredRequest( self ):
for completer in self.completers: for completer in self.completers:
if completer.ShouldUse and completer.finished.is_set(): if completer._should_use and completer._finished.is_set():
self._candidates += completer.results.pop() self._candidates += completer._results.pop()
return self._candidates return self._candidates
@ -139,17 +139,17 @@ class GeneralCompleterStore( Completer ):
while True: while True:
# sleep until ShouldUseNow returns 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 ) self.completion_start_column )
while not completer.completer.AsyncCandidateRequestReady(): while not completer.AsyncCandidateRequestReady():
continue continue
completer.results.append( completer.completer.CandidatesFromStoredRequest() ) completer._results.append( completer.CandidatesFromStoredRequest() )
completer.finished.set() completer._finished.set()
def StartThreads( self ): def StartThreads( self ):
@ -161,50 +161,38 @@ class GeneralCompleterStore( Completer ):
# Process all parsing methods of completers. Needed by identifier completer # Process all parsing methods of completers. Needed by identifier completer
for completer in self.completers: for completer in self.completers:
# clear all stored completion results # clear all stored completion results
completer.results = [] completer._results = []
completer.completer.OnFileReadyToParse() completer.OnFileReadyToParse()
def OnCursorMovedInsertMode( self ): def OnCursorMovedInsertMode( self ):
for completer in self.completers: for completer in self.completers:
completer.completer.OnCursorMovedInsertMode() completer.OnCursorMovedInsertMode()
def OnCursorMovedNormalMode( self ): def OnCursorMovedNormalMode( self ):
for completer in self.completers: for completer in self.completers:
completer.completer.OnCursorMovedNormalMode() completer.OnCursorMovedNormalMode()
def OnBufferVisit( self ): def OnBufferVisit( self ):
for completer in self.completers: for completer in self.completers:
completer.completer.OnBufferVisit() completer.OnBufferVisit()
def OnBufferDelete( self, deleted_buffer_file ): def OnBufferDelete( self, deleted_buffer_file ):
for completer in self.completers: for completer in self.completers:
completer.completer.OnBufferDelete( deleted_buffer_file ) completer.OnBufferDelete( deleted_buffer_file )
def OnCursorHold( self ): def OnCursorHold( self ):
for completer in self.completers: for completer in self.completers:
completer.completer.OnCursorHold() completer.OnCursorHold()
def OnInsertLeave( self ): def OnInsertLeave( self ):
for completer in self.completers: for completer in self.completers:
completer.completer.OnInsertLeave() 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 = []
def WaitAndClear( event, timeout=None ): def WaitAndClear( event, timeout=None ):

View File

@ -1,21 +0,0 @@
# Copyright (C) 2013 Stanislav Golovanov <stgolovanov@gmail.com>
#
# 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/>.
from general_completer import GeneralCompleterStore
def GetCompleter():
return GeneralCompleterStore()

View File

@ -17,14 +17,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
from completers.completer import Completer, CompletionsCache from completers.completer import GeneralCompleter, CompletionsCache
from UltiSnips import UltiSnips_Manager from UltiSnips import UltiSnips_Manager
import vimsupport 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. General completer that provides UltiSnips snippet names in completions.
@ -47,15 +45,11 @@ class UltiSnipsCompleter( Completer ):
def ShouldUseNowInner( self, start_column ): def ShouldUseNowInner( self, start_column ):
query_length = vimsupport.CurrentColumn() - start_column return self.QueryLengthAboveMinThreshold( start_column )
return query_length >= MIN_NUM_CHARS
def SupportedFiletypes( self ):
# magic token meaning all filetypes
return set( [ 'ycm_all' ] )
# 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 ): def CandidatesForQueryAsync( self, query, start_column ):
self.completion_start_column = start_column self.completion_start_column = start_column
@ -77,27 +71,17 @@ class UltiSnipsCompleter( Completer ):
return self.flag 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 ): def CandidatesFromStoredRequestInner( self ):
return self._candidates return self._candidates
def SetCandidates( self ): def SetCandidates( self ):
try: try:
# get all snippets for filetype
rawsnips = UltiSnips_Manager._snips( '', 1 ) 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 ), self._candidates = [ { 'word': str( snip.trigger ),
'menu': str( '<snip> ' + snip.description ) } 'menu': str( '<snip> ' + snip.description ) }
for snip in rawsnips ] for snip in rawsnips ]
@ -108,5 +92,6 @@ class UltiSnipsCompleter( Completer ):
def OnFileReadyToParse( self ): def OnFileReadyToParse( self ):
# Invalidate cache on buffer switch # Invalidate cache on buffer switch
self.completions_cache = None self.completions_cache = CompletionsCache()
self.SetCandidates() self.SetCandidates()
self.completions_cache.raw_completions = self._candidates