Merge branch 'JazzCore-general_completers'

Conflicts:
	python/ycm.py
This commit is contained in:
Strahinja Val Markovic 2013-04-22 22:37:32 -07:00
commit 3258f324dd
14 changed files with 460 additions and 80 deletions

View File

@ -67,6 +67,10 @@ YCM also provides semantic go-to-definition/declaration commands for C-family
languages. Expect more IDE features powered by the various YCM semantic engines
in the future.
You'll also find that YCM has filepath completers (try typing `./` in a file)
and a completer that integrates with [UltiSnips][].
Mac OS X super-quick installation
---------------------------------
@ -861,6 +865,17 @@ Default: `[]`
let g:ycm_extra_conf_globlist = []
### The `g:ycm_filepath_completion_use_working_dir` option
By default, YCM's filepath completion will interpret relative paths like `../`
as being relative to the folder of the file of the currently active buffer.
Setting this option will force YCM to always interpret relative paths as being
relative to Vim's current working directory.
Default: `0`
let g:ycm_filepath_completion_use_working_dir = 0
### The `g:ycm_semantic_triggers` option
This option controls the character-based triggers for the various semantic
@ -1146,3 +1161,4 @@ This software is licensed under the [GPL v3 license][gpl].
[win-wiki]: https://github.com/Valloric/YouCompleteMe/wiki/Windows-Installation-Guide
[eclim]: http://eclim.org/
[jedi]: https://github.com/davidhalter/jedi
[ultisnips]: https://github.com/SirVer/ultisnips/blob/master/doc/UltiSnips.txt

View File

