base.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. # Copyright (C) 2011, 2012 Google Inc.
  2. #
  3. # This file is part of YouCompleteMe.
  4. #
  5. # YouCompleteMe is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # YouCompleteMe is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
  17. import os
  18. import json
  19. from ycm import vimsupport, paths
  20. from ycmd import identifier_utils
  21. YCM_VAR_PREFIX = 'ycm_'
  22. def GetUserOptions( default_options = {} ):
  23. """Builds a dictionary mapping YCM Vim user options to values. Option names
  24. don't have the 'ycm_' prefix."""
  25. user_options = {}
  26. # First load the default settings from ycmd. We do this to ensure that any
  27. # client-side code that assumes all options are loaded (such as the
  28. # omnicompleter) don't have to constantly check for values being present, and
  29. # so that we don't jave to dulicate the list of server settings in
  30. # youcomplete.vim
  31. defaults_file = os.path.join( paths.DIR_OF_YCMD,
  32. 'ycmd',
  33. 'default_settings.json' )
  34. if os.path.exists( defaults_file ):
  35. with open( defaults_file ) as defaults_file_handle:
  36. user_options = json.load( defaults_file_handle )
  37. # Override the server defaults with any client-generated defaults
  38. user_options.update( default_options )
  39. # Finally, override with any user-specified values in the g: dict
  40. # We only evaluate the keys of the vim globals and not the whole dictionary
  41. # to avoid unicode issues.
  42. # See https://github.com/Valloric/YouCompleteMe/pull/2151 for details.
  43. keys = vimsupport.GetVimGlobalsKeys()
  44. for key in keys:
  45. if not key.startswith( YCM_VAR_PREFIX ):
  46. continue
  47. new_key = key[ len( YCM_VAR_PREFIX ): ]
  48. new_value = vimsupport.VimExpressionToPythonType( 'g:' + key )
  49. user_options[ new_key ] = new_value
  50. return user_options
  51. def CurrentIdentifierFinished():
  52. line, current_column = vimsupport.CurrentLineContentsAndCodepointColumn()
  53. previous_char_index = current_column - 1
  54. if previous_char_index < 0:
  55. return True
  56. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  57. regex = identifier_utils.IdentifierRegexForFiletype( filetype )
  58. for match in regex.finditer( line ):
  59. if match.end() == previous_char_index:
  60. return True
  61. # If the whole line is whitespace, that means the user probably finished an
  62. # identifier on the previous line.
  63. return line[ : current_column ].isspace()
  64. def LastEnteredCharIsIdentifierChar():
  65. line, current_column = vimsupport.CurrentLineContentsAndCodepointColumn()
  66. if current_column - 1 < 0:
  67. return False
  68. filetype = vimsupport.CurrentFiletypes()[ 0 ]
  69. return (
  70. identifier_utils.StartOfLongestIdentifierEndingAtIndex(
  71. line, current_column, filetype ) != current_column )
  72. def AdjustCandidateInsertionText( candidates ):
  73. """This function adjusts the candidate insertion text to take into account the
  74. text that's currently in front of the cursor.
  75. For instance ('|' represents the cursor):
  76. 1. Buffer state: 'foo.|bar'
  77. 2. A completion candidate of 'zoobar' is shown and the user selects it.
  78. 3. Buffer state: 'foo.zoobar|bar' instead of 'foo.zoo|bar' which is what the
  79. user wanted.
  80. This function changes candidates to resolve that issue.
  81. It could be argued that the user actually wants the final buffer state to be
  82. 'foo.zoobar|' (the cursor at the end), but that would be much more difficult
  83. to implement and is probably not worth doing.
  84. """
  85. def NewCandidateInsertionText( to_insert, text_after_cursor ):
  86. overlap_len = OverlapLength( to_insert, text_after_cursor )
  87. if overlap_len:
  88. return to_insert[ :-overlap_len ]
  89. return to_insert
  90. text_after_cursor = vimsupport.TextAfterCursor()
  91. if not text_after_cursor:
  92. return candidates
  93. new_candidates = []
  94. for candidate in candidates:
  95. new_candidate = candidate.copy()
  96. if not new_candidate.get( 'abbr' ):
  97. new_candidate[ 'abbr' ] = new_candidate[ 'word' ]
  98. new_candidate[ 'word' ] = NewCandidateInsertionText(
  99. new_candidate[ 'word' ],
  100. text_after_cursor )
  101. new_candidates.append( new_candidate )
  102. return new_candidates
  103. def OverlapLength( left_string, right_string ):
  104. """Returns the length of the overlap between two strings.
  105. Example: "foo baro" and "baro zoo" -> 4
  106. """
  107. left_string_length = len( left_string )
  108. right_string_length = len( right_string )
  109. if not left_string_length or not right_string_length:
  110. return 0
  111. # Truncate the longer string.
  112. if left_string_length > right_string_length:
  113. left_string = left_string[ -right_string_length: ]
  114. elif left_string_length < right_string_length:
  115. right_string = right_string[ :left_string_length ]
  116. if left_string == right_string:
  117. return min( left_string_length, right_string_length )
  118. # Start by looking for a single character match
  119. # and increase length until no match is found.
  120. best = 0
  121. length = 1
  122. while True:
  123. pattern = left_string[ -length: ]
  124. found = right_string.find( pattern )
  125. if found < 0:
  126. return best
  127. length += found
  128. if left_string[ -length: ] == right_string[ :length ]:
  129. best = length
  130. length += 1