filename_completer.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. #!/usr/bin/env python
  2. #
  3. # Copyright (C) 2013 Stanislav Golovanov <stgolovanov@gmail.com>
  4. # Strahinja Val Markovic <val@markovic.io>
  5. #
  6. # YouCompleteMe is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # YouCompleteMe is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  18. import vim
  19. import os
  20. import re
  21. from ycm import vimsupport
  22. from ycm.completers.threaded_completer import ThreadedCompleter
  23. from ycm.completers.cpp.clang_completer import InCFamilyFile
  24. from ycm.completers.cpp.flags import Flags
  25. USE_WORKING_DIR = vimsupport.GetBoolValue(
  26. 'g:ycm_filepath_completion_use_working_dir' )
  27. class FilenameCompleter( ThreadedCompleter ):
  28. """
  29. General completer that provides filename and filepath completions.
  30. """
  31. def __init__( self ):
  32. super( FilenameCompleter, self ).__init__()
  33. self._flags = Flags()
  34. self._path_regex = re.compile( """
  35. # 1 or more 'D:/'-like token or '/' or '~' or './' or '../'
  36. (?:[A-z]+:/|[/~]|\./|\.+/)+
  37. # any alphanumeric symbal and space literal
  38. (?:[ /a-zA-Z0-9()$+_~.\x80-\xff-\[\]]|
  39. # skip any special symbols
  40. [^\x20-\x7E]|
  41. # backslash and 1 char after it. + matches 1 or more of whole group
  42. \\.)*$
  43. """, re.X )
  44. include_regex_common = '^\s*#(?:include|import)\s*(?:"|<)'
  45. self._include_start_regex = re.compile( include_regex_common + '$' )
  46. self._include_regex = re.compile( include_regex_common )
  47. def AtIncludeStatementStart( self, start_column ):
  48. return ( InCFamilyFile() and
  49. self._include_start_regex.match(
  50. vim.current.line[ :start_column ] ) )
  51. def ShouldUseNowInner( self, start_column ):
  52. return ( start_column and ( vim.current.line[ start_column - 1 ] == '/' or
  53. self.AtIncludeStatementStart( start_column ) ) )
  54. def SupportedFiletypes( self ):
  55. return []
  56. def ComputeCandidates( self, unused_query, start_column ):
  57. line = vim.current.line[ :start_column ]
  58. if InCFamilyFile():
  59. include_match = self._include_regex.search( line )
  60. if include_match:
  61. path_dir = line[ include_match.end(): ]
  62. # We do what GCC does for <> versus "":
  63. # http://gcc.gnu.org/onlinedocs/cpp/Include-Syntax.html
  64. include_current_file_dir = '<' not in include_match.group()
  65. return GenerateCandidatesForPaths(
  66. self.GetPathsIncludeCase( path_dir, include_current_file_dir ) )
  67. path_match = self._path_regex.search( line )
  68. path_dir = os.path.expanduser( path_match.group() ) if path_match else ''
  69. return GenerateCandidatesForPaths( GetPathsStandardCase( path_dir ) )
  70. def GetPathsIncludeCase( self, path_dir, include_current_file_dir ):
  71. paths = []
  72. include_paths = self._flags.UserIncludePaths( vim.current.buffer.name )
  73. if include_current_file_dir:
  74. include_paths.append( os.path.dirname( vim.current.buffer.name ) )
  75. for include_path in include_paths:
  76. try:
  77. relative_paths = os.listdir( os.path.join( include_path, path_dir ) )
  78. except:
  79. relative_paths = []
  80. paths.extend( os.path.join( include_path, path_dir, relative_path ) for
  81. relative_path in relative_paths )
  82. return sorted( set( paths ) )
  83. def GetPathsStandardCase( path_dir ):
  84. if not USE_WORKING_DIR and not path_dir.startswith( '/' ):
  85. path_dir = os.path.join( os.path.dirname( vim.current.buffer.name ),
  86. path_dir )
  87. try:
  88. relative_paths = os.listdir( path_dir )
  89. except:
  90. relative_paths = []
  91. return ( os.path.join( path_dir, relative_path )
  92. for relative_path in relative_paths )
  93. def GenerateCandidatesForPaths( absolute_paths ):
  94. def GenerateCandidateForPath( absolute_path ):
  95. is_dir = os.path.isdir( absolute_path )
  96. return { 'word': os.path.basename( absolute_path ),
  97. 'dup': 1,
  98. 'menu': '[Dir]' if is_dir else '[File]' }
  99. return [ GenerateCandidateForPath( path ) for path in absolute_paths ]