diff --git a/python/completers/cpp/clang_completer.py b/python/completers/cpp/clang_completer.py
index dad9792d..11f3db51 100644
--- a/python/completers/cpp/clang_completer.py
+++ b/python/completers/cpp/clang_completer.py
@@ -22,6 +22,7 @@ from collections import defaultdict
import vim
import vimsupport
import ycm_core
+import extra_conf_store
from flags import Flags
CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] )
@@ -287,7 +288,7 @@ class ClangCompleter( Completer ):
def DebugInfo( self ):
filename = vim.current.buffer.name
flags = self.flags.FlagsForFile( filename ) or []
- source = self.flags.ModuleForFile( filename )
+ source = extra_conf_store.ModuleFileForSourceFile( filename )
return 'Flags for {0} loaded from {1}:\n{2}'.format( filename, source, list( flags ) )
diff --git a/python/completers/cpp/flags.py b/python/completers/cpp/flags.py
index 4d5e8d46..15aae8d5 100644
--- a/python/completers/cpp/flags.py
+++ b/python/completers/cpp/flags.py
@@ -17,24 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe. If not, see .
-import imp
-import os
import ycm_core
-import random
-import string
-import sys
+import os
import vimsupport
-from fnmatch import fnmatch
+import extra_conf_store
-YCM_EXTRA_CONF_FILENAME = '.ycm_extra_conf.py'
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 '
- 'docs for details.').format( YCM_EXTRA_CONF_FILENAME )
-CONFIRM_CONF_FILE_MESSAGE = ('Found {0}. Load? \n\n(Question can be turned '
- 'off with options, see YCM docs)')
-GLOBAL_YCM_EXTRA_CONF_FILE = os.path.expanduser(
- vimsupport.GetVariableValue( "g:ycm_global_ycm_extra_conf" )
-)
+ 'docs for details.').format( extra_conf_store.YCM_EXTRA_CONF_FILENAME )
class Flags( object ):
"""Keeps track of the flags necessary to compile a file.
@@ -44,36 +34,22 @@ class Flags( object ):
def __init__( self ):
# It's caches all the way down...
self.flags_for_file = {}
- self.module_for_file = {}
- self.modules = FlagsModules()
self.special_clang_flags = _SpecialClangIncludes()
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 ):
try:
return self.flags_for_file[ filename ]
except KeyError:
- module_file = self.ModuleForFile( filename )
- if not module_file:
+ module = extra_conf_store.ModuleForSourceFile( filename )
+ if not module:
if not self.no_extra_conf_file_warning_posted:
vimsupport.PostVimMessage( NO_EXTRA_CONF_FILENAME_MESSAGE )
self.no_extra_conf_file_warning_posted = True
return None
- results = self.modules[ module_file ].FlagsForFile( filename )
+ results = module.FlagsForFile( filename )
if not results.get( 'flags_ready', True ):
return None
@@ -85,113 +61,6 @@ class Flags( object ):
self.flags_for_file[ filename ] = sanitized_flags
return sanitized_flags
- def ReloadModule( self, module_file ):
- """Reloads a module file cleaning the flags cache for all files
- associated with that module. Returns False if reloading failed
- (for example due to the model not being loaded in the first place)."""
-
- module_file = os.path.abspath(module_file)
- if self.modules.Reload( module_file ):
- for filename, module in self.module_for_file.iteritems():
- if module == module_file:
- del self.flags_for_file[ filename ]
- return True
- return False
-
-
-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 Disable( self, module_file ):
- """Disables the loading of a module for the current session."""
- self.modules[ module_file ] = None
-
- @staticmethod
- 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."""
-
- if ( module_file == GLOBAL_YCM_EXTRA_CONF_FILE or
- not vimsupport.GetBoolValue( 'g:ycm_confirm_extra_conf' ) ):
- return True
-
- globlist = vimsupport.GetVariableValue( 'g:ycm_extra_conf_globlist' )
- for glob in globlist:
- is_blacklisted = glob[0] == '!'
- if MatchesGlobPattern( module_file, glob.lstrip('!') ):
- return not is_blacklisted
-
- return vimsupport.Confirm( CONFIRM_CONF_FILE_MESSAGE.format( module_file ) )
-
- def Load( self, module_file, force = False ):
- """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 force:
- if self.modules.has_key( module_file ):
- return self.modules[ module_file ]
-
- if not self.ShouldLoad( module_file ):
- return self.Disable( module_file )
-
- sys.path.insert( 0, _DirectoryOfThisScript() )
- module = imp.load_source( _RandomName(), module_file )
- del sys.path[ 0 ]
-
- 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():
- """Generates a random module name."""
- return ''.join( random.choice( string.ascii_lowercase ) for x in range( 15 ) )
-
def _SanitizeFlags( flags ):
"""Drops unsafe flags. Currently these are only -arch flags; they tend to
@@ -223,14 +92,3 @@ def _SpecialClangIncludes():
return [ '-I', path_to_includes ]
-def _DirectoryOfThisScript():
- return os.path.dirname( os.path.abspath( __file__ ) )
-
-
-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 ) ) )
diff --git a/python/extra_conf_store.py b/python/extra_conf_store.py
new file mode 100644
index 00000000..3eb7aac5
--- /dev/null
+++ b/python/extra_conf_store.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2011, 2012 Strahinja Val Markovic
+#
+# 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 .
+
+# NOTE: This module is used as a Singleton
+
+import os
+import imp
+import random
+import string
+import sys
+import vimsupport
+from fnmatch import fnmatch
+
+# Constants
+YCM_EXTRA_CONF_FILENAME = '.ycm_extra_conf.py'
+CONFIRM_CONF_FILE_MESSAGE = ('Found {0}. Load? \n\n(Question can be turned '
+ 'off with options, see YCM docs)')
+GLOBAL_YCM_EXTRA_CONF_FILE = os.path.expanduser(
+ vimsupport.GetVariableValue( "g:ycm_global_ycm_extra_conf" )
+)
+
+# Singleton variables
+_module_for_module_file = {}
+_module_file_for_source_file = {}
+
+
+def ModuleForSourceFile( filename ):
+ return _Load( ModuleFileForSourceFile( filename ) )
+
+
+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."""
+
+ if not _module_file_for_source_file.has_key( filename ):
+ for module_file in _ExtraConfModuleSourceFilesForFile( filename ):
+ if _Load( module_file ):
+ _module_file_for_source_file[ filename ] = module_file
+ break
+
+ return _module_file_for_source_file.setdefault( filename )
+
+
+def _Disable( module_file ):
+ """Disables the loading of a module for the current session."""
+ _module_for_module_file[ module_file ] = None
+
+
+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."""
+
+ if ( module_file == GLOBAL_YCM_EXTRA_CONF_FILE or
+ not vimsupport.GetBoolValue( 'g:ycm_confirm_extra_conf' ) ):
+ return True
+
+ globlist = vimsupport.GetVariableValue( 'g:ycm_extra_conf_globlist' )
+ for glob in globlist:
+ is_blacklisted = glob[0] == '!'
+ if _MatchesGlobPattern( module_file, glob.lstrip('!') ):
+ return not is_blacklisted
+
+ return vimsupport.Confirm( CONFIRM_CONF_FILE_MESSAGE.format( module_file ) )
+
+
+def _Load( module_file, force = False ):
+ """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:
+ if _module_for_module_file.has_key( module_file ):
+ return _module_for_module_file[ module_file ]
+
+ if not _ShouldLoad( module_file ):
+ return _Disable( module_file )
+
+ sys.path.insert( 0, _PathToCppCompleterFolder() )
+ module = imp.load_source( _RandomName(), module_file )
+ del sys.path[ 0 ]
+
+ _module_for_module_file[ module_file ] = module
+ 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.
+ 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 _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 ) )