completion_request.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. # Copyright (C) 2013 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. from __future__ import unicode_literals
  18. from __future__ import print_function
  19. from __future__ import division
  20. from __future__ import absolute_import
  21. # Not installing aliases from python-future; it's unreliable and slow.
  22. from builtins import * # noqa
  23. import logging
  24. from future.utils import iteritems
  25. from ycmd.utils import ToUnicode
  26. from ycm.client.base_request import ( BaseRequest, DisplayServerException,
  27. MakeServerException )
  28. from ycm import vimsupport
  29. _logger = logging.getLogger( __name__ )
  30. class CompletionRequest( BaseRequest ):
  31. def __init__( self, request_data ):
  32. super( CompletionRequest, self ).__init__()
  33. self.request_data = request_data
  34. self._response_future = None
  35. self._complete_done_hooks = {
  36. 'cs': self._OnCompleteDone_Csharp,
  37. 'java': self._OnCompleteDone_Java,
  38. }
  39. def Start( self ):
  40. self._response_future = self.PostDataToHandlerAsync( self.request_data,
  41. 'completions' )
  42. def Done( self ):
  43. return bool( self._response_future ) and self._response_future.done()
  44. def RawResponse( self ):
  45. if not self._response_future:
  46. return { 'completions': [], 'completion_start_column': -1 }
  47. response = self.HandleFuture( self._response_future,
  48. truncate_message = True )
  49. if not response:
  50. return { 'completions': [], 'completion_start_column': -1 }
  51. # Vim may not be able to convert the 'errors' entry to its internal format
  52. # so we remove it from the response.
  53. errors = response.pop( 'errors', [] )
  54. for e in errors:
  55. exception = MakeServerException( e )
  56. _logger.error( exception )
  57. DisplayServerException( exception, truncate_message = True )
  58. return response
  59. def Response( self ):
  60. response = self.RawResponse()
  61. response[ 'completions' ] = _ConvertCompletionDatasToVimDatas(
  62. response[ 'completions' ] )
  63. return response
  64. def OnCompleteDone( self ):
  65. if not self.Done():
  66. return
  67. complete_done_actions = self._GetCompleteDoneHooks()
  68. for action in complete_done_actions:
  69. action()
  70. def _GetCompleteDoneHooks( self ):
  71. filetypes = vimsupport.CurrentFiletypes()
  72. for key, value in iteritems( self._complete_done_hooks ):
  73. if key in filetypes:
  74. yield value
  75. def _GetCompletionsUserMayHaveCompleted( self ):
  76. completed_item = vimsupport.GetVariableValue( 'v:completed_item' )
  77. completions = self.RawResponse()[ 'completions' ]
  78. if 'user_data' in completed_item and completed_item[ 'user_data' ]:
  79. # Vim supports user_data (8.0.1493) or later, so we actually know the
  80. # _exact_ element that was selected, having put its index in the
  81. # user_data field.
  82. return [ completions[ int( completed_item[ 'user_data' ] ) ] ]
  83. # Otherwise, we have to guess by matching the values in the completed item
  84. # and the list of completions. Sometimes this returns multiple
  85. # possibilities, which is essentially unresolvable.
  86. result = _FilterToMatchingCompletions( completed_item, completions, True )
  87. result = list( result )
  88. if result:
  89. return result
  90. if _HasCompletionsThatCouldBeCompletedWithMoreText( completed_item,
  91. completions ):
  92. # Since the way that YCM works leads to CompleteDone called on every
  93. # character, return blank if the completion might not be done. This won't
  94. # match if the completion is ended with typing a non-keyword character.
  95. return []
  96. result = _FilterToMatchingCompletions( completed_item, completions, False )
  97. return list( result )
  98. def _OnCompleteDone_Csharp( self ):
  99. completions = self._GetCompletionsUserMayHaveCompleted()
  100. namespaces = [ _GetRequiredNamespaceImport( c ) for c in completions ]
  101. namespaces = [ n for n in namespaces if n ]
  102. if not namespaces:
  103. return
  104. if len( namespaces ) > 1:
  105. choices = [ "{0} {1}".format( i + 1, n )
  106. for i, n in enumerate( namespaces ) ]
  107. choice = vimsupport.PresentDialog( "Insert which namespace:", choices )
  108. if choice < 0:
  109. return
  110. namespace = namespaces[ choice ]
  111. else:
  112. namespace = namespaces[ 0 ]
  113. vimsupport.InsertNamespace( namespace )
  114. def _OnCompleteDone_Java( self ):
  115. completions = self._GetCompletionsUserMayHaveCompleted()
  116. fixit_completions = [ _GetFixItCompletion( c ) for c in completions ]
  117. fixit_completions = [ f for f in fixit_completions if f ]
  118. if not fixit_completions:
  119. return
  120. # If we have user_data in completions (8.0.1493 or later), then we would
  121. # only ever return max. 1 completion here. However, if we had to guess, it
  122. # is possible that we matched multiple completion items (e.g. for overloads,
  123. # or similar classes in multiple packages). In any case, rather than
  124. # prompting the user and disturbing her workflow, we just apply the first
  125. # one. This might be wrong, but the solution is to use a (very) new version
  126. # of Vim which supports user_data on completion items
  127. fixit_completion = fixit_completions[ 0 ]
  128. for fixit in fixit_completion:
  129. vimsupport.ReplaceChunks( fixit[ 'chunks' ], silent=True )
  130. def _GetRequiredNamespaceImport( completion ):
  131. if ( 'extra_data' not in completion
  132. or 'required_namespace_import' not in completion[ 'extra_data' ] ):
  133. return None
  134. return completion[ 'extra_data' ][ 'required_namespace_import' ]
  135. def _GetFixItCompletion( completion ):
  136. if ( 'extra_data' not in completion
  137. or 'fixits' not in completion[ 'extra_data' ] ):
  138. return None
  139. return completion[ 'extra_data' ][ 'fixits' ]
  140. def _FilterToMatchingCompletions( completed_item,
  141. completions,
  142. full_match_only ):
  143. """Filter to completions matching the item Vim said was completed"""
  144. match_keys = ( [ 'word', 'abbr', 'menu', 'info' ] if full_match_only
  145. else [ 'word' ] )
  146. for index, completion in enumerate( completions ):
  147. item = _ConvertCompletionDataToVimData( index, completion )
  148. def matcher( key ):
  149. return ( ToUnicode( completed_item.get( key, "" ) ) ==
  150. ToUnicode( item.get( key, "" ) ) )
  151. if all( [ matcher( i ) for i in match_keys ] ):
  152. yield completion
  153. def _HasCompletionsThatCouldBeCompletedWithMoreText( completed_item,
  154. completions ):
  155. if not completed_item:
  156. return False
  157. completed_word = ToUnicode( completed_item[ 'word' ] )
  158. if not completed_word:
  159. return False
  160. # Sometimes CompleteDone is called after the next character is inserted.
  161. # If so, use inserted character to filter possible completions further.
  162. text = vimsupport.TextBeforeCursor()
  163. reject_exact_match = True
  164. if text and text[ -1 ] != completed_word[ -1 ]:
  165. reject_exact_match = False
  166. completed_word += text[ -1 ]
  167. for index, completion in enumerate( completions ):
  168. word = ToUnicode(
  169. _ConvertCompletionDataToVimData( index, completion )[ 'word' ] )
  170. if reject_exact_match and word == completed_word:
  171. continue
  172. if word.startswith( completed_word ):
  173. return True
  174. return False
  175. def _ConvertCompletionDataToVimData( completion_identifier, completion_data ):
  176. # see :h complete-items for a description of the dictionary fields
  177. vim_data = {
  178. 'word' : '',
  179. 'dup' : 1,
  180. 'empty' : 1,
  181. }
  182. if ( 'extra_data' in completion_data and
  183. 'doc_string' in completion_data[ 'extra_data' ] ):
  184. doc_string = completion_data[ 'extra_data' ][ 'doc_string' ]
  185. else:
  186. doc_string = ""
  187. if 'insertion_text' in completion_data:
  188. vim_data[ 'word' ] = completion_data[ 'insertion_text' ]
  189. if 'menu_text' in completion_data:
  190. vim_data[ 'abbr' ] = completion_data[ 'menu_text' ]
  191. if 'extra_menu_info' in completion_data:
  192. vim_data[ 'menu' ] = completion_data[ 'extra_menu_info' ]
  193. if 'kind' in completion_data:
  194. kind = ToUnicode( completion_data[ 'kind' ] )
  195. if kind:
  196. vim_data[ 'kind' ] = kind[ 0 ].lower()
  197. if 'detailed_info' in completion_data:
  198. vim_data[ 'info' ] = completion_data[ 'detailed_info' ]
  199. if doc_string:
  200. vim_data[ 'info' ] += '\n' + doc_string
  201. elif doc_string:
  202. vim_data[ 'info' ] = doc_string
  203. # We store the completion item index as a string in the completion user_data.
  204. # This allows us to identify the _exact_ item that was completed in the
  205. # CompleteDone handler, by inspecting this item from v:completed_item
  206. #
  207. # We convert to string because completion user data items must be strings.
  208. #
  209. # Note: Not all versions of Vim support this (added in 8.0.1483), but adding
  210. # the item to the dictionary is harmless in earlier Vims.
  211. vim_data[ 'user_data' ] = str( completion_identifier )
  212. return vim_data
  213. def _ConvertCompletionDatasToVimDatas( response_data ):
  214. return [ _ConvertCompletionDataToVimData( i, x )
  215. for i, x in enumerate( response_data ) ]