瀏覽代碼

Add public function to get the response from a subcommand

This is useful for customistaions, and allows things like the
ExecuteCommand response to be used for things programmatically. An
example is to get the debugger port from `YcmCompleter ExecuteCommand
vscode.java.startDebugSession` as required by Vimspector.
Ben Jackson 5 年之前
父節點
當前提交
cea5d0a2c0
共有 6 個文件被更改,包括 190 次插入11 次删除
  1. 39 0
      README.md
  2. 15 2
      autoload/youcompleteme.vim
  3. 41 2
      python/ycm/client/command_request.py
  4. 31 7
      python/ycm/youcompleteme.py
  5. 50 0
      test/commands.test.vim
  6. 14 0
      test/testdata/python/doc.py

+ 39 - 0
README.md

@@ -1959,6 +1959,45 @@ For example:
   call youcompleteme#GetWarningCount()
 ```
 
+### The `youcompleteme#GetCommandResponse( ... )` function
+
+Run a [completer subcommand](#ycmcompleter-subcommands) and return the result as
+a string. This can be useful for example to display the `GetGoc` output in a
+popup window, e.g.:
+
+```viml
+let s:ycm_hover_popup = -1
+function s:Hover()
+  let response = youcompleteme#GetCommandResponse( 'GetDoc' )
+  if response == ''
+    return
+  endif
+
+  call popup_hide( s:ycm_hover_popup )
+  let s:ycm_hover_popup = popup_atcursor( balloon_split( response ), {} )
+endfunction
+
+" CursorHold triggers in normal mode after a delay
+autocmd CursorHold * call s:Hover()
+" Or, if you prefer, a mapping:
+nnoremap <leader>D :call <SID>Hover()<CR>
+```
+
+If the completer subcommand result is not a string (for example, it's a FixIt or
+a Location), or if the completer subcommand raises an error, an empty string is
+returned, so that calling code does not have to check for complex error
+conditions.
+
+The arguments to the function are the same as the arguments to the
+`:YcmCompleter` ex command, e.g. the name of the subcommand, followed by any
+additional subcommand arguments. As with the `YcmCompleter` command, if the
+first argument is `ft=<filetype>` the request is targetted at the specified
+filetype completer. This is an advanced usage and not necessary in most cases.
+
+NOTE: The request is run synchronously and blocks Vim until the response is
+received, so we do not recommend running this as part of an autocommand that
+triggers frequently.
+
 Autocommands
 ------------
 

+ 15 - 2
autoload/youcompleteme.vim

@@ -768,7 +768,7 @@ function! s:OnTextChangedInsertMode( popup_is_visible )
     let s:force_semantic = 0
   endif
 
-  if b:ycm_completing &&
+  if exists( 'b:ycm_completing' ) &&
         \ ( g:ycm_auto_trigger || s:force_semantic ) &&
         \ !s:InsideCommentOrStringAndShouldStop() &&
         \ !s:OnBlankLine()
@@ -907,7 +907,7 @@ function! s:RequestSemanticCompletion()
     return ''
   endif
 
-  if b:ycm_completing
+  if exists( 'b:ycm_completing' )
     let s:force_semantic = 1
     if s:completion_api == s:COMPLETION_TEXTCHANGEDP
       call s:StopPoller( s:pollers.completion )
@@ -1157,6 +1157,19 @@ function! youcompleteme#LogsComplete( arglead, cmdline, cursorpos )
 endfunction
 
 
+function! youcompleteme#GetCommandResponse( ... )
+  if !s:AllowedToCompleteInCurrentBuffer()
+    return ''
+  endif
+
+  if !exists( 'b:ycm_completing' )
+    return ''
+  endif
+
+  return py3eval( 'ycm_state.GetCommandResponse( vim.eval( "a:000" ) )' )
+endfunction
+
+
 function! s:CompleterCommand( mods, count, line1, line2, ... )
   py3 ycm_state.SendCommandRequest(
         \ vim.eval( 'a:000' ),

+ 41 - 2
python/ycm/client/command_request.py

@@ -40,7 +40,7 @@ class CommandRequest( BaseRequest ):
     self._request_data = None
 
 
-  def Start( self ):
+  def Start( self, silent = False ):
     self._request_data = BuildRequestData()
     if self._extra_data:
       self._request_data.update( self._extra_data )
@@ -48,7 +48,8 @@ class CommandRequest( BaseRequest ):
       'command_arguments': self._arguments
     } )
     self._response = self.PostDataToHandler( self._request_data,
-                                             'run_completer_command' )
+                                             'run_completer_command',
+                                             display_message = not silent )
 
 
   def Response( self ):
@@ -80,6 +81,37 @@ class CommandRequest( BaseRequest ):
     return self._HandleGotoResponse( modifiers )
 
 