@ -438,7 +438,7 @@ function! s:CompletionsForQuery( query, use_filetype_completer,
if a:use_filetype_completer
py completer = ycm_state.GetFiletypeCompleter()
else
py completer = ycm_state.GetIdentifierCompleter()
py completer = ycm_state.GetGeneralCompleter()
endif
py completer.CandidatesForQueryAsync( vim.eval( 'a:query' ),
@ -487,7 +487,7 @@ function! youcompleteme#Complete( findstart, base )
\ s:completion_start_column . ')' )
if !s:should_use_filetype_completion &&
\ !pyeval( 'ycm_state.ShouldUseIdentifierCompleter(' .
\ !pyeval( 'ycm_state.ShouldUseGeneralCompleter(' .
\ s:completion_start_column . ')' )
" for vim, -2 means not found but don't trigger an error message
" see :h complete-functions
@ -551,7 +551,7 @@ function! s:CompleterCommand(...)
if a:1 == 'ft=ycm:omni'
py completer = ycm_state.GetOmniCompleter()
elseif a:1 == 'ft=ycm:ident'
py completer = ycm_state.GetIdentifierCompleter()
py completer = ycm_state.GetGeneralCompleter()
else
py completer = ycm_state.GetFiletypeCompleterForFiletype(
\ vim.eval('a:1').lstrip('ft=') )

View File

@ -124,6 +124,9 @@ let g:ycm_confirm_extra_conf =
let g:ycm_extra_conf_globlist =
\ get( g:, 'ycm_extra_conf_globlist', [] )
let g:ycm_filepath_completion_use_working_dir =
\ get( g:, 'ycm_filepath_completion_use_working_dir', 0 )
let g:ycm_semantic_triggers =
\ get( g:, 'ycm_semantic_triggers', {
\ 'c' : ['->', '.'],

View File

@ -17,7 +17,7 @@
# 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.general_completer import GeneralCompleter
import vim
import vimsupport
import ycm_core
@ -28,21 +28,15 @@ 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

@ -25,6 +25,9 @@ from collections import defaultdict
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.
@ -153,6 +156,10 @@ 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 ):
@ -177,11 +184,13 @@ class Completer( object ):
candidates = candidates.words
items_are_objects = 'word' in candidates[ 0 ]
return ycm_core.FilterAndSortCandidates(
matches = ycm_core.FilterAndSortCandidates(
candidates,
'word' if items_are_objects else '',
query )
return matches
def CandidatesForQueryAsyncInner( self, query, start_column ):
pass

View File

View File

@ -0,0 +1,79 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Stanislav Golovanov <stgolovanov@gmail.com>
# Strahinja Val Markovic <val@markovic.io>
#
# 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.threaded_completer import ThreadedCompleter
import vim
import vimsupport
import os
import re
USE_WORKING_DIR = vimsupport.GetBoolValue(
'g:ycm_filepath_completion_use_working_dir' )
class FilenameCompleter( ThreadedCompleter ):
"""
General completer that provides filename and filepath completions.
"""
def __init__(self):
super( FilenameCompleter, self ).__init__()
self._path_regex = re.compile("""
# 1 or more 'D:/'-like token or '/' or '~' or './' or '../'
(?:[A-z]+:/|[/~]|\./|\.+/)+
# any alphanumeric symbal and space literal
(?:[ /a-zA-Z0-9()$+_~.\x80-\xff-\[\]]|
# skip any special symbols
[^\x20-\x7E]|
# backslash and 1 char after it. + matches 1 or more of whole group
\\.)*$
""", re.X )
def ShouldUseNowInner( self, start_column ):
return vim.current.line[ start_column - 1 ] == '/'
def SupportedFiletypes( self ):
return []
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,
'dup': 1,
'menu': '[Dir]' if is_dir else '[File]' }
line = vim.current.line[ :start_column ]
match = self._path_regex.search( line )
path_dir = os.path.expanduser( match.group() ) if match else ''
if not USE_WORKING_DIR and not path_dir.startswith( '/' ):
path_dir = os.path.join( os.path.dirname( vim.current.buffer.name ),
path_dir )
try:
paths = os.listdir( path_dir )
except:
paths = []
return [ GenerateCandidateForPath( path, path_dir ) for path in paths ]

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Stanislav Golovanov <stgolovanov@gmail.com>
# 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/>.
from completers.completer import Completer
from completers.all.identifier_completer import IdentifierCompleter
from filename_completer import FilenameCompleter
try:
from ultisnips_completer import UltiSnipsCompleter
USE_ULTISNIPS_COMPLETER = True
except ImportError:
USE_ULTISNIPS_COMPLETER = False
class GeneralCompleterStore( Completer ):
"""
Holds a list of completers that can be used in all filetypes.
It overrides all Competer API methods so that specific calls to
GeneralCompleterStore are passed to all general completers.
"""
def __init__( self ):
super( GeneralCompleterStore, self ).__init__()
self._identifier_completer = IdentifierCompleter()
self._filename_completer = FilenameCompleter()
self._ultisnips_completer = ( UltiSnipsCompleter()
if USE_ULTISNIPS_COMPLETER else None )
self._non_filename_completers = filter( lambda x: x,
[ self._ultisnips_completer,
self._identifier_completer ] )
self._all_completers = filter( lambda x: x,
[ self._identifier_completer,
self._filename_completer,
self._ultisnips_completer ] )
self._current_query_completers = []
def SupportedFiletypes( self ):
return set()
def ShouldUseNow( self, start_column ):
self._current_query_completers = []
if self._filename_completer.ShouldUseNow( start_column ):
self._current_query_completers = [ self._filename_completer ]
return True
should_use_now = False
for completer in self._non_filename_completers:
should_use_this_completer = completer.ShouldUseNow( start_column )
should_use_now = should_use_now or should_use_this_completer
if should_use_this_completer:
self._current_query_completers.append( completer )
return should_use_now
def CandidatesForQueryAsync( self, query, start_column ):
for completer in self._current_query_completers:
completer.CandidatesForQueryAsync( query, start_column )
def AsyncCandidateRequestReady( self ):
return all( x.AsyncCandidateRequestReady() for x in
self._current_query_completers )
def CandidatesFromStoredRequest( self ):
candidates = []
for completer in self._current_query_completers:
candidates += completer.CandidatesFromStoredRequest()
return candidates
def OnFileReadyToParse( self ):
for completer in self._all_completers:
completer.OnFileReadyToParse()
def OnCursorMovedInsertMode( self ):
for completer in self._all_completers:
completer.OnCursorMovedInsertMode()
def OnCursorMovedNormalMode( self ):
for completer in self._all_completers:
completer.OnCursorMovedNormalMode()
def OnBufferVisit( self ):
for completer in self._all_completers:
completer.OnBufferVisit()
def OnBufferDelete( self, deleted_buffer_file ):
for completer in self._all_completers:
completer.OnBufferDelete( deleted_buffer_file )
def OnCursorHold( self ):
for completer in self._all_completers:
completer.OnCursorHold()
def OnInsertLeave( self ):
for completer in self._all_completers:
completer.OnInsertLeave()

View File

@ -0,0 +1,68 @@
#!/usr/bin/env python
#
# Copyright (C) 2013 Stanislav Golovanov <stgolovanov@gmail.com>
# 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/>.
from completers.general_completer import GeneralCompleter
from UltiSnips import UltiSnips_Manager
class UltiSnipsCompleter( GeneralCompleter ):
"""
General completer that provides UltiSnips snippet names in completions.
"""
def __init__( self ):
super( UltiSnipsCompleter, self ).__init__()
self._candidates = None
self._filtered_candidates = None
def ShouldUseNowInner( self, start_column ):
return self.QueryLengthAboveMinThreshold( start_column )
def CandidatesForQueryAsync( self, query, unused_start_column ):
self._filtered_candidates = self.FilterAndSortCandidates( self._candidates,
query )
def AsyncCandidateRequestReady( self ):
return bool( self._candidates )
def CandidatesFromStoredRequest( self ):
return self._filtered_candidates
def OnFileReadyToParse( self ):
self._candidates = _GetCandidates()
def _GetCandidates():
try:
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
return [ { 'word': str( snip.trigger ),
'menu': str( '<snip> ' + snip.description ) }
for snip in rawsnips ]
except:
return []

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python
#
# 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/>.
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()

View File

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

View File

@ -0,0 +1,83 @@
#!/usr/bin/env python
#
# 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/>.
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

View File

@ -33,9 +33,9 @@ except ImportError as e:
'the docs. Full error: {1}'.format(
os.path.dirname( os.path.abspath( __file__ ) ), str( e ) ) )
from completers.all.identifier_completer import IdentifierCompleter
from completers.all.omni_completer import OmniCompleter
from completers.all.omni_completer import OmniCompleter
from completers.general.general_completer_store import GeneralCompleterStore
FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval(
'g:ycm_filetype_specific_completion_to_disable' )
@ -43,13 +43,13 @@ FILETYPE_SPECIFIC_COMPLETION_TO_DISABLE = vim.eval(
class YouCompleteMe( object ):
def __init__( self ):
self.identcomp = IdentifierCompleter()
self.gencomp = GeneralCompleterStore()
self.omnicomp = OmniCompleter()
self.filetype_completers = {}
def GetIdentifierCompleter( self ):
return self.identcomp
def GetGeneralCompleter( self ):
return self.gencomp
def GetOmniCompleter( self ):
@ -101,8 +101,8 @@ class YouCompleteMe( object ):
return completer
def ShouldUseIdentifierCompleter( self, start_column ):
return self.identcomp.ShouldUseNow( start_column )
def ShouldUseGeneralCompleter( self, start_column ):
return self.gencomp.ShouldUseNow( start_column )
def ShouldUseFiletypeCompleter( self, start_column ):
@ -132,21 +132,21 @@ class YouCompleteMe( object ):
def OnFileReadyToParse( self ):
self.identcomp.OnFileReadyToParse()
self.gencomp.OnFileReadyToParse()
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnFileReadyToParse()
def OnBufferDelete( self, deleted_buffer_file ):
self.identcomp.OnBufferDelete( deleted_buffer_file )
self.gencomp.OnBufferDelete( deleted_buffer_file )
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnBufferDelete( deleted_buffer_file )
def OnInsertLeave( self ):
self.identcomp.OnInsertLeave()
self.gencomp.OnInsertLeave()
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnInsertLeave()
@ -176,7 +176,7 @@ class YouCompleteMe( object ):
def OnCurrentIdentifierFinished( self ):
self.identcomp.OnCurrentIdentifierFinished()
self.gencomp.OnCurrentIdentifierFinished()
if self.FiletypeCompletionUsable():
self.GetFiletypeCompleter().OnCurrentIdentifierFinished()
@ -184,7 +184,7 @@ class YouCompleteMe( object ):
def DebugInfo( self ):
completers = set( self.filetype_completers.values() )
completers.add( self.identcomp )
completers.add( self.gencomp )
output = []
for completer in completers:
if not completer:

View File

@ -23,3 +23,4 @@ def IsIdentifierChar( char ):
def SanitizeQuery( query ):
return query.strip()