extra_conf_store.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2011, 2012 Strahinja Val Markovic <val@markovic.io>
  4. #
  5. # This file is part of YouCompleteMe.
  6. #
  7. # YouCompleteMe is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # YouCompleteMe is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  19. # NOTE: This module is used as a Singleton
  20. import os
  21. import imp
  22. import random
  23. import string
  24. import sys
  25. import vim
  26. from ycm import vimsupport
  27. from ycm import user_options_store
  28. from fnmatch import fnmatch
  29. # Constants
  30. YCM_EXTRA_CONF_FILENAME = '.ycm_extra_conf.py'
  31. CONFIRM_CONF_FILE_MESSAGE = ('Found {0}. Load? \n\n(Question can be turned '
  32. 'off with options, see YCM docs)')
  33. # Singleton variables
  34. _module_for_module_file = {}
  35. _module_file_for_source_file = {}
  36. def ModuleForSourceFile( filename ):
  37. return _Load( ModuleFileForSourceFile( filename ) )
  38. def ModuleFileForSourceFile( filename ):
  39. """This will try all files returned by _ExtraConfModuleSourceFilesForFile in
  40. order and return the filename of the first module that was allowed to load.
  41. If no module was found or allowed to load, None is returned."""
  42. if not filename in _module_file_for_source_file:
  43. for module_file in _ExtraConfModuleSourceFilesForFile( filename ):
  44. if _Load( module_file ):
  45. _module_file_for_source_file[ filename ] = module_file
  46. break
  47. return _module_file_for_source_file.setdefault( filename )
  48. def CallExtraConfYcmCorePreloadIfExists():
  49. _CallExtraConfMethod( 'YcmCorePreload' )
  50. def CallExtraConfVimCloseIfExists():
  51. _CallExtraConfMethod( 'VimClose' )
  52. def _CallExtraConfMethod( function_name ):
  53. vim_current_working_directory = vim.eval( 'getcwd()' )
  54. path_to_dummy = os.path.join( vim_current_working_directory, 'DUMMY_FILE' )
  55. # The dummy file in the Vim CWD ensures we find the correct extra conf file
  56. module = ModuleForSourceFile( path_to_dummy )
  57. if not module or not hasattr( module, function_name ):
  58. return
  59. getattr( module, function_name )()
  60. def _Disable( module_file ):
  61. """Disables the loading of a module for the current session."""
  62. _module_for_module_file[ module_file ] = None
  63. def _ShouldLoad( module_file ):
  64. """Checks if a module is safe to be loaded. By default this will try to
  65. decide using a white-/blacklist and ask the user for confirmation as a
  66. fallback."""
  67. if ( module_file == _GlobalYcmExtraConfFileLocation() or
  68. not user_options_store.Value( 'confirm_extra_conf' ) ):
  69. return True
  70. globlist = user_options_store.Value( 'extra_conf_globlist' )
  71. for glob in globlist:
  72. is_blacklisted = glob[0] == '!'
  73. if _MatchesGlobPattern( module_file, glob.lstrip('!') ):
  74. return not is_blacklisted
  75. return vimsupport.Confirm( CONFIRM_CONF_FILE_MESSAGE.format( module_file ) )
  76. def _Load( module_file, force = False ):
  77. """Load and return the module contained in a file.
  78. Using force = True the module will be loaded regardless
  79. of the criteria in _ShouldLoad.
  80. This will return None if the module was not allowed to be loaded."""
  81. if not module_file:
  82. return None
  83. if not force:
  84. if module_file in _module_for_module_file:
  85. return _module_for_module_file[ module_file ]
  86. if not _ShouldLoad( module_file ):
  87. return _Disable( module_file )
  88. # This has to be here because a long time ago, the ycm_extra_conf.py files
  89. # used to import clang_helpers.py from the cpp folder. This is not needed
  90. # anymore, but there are a lot of old ycm_extra_conf.py files that we don't
  91. # want to break.
  92. sys.path.insert( 0, _PathToCppCompleterFolder() )
  93. module = imp.load_source( _RandomName(), module_file )
  94. del sys.path[ 0 ]
  95. _module_for_module_file[ module_file ] = module
  96. return module
  97. def _MatchesGlobPattern( filename, glob ):
  98. """Returns true if a filename matches a given pattern. A '~' in glob will be
  99. expanded to the home directory and checking will be performed using absolute
  100. paths. See the documentation of fnmatch for the supported patterns."""
  101. abspath = os.path.abspath( filename )
  102. return fnmatch( abspath, os.path.abspath( os.path.expanduser( glob ) ) )
  103. def _ExtraConfModuleSourceFilesForFile( filename ):
  104. """For a given filename, search all parent folders for YCM_EXTRA_CONF_FILENAME
  105. files that will compute the flags necessary to compile the file.
  106. If _GlobalYcmExtraConfFileLocation() exists it is returned as a fallback."""
  107. for folder in _PathsToAllParentFolders( filename ):
  108. candidate = os.path.join( folder, YCM_EXTRA_CONF_FILENAME )
  109. if os.path.exists( candidate ):
  110. yield candidate
  111. global_ycm_extra_conf = _GlobalYcmExtraConfFileLocation()
  112. if ( global_ycm_extra_conf
  113. and os.path.exists( global_ycm_extra_conf ) ):
  114. yield global_ycm_extra_conf
  115. def _PathsToAllParentFolders( filename ):
  116. """Build a list of all parent folders of a file.
  117. The nearest folders will be returned first.
  118. Example: _PathsToAllParentFolders( '/home/user/projects/test.c' )
  119. [ '/home/user/projects', '/home/user', '/home', '/' ]"""
  120. def PathFolderComponents( filename ):
  121. folders = []
  122. path = os.path.dirname( filename )
  123. while True:
  124. path, folder = os.path.split( path )
  125. if folder:
  126. folders.append( folder )
  127. else:
  128. if path:
  129. folders.append( path )
  130. break
  131. return list( reversed( folders ) )
  132. parent_folders = PathFolderComponents( filename )
  133. parent_folders = [ os.path.join( *parent_folders[:i + 1] )
  134. for i in xrange( len( parent_folders ) ) ]
  135. return reversed( parent_folders )
  136. def _PathToCppCompleterFolder():
  137. """Returns the path to the 'cpp' completer folder. This is necessary
  138. because ycm_extra_conf files need it on the path."""
  139. return os.path.join( _DirectoryOfThisScript(), 'completers', 'cpp' )
  140. def _DirectoryOfThisScript():
  141. return os.path.dirname( os.path.abspath( __file__ ) )
  142. def _RandomName():
  143. """Generates a random module name."""
  144. return ''.join( random.choice( string.ascii_lowercase ) for x in range( 15 ) )
  145. def _GlobalYcmExtraConfFileLocation():
  146. return os.path.expanduser(
  147. user_options_store.Value( 'global_ycm_extra_conf' ) )