Merge branch 'master' of github.com:kljohann/YouCompleteMe into kljohann-master

This commit is contained in:
Strahinja Val Markovic 2013-02-26 18:21:46 -08:00
commit 95b9eeee18
6 changed files with 157 additions and 53 deletions

View File

@ -322,6 +322,10 @@ method in that module which should provide it with the information necessary to
compile the current file. (You can also provide a path to a global compile the current file. (You can also provide a path to a global
`.ycm_extra_conf.py` file, which will be used as a fallback. See the Options `.ycm_extra_conf.py` file, which will be used as a fallback. See the Options
section for more details.) section for more details.)
To prevent the execution of malicious code from a file you didn't write
YCM will ask once per module if it is safe to be loaded.
(This can be disabled. See the Options section.)
This system was designed this way so that the user can perform any arbitrary This system was designed this way so that the user can perform any arbitrary
sequence of operations to produce a list of compilation flags YCM should hand sequence of operations to produce a list of compilation flags YCM should hand
@ -657,6 +661,16 @@ Default: `''`
let g:ycm_global_ycm_extra_conf = '' let g:ycm_global_ycm_extra_conf = ''
### The `g:ycm_confirm_extra_conf` option
When this option is set to `1` YCM will ask once per '.ycm_extra_conf.py' file
if it is safe to be loaded. This is to prevent execution of malicious code
from a '.ycm_extra_conf.py' file you didn't write.
Default: `1`
let g:ycm_confirm_extra_conf = 1
### The `g:ycm_semantic_triggers` option ### The `g:ycm_semantic_triggers` option
This option controls the character-based triggers for the various semantic This option controls the character-based triggers for the various semantic

View File

