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
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
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 ):

View File

@ -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(
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

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.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 ):

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
# 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
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> ' + 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