def YouCompleteMe_OnPeriodicTick_DontRetry_test(post_data_to_handler_async, validate_response_object, filetype_completer_exists, ycm): current_buffer = VimBuffer('/current', filetype='ycmtest', number=1) # Create the request and make the first poll; we expect no response with MockVimBuffers([current_buffer], [current_buffer], (1, 1)): assert_that(ycm.OnPeriodicTick(), equal_to(True)) post_data_to_handler_async.assert_called() assert ycm._message_poll_requests['ycmtest'] is not None post_data_to_handler_async.reset_mock() # OK that sent the request, now poll to check if it is complete (say it is # not) with MockVimBuffers([current_buffer], [current_buffer], (1, 1)) as v: mock_response = MockAsyncServerResponseInProgress() with patch.dict(ycm._message_poll_requests, {}): ycm._message_poll_requests['ycmtest'] = MessagesPoll( v.current.buffer) ycm._message_poll_requests[ 'ycmtest']._response_future = mock_response mock_future = ycm._message_poll_requests[ 'ycmtest']._response_future poll_again = ycm.OnPeriodicTick() mock_future.done.assert_called() mock_future.result.assert_not_called() assert_that(poll_again, equal_to(True)) # Poll again, but return a response (telling us to stop polling) with MockVimBuffers([current_buffer], [current_buffer], (1, 1)) as v: mock_response = MockAsyncServerResponseDone(False) with patch.dict(ycm._message_poll_requests, {}): ycm._message_poll_requests['ycmtest'] = MessagesPoll( v.current.buffer) ycm._message_poll_requests[ 'ycmtest']._response_future = mock_response mock_future = ycm._message_poll_requests[ 'ycmtest']._response_future poll_again = ycm.OnPeriodicTick() mock_future.done.assert_called() mock_future.result.assert_called() post_data_to_handler_async.assert_not_called() # We reset and don't poll anymore assert_that(ycm._message_poll_requests['ycmtest'] is None) assert_that(poll_again, equal_to(False))
def YouCompleteMe_OnPeriodicTick_ValidResponse_test(ycm, handle_poll_response, post_data_to_handler_async, *args): current_buffer = VimBuffer('/current', filetype='ycmtest', number=1) # Create the request and make the first poll; we expect no response with MockVimBuffers([current_buffer], [current_buffer], (1, 1)): assert_that(ycm.OnPeriodicTick(), equal_to(True)) post_data_to_handler_async.assert_called() post_data_to_handler_async.reset_mock() # Poll again, and return a _proper_ response (finally!). # Note, _HandlePollResponse is tested independently (for simplicity) with MockVimBuffers([current_buffer], [current_buffer], (1, 1)) as v: mock_response = MockAsyncServerResponseDone([]) with patch.dict(ycm._message_poll_requests, {}): ycm._message_poll_requests['ycmtest'] = MessagesPoll( v.current.buffer) ycm._message_poll_requests[ 'ycmtest']._response_future = mock_response mock_future = ycm._message_poll_requests[ 'ycmtest']._response_future assert_that(ycm.OnPeriodicTick(), equal_to(True)) handle_poll_response.assert_called() mock_future.done.assert_called() mock_future.result.assert_called() post_data_to_handler_async.assert_called() # Poll again! assert_that(ycm._message_poll_requests['ycmtest'] is not None)
def YouCompleteMe_OnPeriodicTick_Exception_test(ycm, post_data_to_handler_async, *args): current_buffer = VimBuffer('/current', filetype='ycmtest', number=1) # Create the request and make the first poll; we expect no response with MockVimBuffers([current_buffer], [current_buffer], (1, 1)): assert_that(ycm.OnPeriodicTick(), equal_to(True)) post_data_to_handler_async.assert_called() post_data_to_handler_async.reset_mock() # Poll again, but return an exception response with MockVimBuffers([current_buffer], [current_buffer], (1, 1)) as v: mock_response = MockAsyncServerResponseException(RuntimeError('test')) with patch.dict(ycm._message_poll_requests, {}): ycm._message_poll_requests['ycmtest'] = MessagesPoll( v.current.buffer) ycm._message_poll_requests[ 'ycmtest']._response_future = mock_response mock_future = ycm._message_poll_requests[ 'ycmtest']._response_future assert_that(ycm.OnPeriodicTick(), equal_to(False)) mock_future.done.assert_called() mock_future.result.assert_called() post_data_to_handler_async.assert_not_called() # We reset and don't poll anymore assert_that(ycm._message_poll_requests['ycmtest'] is None)
def OnPeriodicTick( self ): if not self.IsServerAlive(): # Server has died. We'll reset when the server is started again. return False elif not self.IsServerReady(): # Try again in a jiffy return True if not self._message_poll_request: self._message_poll_request = MessagesPoll() if not self._message_poll_request.Poll( self ): # Don't poll again until some event which might change the server's mind # about whether to provide messages for the current buffer (e.g. buffer # visit, file ready to parse, etc.) self._message_poll_request = None return False # Poll again in a jiffy return True
def OnPeriodicTick( self ): if not self.IsServerAlive(): # Server has died. We'll reset when the server is started again. return False elif not self.IsServerReady(): # Try again in a jiffy return True for w in vim.windows: for filetype in vimsupport.FiletypesForBuffer( w.buffer ): if filetype not in self._message_poll_requests: self._message_poll_requests[ filetype ] = MessagesPoll( w.buffer ) # None means don't poll this filetype if ( self._message_poll_requests[ filetype ] and not self._message_poll_requests[ filetype ].Poll( self ) ): self._message_poll_requests[ filetype ] = None return any( self._message_poll_requests.values() )
class YouCompleteMe( object ): def __init__( self ): self._available_completers = {} self._user_options = None self._user_notified_about_crash = False self._omnicomp = None self._buffers = None self._latest_completion_request = None self._logger = logging.getLogger( 'ycm' ) self._client_logfile = None self._server_stdout = None self._server_stderr = None self._server_popen = None self._filetypes_with_keywords_loaded = set() self._ycmd_keepalive = YcmdKeepalive() self._server_is_ready_with_cache = False self._SetUpLogging() self._SetUpServer() self._ycmd_keepalive.Start() def _SetUpServer( self ): self._available_completers = {} self._user_notified_about_crash = False self._filetypes_with_keywords_loaded = set() self._server_is_ready_with_cache = False self._message_poll_request = None self._user_options = base.GetUserOptions() self._omnicomp = OmniCompleter( self._user_options ) self._buffers = BufferDict( self._user_options ) self._SetLogLevel() hmac_secret = os.urandom( HMAC_SECRET_LENGTH ) options_dict = dict( self._user_options ) options_dict[ 'hmac_secret' ] = utils.ToUnicode( base64.b64encode( hmac_secret ) ) options_dict[ 'server_keep_logfiles' ] = self._user_options[ 'keep_logfiles' ] # The temp options file is deleted by ycmd during startup. with NamedTemporaryFile( delete = False, mode = 'w+' ) as options_file: json.dump( options_dict, options_file ) server_port = utils.GetUnusedLocalhostPort() BaseRequest.server_location = 'http://127.0.0.1:' + str( server_port ) BaseRequest.hmac_secret = hmac_secret try: python_interpreter = paths.PathToPythonInterpreter() except RuntimeError as error: error_message = ( "Unable to start the ycmd server. {0}. " "Correct the error then restart the server " "with ':YcmRestartServer'.".format( str( error ).rstrip( '.' ) ) ) self._logger.exception( error_message ) vimsupport.PostVimMessage( error_message ) return args = [ python_interpreter, paths.PathToServerScript(), '--port={0}'.format( server_port ), '--options_file={0}'.format( options_file.name ), '--log={0}'.format( self._user_options[ 'log_level' ] ), '--idle_suicide_seconds={0}'.format( SERVER_IDLE_SUICIDE_SECONDS ) ] self._server_stdout = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stdout' ) ) self._server_stderr = utils.CreateLogfile( SERVER_LOGFILE_FORMAT.format( port = server_port, std = 'stderr' ) ) args.append( '--stdout={0}'.format( self._server_stdout ) ) args.append( '--stderr={0}'.format( self._server_stderr ) ) if self._user_options[ 'keep_logfiles' ]: args.append( '--keep_logfiles' ) self._server_popen = utils.SafePopen( args, stdin_windows = PIPE, stdout = PIPE, stderr = PIPE ) def _SetUpLogging( self ): def FreeFileFromOtherProcesses( file_object ): if utils.OnWindows(): from ctypes import windll import msvcrt file_handle = msvcrt.get_osfhandle( file_object.fileno() ) windll.kernel32.SetHandleInformation( file_handle, HANDLE_FLAG_INHERIT, 0 ) self._client_logfile = utils.CreateLogfile( CLIENT_LOGFILE_FORMAT ) handler = logging.FileHandler( self._client_logfile ) # On Windows and Python prior to 3.4, file handles are inherited by child # processes started with at least one replaced standard stream, which is the # case when we start the ycmd server (we are redirecting all standard # outputs into a pipe). These files cannot be removed while the child # processes are still up. This is not desirable for a logfile because we # want to remove it at Vim exit without having to wait for the ycmd server # to be completely shut down. We need to make the logfile handle # non-inheritable. See https://www.python.org/dev/peps/pep-0446 for more # details. FreeFileFromOtherProcesses( handler.stream ) formatter = logging.Formatter( '%(asctime)s - %(levelname)s - %(message)s' ) handler.setFormatter( formatter ) self._logger.addHandler( handler ) def _SetLogLevel( self ): log_level = self._user_options[ 'log_level' ] numeric_level = getattr( logging, log_level.upper(), None ) if not isinstance( numeric_level, int ): raise ValueError( 'Invalid log level: {0}'.format( log_level ) ) self._logger.setLevel( numeric_level ) def IsServerAlive( self ): # When the process hasn't finished yet, poll() returns None. return bool( self._server_popen ) and self._server_popen.poll() is None def CheckIfServerIsReady( self ): if not self._server_is_ready_with_cache and self.IsServerAlive(): self._server_is_ready_with_cache = BaseRequest().GetDataFromHandler( 'ready', display_message = False ) return self._server_is_ready_with_cache def IsServerReady( self ): return self._server_is_ready_with_cache def NotifyUserIfServerCrashed( self ): if ( not self._server_popen or self._user_notified_about_crash or self.IsServerAlive() ): return self._user_notified_about_crash = True return_code = self._server_popen.poll() logfile = os.path.basename( self._server_stderr ) if return_code == server_utils.CORE_UNEXPECTED_STATUS: error_message = CORE_UNEXPECTED_MESSAGE.format( logfile = logfile ) elif return_code == server_utils.CORE_MISSING_STATUS: error_message = CORE_MISSING_MESSAGE elif return_code == server_utils.CORE_PYTHON2_STATUS: error_message = CORE_PYTHON2_MESSAGE elif return_code == server_utils.CORE_PYTHON3_STATUS: error_message = CORE_PYTHON3_MESSAGE elif return_code == server_utils.CORE_OUTDATED_STATUS: error_message = CORE_OUTDATED_MESSAGE else: error_message = EXIT_CODE_UNEXPECTED_MESSAGE.format( code = return_code, logfile = logfile ) error_message = SERVER_SHUTDOWN_MESSAGE + ' ' + error_message self._logger.error( error_message ) vimsupport.PostVimMessage( error_message ) def ServerPid( self ): if not self._server_popen: return -1 return self._server_popen.pid def _ShutdownServer( self ): SendShutdownRequest() def RestartServer( self ): vimsupport.PostVimMessage( 'Restarting ycmd server...' ) self._ShutdownServer() self._SetUpServer() def SendCompletionRequest( self, force_semantic = False ): request_data = BuildRequestData() request_data[ 'force_semantic' ] = force_semantic if not self.NativeFiletypeCompletionUsable(): wrapped_request_data = RequestWrap( request_data ) if self._omnicomp.ShouldUseNow( wrapped_request_data ): self._latest_completion_request = OmniCompletionRequest( self._omnicomp, wrapped_request_data ) self._latest_completion_request.Start() return self._AddExtraConfDataIfNeeded( request_data ) self._latest_completion_request = CompletionRequest( request_data ) self._latest_completion_request.Start() def CompletionRequestReady( self ): return bool( self._latest_completion_request and self._latest_completion_request.Done() ) def GetCompletionResponse( self ): response = self._latest_completion_request.Response() response[ 'completions' ] = base.AdjustCandidateInsertionText( response[ 'completions' ] ) return response def SendCommandRequest( self, arguments, modifiers, has_range, start_line, end_line ): final_arguments = [] for argument in arguments: # The ft= option which specifies the completer when running a command is # ignored because it has not been working for a long time. The option is # still parsed to not break users that rely on it. if argument.startswith( 'ft=' ): continue final_arguments.append( argument ) extra_data = { 'options': { 'tab_size': vimsupport.GetIntValue( 'shiftwidth()' ), 'insert_spaces': vimsupport.GetBoolValue( '&expandtab' ) } } if has_range: extra_data.update( vimsupport.BuildRange( start_line, end_line ) ) self._AddExtraConfDataIfNeeded( extra_data ) return SendCommandRequest( final_arguments, modifiers, self._user_options[ 'goto_buffer_command' ], extra_data ) def GetDefinedSubcommands( self ): subcommands = BaseRequest().PostDataToHandler( BuildRequestData(), 'defined_subcommands' ) return subcommands if subcommands else [] def GetCurrentCompletionRequest( self ): return self._latest_completion_request def GetOmniCompleter( self ): return self._omnicomp def FiletypeCompleterExistsForFiletype( self, filetype ): try: return self._available_completers[ filetype ] except KeyError: pass exists_completer = SendCompleterAvailableRequest( filetype ) if exists_completer is None: return False self._available_completers[ filetype ] = exists_completer return exists_completer def NativeFiletypeCompletionAvailable( self ): return any( self.FiletypeCompleterExistsForFiletype( x ) for x in vimsupport.CurrentFiletypes() ) def NativeFiletypeCompletionUsable( self ): disabled_filetypes = self._user_options[ 'filetype_specific_completion_to_disable' ] return ( vimsupport.CurrentFiletypesEnabled( disabled_filetypes ) and self.NativeFiletypeCompletionAvailable() ) def NeedsReparse( self ): return self.CurrentBuffer().NeedsReparse() def UpdateWithNewDiagnosticsForFile( self, filepath, diagnostics ): bufnr = vimsupport.GetBufferNumberForFilename( filepath ) if bufnr in self._buffers and vimsupport.BufferIsVisible( bufnr ): # Note: We only update location lists, etc. for visible buffers, because # otherwise we default to using the current location list and the results # are that non-visible buffer errors clobber visible ones. self._buffers[ bufnr ].UpdateWithNewDiagnostics( diagnostics ) else: # The project contains errors in file "filepath", but that file is not # open in any buffer. This happens for Language Server Protocol-based # completers, as they return diagnostics for the entire "project" # asynchronously (rather than per-file in the response to the parse # request). # # There are a number of possible approaches for # this, but for now we simply ignore them. Other options include: # - Use the QuickFix list to report project errors? # - Use a special buffer for project errors # - Put them in the location list of whatever the "current" buffer is # - Store them in case the buffer is opened later # - add a :YcmProjectDiags command # - Add them to errror/warning _counts_ but not any actual location list # or other # - etc. # # However, none of those options are great, and lead to their own # complexities. So for now, we just ignore these diagnostics for files not # open in any buffer. pass def OnPeriodicTick( self ): if not self.IsServerAlive(): # Server has died. We'll reset when the server is started again. return False elif not self.IsServerReady(): # Try again in a jiffy return True if not self._message_poll_request: self._message_poll_request = MessagesPoll() if not self._message_poll_request.Poll( self ): # Don't poll again until some event which might change the server's mind # about whether to provide messages for the current buffer (e.g. buffer # visit, file ready to parse, etc.) self._message_poll_request = None return False # Poll again in a jiffy return True def OnFileReadyToParse( self ): if not self.IsServerAlive(): self.NotifyUserIfServerCrashed() return if not self.IsServerReady(): return extra_data = {} self._AddTagsFilesIfNeeded( extra_data ) self._AddSyntaxDataIfNeeded( extra_data ) self._AddExtraConfDataIfNeeded( extra_data ) self.CurrentBuffer().SendParseRequest( extra_data ) def OnBufferUnload( self, deleted_buffer_number ): SendEventNotificationAsync( 'BufferUnload', deleted_buffer_number ) def UpdateMatches( self ): self.CurrentBuffer().UpdateMatches() def OnBufferVisit( self ): extra_data = {} self._AddUltiSnipsDataIfNeeded( extra_data ) SendEventNotificationAsync( 'BufferVisit', extra_data = extra_data ) def CurrentBuffer( self ): return self._buffers[ vimsupport.GetCurrentBufferNumber() ] def OnInsertLeave( self ): SendEventNotificationAsync( 'InsertLeave' ) def OnCursorMoved( self ): self.CurrentBuffer().OnCursorMoved() def _CleanLogfile( self ): logging.shutdown() if not self._user_options[ 'keep_logfiles' ]: if self._client_logfile: utils.RemoveIfExists( self._client_logfile ) def OnVimLeave( self ): self._ShutdownServer() self._CleanLogfile() def OnCurrentIdentifierFinished( self ): SendEventNotificationAsync( 'CurrentIdentifierFinished' ) def OnCompleteDone( self ): completion_request = self.GetCurrentCompletionRequest() if completion_request: completion_request.OnCompleteDone() def GetErrorCount( self ): return self.CurrentBuffer().GetErrorCount() def GetWarningCount( self ): return self.CurrentBuffer().GetWarningCount() def DiagnosticUiSupportedForCurrentFiletype( self ): return any( x in DIAGNOSTIC_UI_FILETYPES or x in DIAGNOSTIC_UI_ASYNC_FILETYPES for x in vimsupport.CurrentFiletypes() ) def ShouldDisplayDiagnostics( self ): return bool( self._user_options[ 'show_diagnostics_ui' ] and self.DiagnosticUiSupportedForCurrentFiletype() ) def _PopulateLocationListWithLatestDiagnostics( self ): return self.CurrentBuffer().PopulateLocationList() def FileParseRequestReady( self ): # Return True if server is not ready yet, to stop repeating check timer. return ( not self.IsServerReady() or self.CurrentBuffer().FileParseRequestReady() ) def HandleFileParseRequest( self, block = False ): if not self.IsServerReady(): return current_buffer = self.CurrentBuffer() # Order is important here: # FileParseRequestReady has a low cost, while # NativeFiletypeCompletionUsable is a blocking server request if ( not current_buffer.IsResponseHandled() and current_buffer.FileParseRequestReady( block ) and self.NativeFiletypeCompletionUsable() ): if self.ShouldDisplayDiagnostics(): # Forcefuly update the location list, etc. from the parse request when # doing something like :YcmDiags current_buffer.UpdateDiagnostics( block is True ) else: # YCM client has a hard-coded list of filetypes which are known # to support diagnostics, self.DiagnosticUiSupportedForCurrentFiletype() # # For filetypes which don't support diagnostics, we just want to check # the _latest_file_parse_request for any exception or UnknownExtraConf # response, to allow the server to raise configuration warnings, etc. # to the user. We ignore any other supplied data. current_buffer.GetResponse() # We set the file parse request as handled because we want to prevent # repeated issuing of the same warnings/errors/prompts. Setting this # makes IsRequestHandled return True until the next request is created. # # Note: it is the server's responsibility to determine the frequency of # error/warning/prompts when receiving a FileReadyToParse event, but # it is our responsibility to ensure that we only apply the # warning/error/prompt received once (for each event). current_buffer.MarkResponseHandled() def ShouldResendFileParseRequest( self ): return self.CurrentBuffer().ShouldResendParseRequest() def DebugInfo( self ): debug_info = '' if self._client_logfile: debug_info += 'Client logfile: {0}\n'.format( self._client_logfile ) extra_data = {} self._AddExtraConfDataIfNeeded( extra_data ) debug_info += FormatDebugInfoResponse( SendDebugInfoRequest( extra_data ) ) debug_info += 'Server running at: {0}\n'.format( BaseRequest.server_location ) if self._server_popen: debug_info += 'Server process ID: {0}\n'.format( self._server_popen.pid ) if self._server_stdout and self._server_stderr: debug_info += ( 'Server logfiles:\n' ' {0}\n' ' {1}'.format( self._server_stdout, self._server_stderr ) ) return debug_info def GetLogfiles( self ): logfiles_list = [ self._client_logfile, self._server_stdout, self._server_stderr ] extra_data = {} self._AddExtraConfDataIfNeeded( extra_data ) debug_info = SendDebugInfoRequest( extra_data ) if debug_info: completer = debug_info[ 'completer' ] if completer: for server in completer[ 'servers' ]: logfiles_list.extend( server[ 'logfiles' ] ) logfiles = {} for logfile in logfiles_list: logfiles[ os.path.basename( logfile ) ] = logfile return logfiles def _OpenLogfile( self, logfile ): # Open log files in a horizontal window with the same behavior as the # preview window (same height and winfixheight enabled). Automatically # watch for changes. Set the cursor position at the end of the file. options = { 'size': vimsupport.GetIntValue( '&previewheight' ), 'fix': True, 'focus': False, 'watch': True, 'position': 'end' } vimsupport.OpenFilename( logfile, options ) def _CloseLogfile( self, logfile ): vimsupport.CloseBuffersForFilename( logfile ) def ToggleLogs( self, *filenames ): logfiles = self.GetLogfiles() if not filenames: sorted_logfiles = sorted( list( logfiles ) ) try: logfile_index = vimsupport.SelectFromList( 'Which logfile do you wish to open (or close if already open)?', sorted_logfiles ) except RuntimeError as e: vimsupport.PostVimMessage( str( e ) ) return logfile = logfiles[ sorted_logfiles[ logfile_index ] ] if not vimsupport.BufferIsVisibleForFilename( logfile ): self._OpenLogfile( logfile ) else: self._CloseLogfile( logfile ) return for filename in set( filenames ): if filename not in logfiles: continue logfile = logfiles[ filename ] if not vimsupport.BufferIsVisibleForFilename( logfile ): self._OpenLogfile( logfile ) continue self._CloseLogfile( logfile ) def ShowDetailedDiagnostic( self ): detailed_diagnostic = BaseRequest().PostDataToHandler( BuildRequestData(), 'detailed_diagnostic' ) if detailed_diagnostic and 'message' in detailed_diagnostic: vimsupport.PostVimMessage( detailed_diagnostic[ 'message' ], warning = False ) def ForceCompileAndDiagnostics( self ): if not self.NativeFiletypeCompletionUsable(): vimsupport.PostVimMessage( 'Native filetype completion not supported for current file, ' 'cannot force recompilation.', warning = False ) return False vimsupport.PostVimMessage( 'Forcing compilation, this will block Vim until done.', warning = False ) self.OnFileReadyToParse() self.HandleFileParseRequest( block = True ) vimsupport.PostVimMessage( 'Diagnostics refreshed', warning = False ) return True def ShowDiagnostics( self ): if not self.ForceCompileAndDiagnostics(): return if not self._PopulateLocationListWithLatestDiagnostics(): vimsupport.PostVimMessage( 'No warnings or errors detected.', warning = False ) return if self._user_options[ 'open_loclist_on_ycm_diags' ]: vimsupport.OpenLocationList( focus = True ) def _AddSyntaxDataIfNeeded( self, extra_data ): if not self._user_options[ 'seed_identifiers_with_syntax' ]: return filetype = vimsupport.CurrentFiletypes()[ 0 ] if filetype in self._filetypes_with_keywords_loaded: return if self.IsServerReady(): self._filetypes_with_keywords_loaded.add( filetype ) extra_data[ 'syntax_keywords' ] = list( syntax_parse.SyntaxKeywordsForCurrentBuffer() ) def _AddTagsFilesIfNeeded( self, extra_data ): def GetTagFiles(): tag_files = vim.eval( 'tagfiles()' ) return [ os.path.join( utils.GetCurrentDirectory(), tag_file ) for tag_file in tag_files ] if not self._user_options[ 'collect_identifiers_from_tags_files' ]: return extra_data[ 'tag_files' ] = GetTagFiles() def _AddExtraConfDataIfNeeded( self, extra_data ): def BuildExtraConfData( extra_conf_vim_data ): extra_conf_data = {} for expr in extra_conf_vim_data: try: extra_conf_data[ expr ] = vimsupport.VimExpressionToPythonType( expr ) except vim.error: message = ( "Error evaluating '{expr}' in the 'g:ycm_extra_conf_vim_data' " "option.".format( expr = expr ) ) vimsupport.PostVimMessage( message, truncate = True ) self._logger.exception( message ) return extra_conf_data extra_conf_vim_data = self._user_options[ 'extra_conf_vim_data' ] if extra_conf_vim_data: extra_data[ 'extra_conf_data' ] = BuildExtraConfData( extra_conf_vim_data ) def _AddUltiSnipsDataIfNeeded( self, extra_data ): # See :h UltiSnips#SnippetsInCurrentScope. try: vim.eval( 'UltiSnips#SnippetsInCurrentScope( 1 )' ) except vim.error: return snippets = vimsupport.GetVariableValue( 'g:current_ulti_dict_info' ) extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': trigger, 'description': snippet[ 'description' ] } for trigger, snippet in iteritems( snippets ) ]