@ -101,6 +101,9 @@ let g:ycm_key_detailed_diagnostics =
let g:ycm_global_ycm_extra_conf = let g:ycm_global_ycm_extra_conf =
\ get( g:, 'ycm_global_ycm_extra_conf', '' ) \ get( g:, 'ycm_global_ycm_extra_conf', '' )
let g:ycm_confirm_extra_conf =
\ get( g:, 'ycm_confirm_extra_conf', 1 )
let g:ycm_semantic_triggers = let g:ycm_semantic_triggers =
\ get( g:, 'ycm_semantic_triggers', { \ get( g:, 'ycm_semantic_triggers', {
\ 'c' : ['->', '.'], \ 'c' : ['->', '.'],

View File

@ -87,8 +87,8 @@ class IdentifierCompleter( Completer ):
def AddBufferIdentifiers( self ): def AddBufferIdentifiers( self ):
filetype = vim.eval( "&filetype" ) filetype = vim.eval( "&filetype" )
filepath = vim.eval( "expand('%:p')" ) filepath = vim.eval( "expand('%:p')" )
collect_from_comments_and_strings = bool( int( vimsupport.GetVariableValue( collect_from_comments_and_strings = vimsupport.GetBoolValue(
"g:ycm_collect_identifiers_from_comments_and_strings" ) ) ) "g:ycm_collect_identifiers_from_comments_and_strings" )
if not filetype or not filepath: if not filetype or not filepath:
return return

View File

@ -206,7 +206,8 @@ class ClangCompleter( Completer ):
def DebugInfo( self ): def DebugInfo( self ):
filename = vim.current.buffer.name filename = vim.current.buffer.name
flags = self.flags.FlagsForFile( filename ) or [] flags = self.flags.FlagsForFile( filename ) or []
return 'Flags for {0}:\n{1}'.format( filename, list( flags ) ) source = self.flags.ModuleForFile( filename )
return 'Flags for {0} loaded from {1}:\n{2}'.format( filename, source, list( flags ) )
# TODO: make these functions module-local # TODO: make these functions module-local

View File

@ -29,32 +29,49 @@ YCM_EXTRA_CONF_FILENAME = '.ycm_extra_conf.py'
NO_EXTRA_CONF_FILENAME_MESSAGE = ('No {0} file detected, so no compile flags ' NO_EXTRA_CONF_FILENAME_MESSAGE = ('No {0} file detected, so no compile flags '
'are available. Thus no semantic support for C/C++/ObjC/ObjC++. See the ' 'are available. Thus no semantic support for C/C++/ObjC/ObjC++. See the '
'docs for details.').format( YCM_EXTRA_CONF_FILENAME ) 'docs for details.').format( YCM_EXTRA_CONF_FILENAME )
CONFIRM_CONF_FILE_MESSAGE = 'Found {0}. Load?'
GLOBAL_YCM_EXTRA_CONF_FILE = os.path.expanduser( GLOBAL_YCM_EXTRA_CONF_FILE = os.path.expanduser(
vimsupport.GetVariableValue( "g:ycm_global_ycm_extra_conf" ) vimsupport.GetVariableValue( "g:ycm_global_ycm_extra_conf" )
) )
class Flags( object ): class Flags( object ):
"""Keeps track of the flags necessary to compile a file.
The flags are loaded from user-created python files
(hereafter referred to as 'modules') that contain
a method FlagsForFile( filename )."""
def __init__( self ): def __init__( self ):
# It's caches all the way down... # It's caches all the way down...
self.flags_for_file = {} self.flags_for_file = {}
self.flags_module_for_file = {} self.module_for_file = {}
self.flags_module_for_flags_module_file = {} self.modules = FlagsModules()
self.special_clang_flags = _SpecialClangIncludes() self.special_clang_flags = _SpecialClangIncludes()
self.no_extra_conf_file_warning_posted = False self.no_extra_conf_file_warning_posted = False
def ModuleForFile( self, filename ):
"""This will try all files returned by _FlagsModuleSourceFilesForFile in
order and return the filename of the first module that was allowed to load.
If no module was found or allowed to load, None is returned."""
if not self.module_for_file.has_key( filename ):
for flags_module_file in _FlagsModuleSourceFilesForFile( filename ):
if self.modules.Load( flags_module_file ):
self.module_for_file[ filename ] = flags_module_file
break
return self.module_for_file.setdefault( filename )
def FlagsForFile( self, filename ): def FlagsForFile( self, filename ):
try: try:
return self.flags_for_file[ filename ] return self.flags_for_file[ filename ]
except KeyError: except KeyError:
flags_module = self._FlagsModuleForFile( filename ) module_file = self.ModuleForFile( filename )
if not flags_module: if not module_file:
if not self.no_extra_conf_file_warning_posted: if not self.no_extra_conf_file_warning_posted:
vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE ) vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE )
self.no_extra_conf_file_warning_posted = True self.no_extra_conf_file_warning_posted = True
return None return None
results = flags_module.FlagsForFile( filename ) results = self.modules[ module_file ].FlagsForFile( filename )
if not results.get( 'flags_ready', True ): if not results.get( 'flags_ready', True ):
return None return None
@ -66,58 +83,95 @@ class Flags( object ):
self.flags_for_file[ filename ] = sanitized_flags self.flags_for_file[ filename ] = sanitized_flags
return sanitized_flags return sanitized_flags
def ReloadModule( self, module_file ):
def _FlagsModuleForFile( self, filename ): """Reloads a module file cleaning the flags cache for all files
try: associated with that module. Returns False if reloading failed
return self.flags_module_for_file[ filename ] (for example due to the model not being loaded in the first place)."""
except KeyError: module_file = os.path.abspath(module_file)
flags_module_file = _FlagsModuleSourceFileForFile( filename ) if self.modules.Reload( module_file ):
if not flags_module_file: for filename, module in self.module_for_file.iteritems():
return None if module == module_file:
del self.flags_for_file[ filename ]
try: return True
flags_module = self.flags_module_for_flags_module_file[ return False
flags_module_file ]
except KeyError:
sys.path.insert( 0, _DirectoryOfThisScript() )
flags_module = imp.load_source( _RandomName(), flags_module_file )
del sys.path[ 0 ]
self.flags_module_for_flags_module_file[
flags_module_file ] = flags_module
self.flags_module_for_file[ filename ] = flags_module
return flags_module
class FlagsModules( object ):
"""Keeps track of modules.
Modules are loaded on-demand and cached in self.modules for quick access."""
def __init__( self ):
self.modules = {}
def _FlagsModuleSourceFileForFile( filename ): def Disable( self, module_file ):
"""For a given filename, finds its nearest YCM_EXTRA_CONF_FILENAME file that """Disables the loading of a module for the current session."""
will compute the flags necessary to compile the file. If no self.modules[ module_file ] = None
YCM_EXTRA_CONF_FILENAME file could be found, try to use
GLOBAL_YCM_EXTRA_CONF_FILE instead. If that also fails, return None.
Uses the global ycm_extra_conf file if one is set."""
ycm_conf_file = None @staticmethod
parent_folder = os.path.dirname( filename ) def ShouldLoad( module_file ):
old_parent_folder = '' """Checks if a module is safe to be loaded.
By default this will ask the user for confirmation."""
if module_file == GLOBAL_YCM_EXTRA_CONF_FILE:
return True
if ( vimsupport.GetBoolValue( 'g:ycm_confirm_extra_conf' ) and
not vimsupport.Confirm(
CONFIRM_CONF_FILE_MESSAGE.format( module_file ) ) ):
return False
return True
while True: def Load( self, module_file, force = False ):
current_file = os.path.join( parent_folder, YCM_EXTRA_CONF_FILENAME ) """Load and return the module contained in a file.
if os.path.exists( current_file ): Using force = True the module will be loaded regardless
ycm_conf_file = current_file of the criteria in ShouldLoad.
break This will return None if the module was not allowed to be loaded."""
if not force:
if self.modules.has_key( module_file ):
return self.modules[ module_file ]
old_parent_folder = parent_folder if not self.ShouldLoad( module_file ):
parent_folder = os.path.dirname( parent_folder ) return self.Disable( module_file )
if parent_folder is old_parent_folder:
break
if ( not ycm_conf_file and GLOBAL_YCM_EXTRA_CONF_FILE and sys.path.insert( 0, _DirectoryOfThisScript() )
os.path.exists( GLOBAL_YCM_EXTRA_CONF_FILE ) ): module = imp.load_source( _RandomName(), module_file )
ycm_conf_file = GLOBAL_YCM_EXTRA_CONF_FILE del sys.path[ 0 ]
return ycm_conf_file self.modules[ module_file ] = module
return module
def Reload( self, module_file ):
"""Reloads the given module. If it has not been loaded yet does nothing.
Note that the module will not be subject to the loading criteria again."""
if self.modules.get( module_file ):
return self.Load( module_file, force = True )
def __getitem__( self, key ):
return self.Load( key )
def _FlagsModuleSourceFilesForFile( filename ):
"""For a given filename, search all parent folders for YCM_EXTRA_CONF_FILENAME
files that will compute the flags necessary to compile the file.
If GLOBAL_YCM_EXTRA_CONF_FILE exists it is returned as a fallback."""
for folder in _PathsToAllParentFolders( filename ):
candidate = os.path.join( folder, YCM_EXTRA_CONF_FILENAME )
if os.path.exists( candidate ):
yield candidate
if ( GLOBAL_YCM_EXTRA_CONF_FILE
and os.path.exists( GLOBAL_YCM_EXTRA_CONF_FILE ) ):
yield GLOBAL_YCM_EXTRA_CONF_FILE
def _PathsToAllParentFolders( filename ):
"""Build a list of all parent folders of a file.
The neares files will be returned first.
Example: _PathsToAllParentFolders( '/home/user/projects/test.c' )
[ '/home/user/projects', '/home/user', '/home', '/' ]"""
parent_folders = os.path.abspath(
os.path.dirname( filename ) ).split( os.path.sep )
if not parent_folders[0]:
parent_folders[0] = os.path.sep
parent_folders = [ os.path.join( *parent_folders[:i + 1] )
for i in xrange( len( parent_folders ) ) ]
return reversed( parent_folders )
def _RandomName(): def _RandomName():

View File

@ -44,7 +44,7 @@ def CurrentColumn():
def GetUnsavedBuffers(): def GetUnsavedBuffers():
def BufferModified( buffer_number ): def BufferModified( buffer_number ):
to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number ) to_eval = 'getbufvar({0}, "&mod")'.format( buffer_number )
return bool( int( vim.eval( to_eval ) ) ) return GetBoolValue( to_eval )
return ( x for x in vim.buffers if BufferModified( x.number ) ) return ( x for x in vim.buffers if BufferModified( x.number ) )
@ -59,6 +59,34 @@ def PostVimMessage( message ):
.format( EscapeForVim( message ) ) ) .format( EscapeForVim( message ) ) )
def PresentDialog( message, choices, default_choice_index = 0 ):
"""Presents the user with a dialog where a choice can be made.
This will be a dialog for gvim users or a question in the message buffer
for vim users or if `set guioptions+=c` was used.
choices is list of alternatives.
default_choice_index is the 0-based index of the default element
that will get choosen if the user hits <CR>. Use -1 for no default.
PresentDialog will return a 0-based index into the list
or -1 if the dialog was dismissed by using <Esc>, Ctrl-C, etc.
See also:
:help confirm() in vim (Note that vim uses 1-based indexes)
Example call:
PresentDialog("Is this a nice example?", ["Yes", "No", "May&be"])
Is this a nice example?
[Y]es, (N)o, May(b)e:"""
to_eval = "confirm('{0}', '{1}', {2})".format( EscapeForVim( message ),
EscapeForVim( "\n" .join( choices ) ), default_choice_index + 1 )
return int( vim.eval( to_eval ) ) - 1
def Confirm( message ):
return bool( PresentDialog( message, [ "Ok", "Cancel" ] ) == 0 )
def EchoText( text ): def EchoText( text ):
def EchoLine( text ): def EchoLine( text ):
vim.command( "echom '{0}'".format( EscapeForVim( text ) ) ) vim.command( "echom '{0}'".format( EscapeForVim( text ) ) )
@ -78,3 +106,7 @@ def CurrentFiletypes():
def GetVariableValue( variable ): def GetVariableValue( variable ):
return vim.eval( variable ) return vim.eval( variable )
def GetBoolValue( variable ):
return bool( int( vim.eval( variable ) ) )