2013-04-12 15:06:52 -04:00
|
|
|
#!/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/>.
|
|
|
|
|
|
|
|
# NOTE: This module is used as a Singleton
|
|
|
|
|
|
|
|
import os
|
|
|
|
import imp
|
|
|
|
import random
|
|
|
|
import string
|
|
|
|
import sys
|
2013-10-08 19:21:43 -04:00
|
|
|
from threading import Lock
|
2013-09-02 17:45:53 -04:00
|
|
|
from ycm import user_options_store
|
2013-10-08 19:21:43 -04:00
|
|
|
from ycm.server.responses import UnknownExtraConf
|
2013-04-12 15:06:52 -04:00
|
|
|
from fnmatch import fnmatch
|
|
|
|
|
|
|
|
# Constants
|
|
|
|
YCM_EXTRA_CONF_FILENAME = '.ycm_extra_conf.py'
|
|
|
|
|
|
|
|
# Singleton variables
|
|
|
|
_module_for_module_file = {}
|
2013-10-08 19:21:43 -04:00
|
|
|
_module_for_module_file_lock = Lock()
|
2013-04-12 15:06:52 -04:00
|
|
|
_module_file_for_source_file = {}
|
2013-10-08 19:21:43 -04:00
|
|
|
_module_file_for_source_file_lock = Lock()
|
2013-04-12 15:06:52 -04:00
|
|
|
|
2013-10-08 19:21:43 -04:00
|
|
|
|
|
|
|
def Reset():
|
|
|
|
global _module_for_module_file, _module_file_for_source_file
|
|
|
|
_module_for_module_file = {}
|
|
|
|
_module_file_for_source_file = {}
|
2013-09-17 19:34:02 -04:00
|
|
|
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
def ModuleForSourceFile( filename ):
|
2013-10-08 19:21:43 -04:00
|
|
|
return Load( ModuleFileForSourceFile( filename ) )
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
def ModuleFileForSourceFile( filename ):
|
|
|
|
"""This will try all files returned by _ExtraConfModuleSourceFilesForFile 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."""
|
|
|
|
|
2013-10-08 19:21:43 -04:00
|
|
|
with _module_file_for_source_file_lock:
|
|
|
|
if not filename in _module_file_for_source_file:
|
|
|
|
for module_file in _ExtraConfModuleSourceFilesForFile( filename ):
|
|
|
|
if Load( module_file ):
|
|
|
|
_module_file_for_source_file[ filename ] = module_file
|
|
|
|
break
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
return _module_file_for_source_file.setdefault( filename )
|
|
|
|
|
|
|
|
|
2013-04-12 19:15:31 -04:00
|
|
|
def CallExtraConfYcmCorePreloadIfExists():
|
|
|
|
_CallExtraConfMethod( 'YcmCorePreload' )
|
|
|
|
|
|
|
|
|
2013-09-23 17:33:14 -04:00
|
|
|
def Shutdown():
|
|
|
|
# VimClose is for the sake of backwards compatibility; it's a no-op when it
|
|
|
|
# doesn't exist.
|
2013-04-12 19:15:31 -04:00
|
|
|
_CallExtraConfMethod( 'VimClose' )
|
2013-09-23 17:33:14 -04:00
|
|
|
_CallExtraConfMethod( 'Shutdown' )
|
2013-04-12 19:15:31 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _CallExtraConfMethod( function_name ):
|
2013-09-16 17:24:38 -04:00
|
|
|
vim_current_working_directory = os.getcwd()
|
2013-04-12 19:15:31 -04:00
|
|
|
path_to_dummy = os.path.join( vim_current_working_directory, 'DUMMY_FILE' )
|
2013-09-06 02:43:14 -04:00
|
|
|
# The dummy file in the Vim CWD ensures we find the correct extra conf file
|
2013-04-12 19:15:31 -04:00
|
|
|
module = ModuleForSourceFile( path_to_dummy )
|
|
|
|
if not module or not hasattr( module, function_name ):
|
|
|
|
return
|
|
|
|
getattr( module, function_name )()
|
|
|
|
|
|
|
|
|
2013-04-12 15:06:52 -04:00
|
|
|
def _Disable( module_file ):
|
|
|
|
"""Disables the loading of a module for the current session."""
|
2013-10-08 19:21:43 -04:00
|
|
|
with _module_for_module_file_lock:
|
|
|
|
_module_for_module_file[ module_file ] = None
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _ShouldLoad( module_file ):
|
|
|
|
"""Checks if a module is safe to be loaded. By default this will try to
|
|
|
|
decide using a white-/blacklist and ask the user for confirmation as a
|
|
|
|
fallback."""
|
|
|
|
|
2013-09-02 17:45:53 -04:00
|
|
|
if ( module_file == _GlobalYcmExtraConfFileLocation() or
|
|
|
|
not user_options_store.Value( 'confirm_extra_conf' ) ):
|
2013-04-12 15:06:52 -04:00
|
|
|
return True
|
|
|
|
|
2013-09-02 17:45:53 -04:00
|
|
|
globlist = user_options_store.Value( 'extra_conf_globlist' )
|
2013-04-12 15:06:52 -04:00
|
|
|
for glob in globlist:
|
|
|
|
is_blacklisted = glob[0] == '!'
|
|
|
|
if _MatchesGlobPattern( module_file, glob.lstrip('!') ):
|
|
|
|
return not is_blacklisted
|
|
|
|
|
2013-10-08 19:21:43 -04:00
|
|
|
# We disable the file if it's unknown so that we don't ask the user about it
|
|
|
|
# repeatedly. Raising UnknownExtraConf should result in the client sending
|
|
|
|
# another request to load the module file if the user explicitly chooses to do
|
|
|
|
# that.
|
|
|
|
_Disable( module_file )
|
2013-09-17 19:34:02 -04:00
|
|
|
raise UnknownExtraConf( module_file )
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
|
2013-10-08 19:21:43 -04:00
|
|
|
def Load( module_file, force = False ):
|
2013-04-12 15:06:52 -04:00
|
|
|
"""Load and return the module contained in a file.
|
|
|
|
Using force = True the module will be loaded regardless
|
|
|
|
of the criteria in _ShouldLoad.
|
|
|
|
This will return None if the module was not allowed to be loaded."""
|
|
|
|
|
|
|
|
if not module_file:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if not force:
|
2013-10-08 19:21:43 -04:00
|
|
|
with _module_for_module_file_lock:
|
|
|
|
if module_file in _module_for_module_file:
|
|
|
|
return _module_for_module_file[ module_file ]
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
if not _ShouldLoad( module_file ):
|
2013-10-08 19:21:43 -04:00
|
|
|
_Disable( module_file )
|
|
|
|
return None
|
2013-04-12 15:06:52 -04:00
|
|
|
|
2013-05-19 13:48:23 -04:00
|
|
|
# This has to be here because a long time ago, the ycm_extra_conf.py files
|
|
|
|
# used to import clang_helpers.py from the cpp folder. This is not needed
|
|
|
|
# anymore, but there are a lot of old ycm_extra_conf.py files that we don't
|
|
|
|
# want to break.
|
2013-04-12 15:06:52 -04:00
|
|
|
sys.path.insert( 0, _PathToCppCompleterFolder() )
|
|
|
|
module = imp.load_source( _RandomName(), module_file )
|
|
|
|
del sys.path[ 0 ]
|
|
|
|
|
2013-10-08 19:21:43 -04:00
|
|
|
with _module_for_module_file_lock:
|
|
|
|
_module_for_module_file[ module_file ] = module
|
2013-04-12 15:06:52 -04:00
|
|
|
return module
|
|
|
|
|
|
|
|
|
|
|
|
def _MatchesGlobPattern( filename, glob ):
|
|
|
|
"""Returns true if a filename matches a given pattern. A '~' in glob will be
|
|
|
|
expanded to the home directory and checking will be performed using absolute
|
|
|
|
paths. See the documentation of fnmatch for the supported patterns."""
|
|
|
|
|
|
|
|
abspath = os.path.abspath( filename )
|
|
|
|
return fnmatch( abspath, os.path.abspath( os.path.expanduser( glob ) ) )
|
|
|
|
|
|
|
|
|
|
|
|
def _ExtraConfModuleSourceFilesForFile( 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.
|
2013-09-02 17:45:53 -04:00
|
|
|
If _GlobalYcmExtraConfFileLocation() exists it is returned as a fallback."""
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
for folder in _PathsToAllParentFolders( filename ):
|
|
|
|
candidate = os.path.join( folder, YCM_EXTRA_CONF_FILENAME )
|
|
|
|
if os.path.exists( candidate ):
|
|
|
|
yield candidate
|
2013-09-02 17:45:53 -04:00
|
|
|
global_ycm_extra_conf = _GlobalYcmExtraConfFileLocation()
|
|
|
|
if ( global_ycm_extra_conf
|
|
|
|
and os.path.exists( global_ycm_extra_conf ) ):
|
|
|
|
yield global_ycm_extra_conf
|
2013-04-12 15:06:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _PathsToAllParentFolders( filename ):
|
|
|
|
"""Build a list of all parent folders of a file.
|
2013-08-25 13:44:45 -04:00
|
|
|
The nearest folders will be returned first.
|
2013-04-12 15:06:52 -04:00
|
|
|
Example: _PathsToAllParentFolders( '/home/user/projects/test.c' )
|
|
|
|
[ '/home/user/projects', '/home/user', '/home', '/' ]"""
|
|
|
|
|
2013-08-25 13:44:45 -04:00
|
|
|
def PathFolderComponents( filename ):
|
|
|
|
folders = []
|
|
|
|
path = os.path.dirname( filename )
|
|
|
|
while True:
|
|
|
|
path, folder = os.path.split( path )
|
|
|
|
if folder:
|
|
|
|
folders.append( folder )
|
|
|
|
else:
|
|
|
|
if path:
|
|
|
|
folders.append( path )
|
|
|
|
break
|
|
|
|
return list( reversed( folders ) )
|
|
|
|
|
|
|
|
parent_folders = PathFolderComponents( filename )
|
2013-04-12 15:06:52 -04:00
|
|
|
parent_folders = [ os.path.join( *parent_folders[:i + 1] )
|
|
|
|
for i in xrange( len( parent_folders ) ) ]
|
|
|
|
return reversed( parent_folders )
|
|
|
|
|
|
|
|
|
|
|
|
def _PathToCppCompleterFolder():
|
|
|
|
"""Returns the path to the 'cpp' completer folder. This is necessary
|
|
|
|
because ycm_extra_conf files need it on the path."""
|
|
|
|
return os.path.join( _DirectoryOfThisScript(), 'completers', 'cpp' )
|
|
|
|
|
|
|
|
|
|
|
|
def _DirectoryOfThisScript():
|
|
|
|
return os.path.dirname( os.path.abspath( __file__ ) )
|
|
|
|
|
|
|
|
|
|
|
|
def _RandomName():
|
|
|
|
"""Generates a random module name."""
|
|
|
|
return ''.join( random.choice( string.ascii_lowercase ) for x in range( 15 ) )
|
2013-09-02 17:45:53 -04:00
|
|
|
|
|
|
|
|
|
|
|
def _GlobalYcmExtraConfFileLocation():
|
|
|
|
return os.path.expanduser(
|
|
|
|
user_options_store.Value( 'global_ycm_extra_conf' ) )
|