+  def StringResponse( self ):
+    # Retuns a supporable public API version of the response. The reason this
+    # exists is that the ycmd API here is wonky as it originally only supported
+    # text-responses and now has things like fixits and such.
+    #
+    # The supportable public API is basically any text-only response. All other
+    # response types are returned as empty strings
+    if not self.Done():
+      raise RuntimeError( "Response is not ready" )
+
+    # Completer threw an error ?
+    if self._response is None:
+      return ""
+
+    # If not a dictionary or a list, the response is necessarily a
+    # scalar: boolean, number, string, etc. In this case, we print
+    # it to the user.
+    if not isinstance( self._response, ( dict, list ) ):
+      return str( self._response )
+
+    if 'message' in self._response:
+      return self._response[ 'message' ]
+
+    if 'detailed_info' in self._response:
+      return self._response[ 'detailed_info' ]
+
+    # The only other type of response we understand is 'fixits' and GoTo. We
+    # don't provide string versions of them.
+    return ""
+
+
   def _HandleGotoResponse( self, modifiers ):
     if isinstance( self._response, list ):
       vimsupport.SetQuickFixList(
@@ -148,6 +180,13 @@ def SendCommandRequest( arguments,
   return request.Response()
 
 
+def GetCommandResponse( arguments, extra_data = None ):
+  request = CommandRequest( arguments, "", extra_data )
+  # This is a blocking call.
+  request.Start( silent = True )
+  return request.StringResponse()
+
+
 def _BuildQfListItem( goto_data_item ):
   qf_item = {}
   if 'filepath' in goto_data_item:

+ 31 - 7
python/ycm/youcompleteme.py

@@ -32,7 +32,7 @@ from ycm import syntax_parse
 from ycm.client.ycmd_keepalive import YcmdKeepalive
 from ycm.client.base_request import BaseRequest, BuildRequestData
 from ycm.client.completer_available_request import SendCompleterAvailableRequest
-from ycm.client.command_request import SendCommandRequest
+from ycm.client.command_request import SendCommandRequest, GetCommandResponse
 from ycm.client.completion_request import CompletionRequest
 from ycm.client.signature_help_request import ( SignatureHelpRequest,
                                                 SigHelpAvailableByFileType )
@@ -373,12 +373,11 @@ class YouCompleteMe:
       signature_info )
 
 
-  def SendCommandRequest( self,
-                          arguments,
-                          modifiers,
-                          has_range,
-                          start_line,
-                          end_line ):
+  def _GetCommandRequestArguments( self,
+                                   arguments,
+                                   has_range,
+                                   start_line,
+                                   end_line ):
     final_arguments = []
     for argument in arguments:
       # The ft= option which specifies the completer when running a command is
@@ -398,12 +397,37 @@ class YouCompleteMe:
       extra_data.update( vimsupport.BuildRange( start_line, end_line ) )
     self._AddExtraConfDataIfNeeded( extra_data )
 
+    return final_arguments, extra_data
+
+
+
+  def SendCommandRequest( self,
+                          arguments,
+                          modifiers,
+                          has_range,
+                          start_line,
+                          end_line ):
+    final_arguments, extra_data = self._GetCommandRequestArguments(
+      arguments,
+      has_range,
+      start_line,
+      end_line )
     return SendCommandRequest( final_arguments,
                                modifiers,
                                self._user_options[ 'goto_buffer_command' ],
                                extra_data )
 
 
+  def GetCommandResponse( self, arguments ):
+    final_arguments, extra_data = self._GetCommandRequestArguments(
+      arguments,
+      False,
+      0,
+      0 )
+    return GetCommandResponse( final_arguments, extra_data )
+
+
+
   def GetDefinedSubcommands( self ):
     subcommands = BaseRequest().PostDataToHandler( BuildRequestData(),
                                                    'defined_subcommands' )

+ 50 - 0
test/commands.test.vim

@@ -45,3 +45,53 @@ function! Test_ToggleLogs()
 
   %bwipeout!
 endfunction
+
+function! Test_GetCommandResponse()
+  call youcompleteme#test#setup#OpenFile( '/test/testdata/python/doc.py', {} )
+
+  " detailed_info
+  call setpos( '.', [ 0, 12, 3 ] )
+  call assert_equal( "Test_OneLine()\n\nThis is the one line output.",
+                   \ youcompleteme#GetCommandResponse( 'GetDoc' ) )
+
+  call setpos( '.', [ 0, 13, 7 ] )
+  call assert_equal( "Test_MultiLine()\n\nThis is the one line output.\n"
+                   \ . "This is second line.",
+                   \ youcompleteme#GetCommandResponse( 'GetDoc' ) )
+
+  " display message
+  call setpos( '.', [ 0, 12, 10 ] )
+  call assert_equal( 'def Test_OneLine()',
+                   \ youcompleteme#GetCommandResponse( 'GetType' ) )
+
+  " Location
+  call setpos( '.', [ 0, 12, 10 ] )
+  call assert_equal( '',
+                   \ youcompleteme#GetCommandResponse( 'GoTo' ) )
+
+  " Error
+  call setpos( '.', [ 0, 12, 10 ] )
+  call assert_equal( '',
+                   \ youcompleteme#GetCommandResponse( 'NotACommand', 'arg' ) )
+
+  " Specify completer
+  call setpos( '.', [ 0, 13, 7 ] )
+  call assert_equal( "Test_MultiLine()\n\nThis is the one line output.\n"
+                   \ . "This is second line.",
+                   \ youcompleteme#GetCommandResponse( 'ft=python', 'GetDoc' ) )
+
+  " on a command, no error
+  call setpos( '.', [ 0, 1, 3 ] )
+  call assert_equal( '', youcompleteme#GetCommandResponse( 'GetDoc' ) )
+endfunction
+
+
+function! Test_GetCommandResponse_FixIt()
+  call youcompleteme#test#setup#OpenFile( '/test/testdata/cpp/fixit.c', {} )
+
+  " fixit returns empty
+  call setpos( '.', [ 0, 3, 4 ] )
+  call assert_equal( '',
+                   \ youcompleteme#GetCommandResponse( 'FixIt' ) )
+
+endfunction

+ 14 - 0
test/testdata/python/doc.py

@@ -0,0 +1,14 @@
+# Comment
+def Test_OneLine():
+  """This is the one line output."""
+  pass
+
+def Test_MultiLine():
+  """This is the one line output.
+  This is second line."""
+  pass
+
+def Main():
+  Test_OneLine()
+  Test_MultiLine()
+