Diagnostics work again... somewhat.
There appear to be timing issues for the diag requests. Somehow, we're sending out-of-date diagnostics and then not updating the UI when things change. That needs to be fixed.
This commit is contained in:
parent
e44fd87582
commit
b9bb788a2a
@ -260,7 +260,7 @@ function! s:OnCursorHold()
|
||||
call s:SetUpCompleteopt()
|
||||
" Order is important here; we need to extract any done diagnostics before
|
||||
" reparsing the file again
|
||||
" call s:UpdateDiagnosticNotifications()
|
||||
call s:UpdateDiagnosticNotifications()
|
||||
call s:OnFileReadyToParse()
|
||||
endfunction
|
||||
|
||||
@ -272,6 +272,7 @@ function! s:OnFileReadyToParse()
|
||||
|
||||
let buffer_changed = b:changedtick != b:ycm_changedtick.file_ready_to_parse
|
||||
if buffer_changed
|
||||
py ycm_state.RequestDiagnosticsForCurrentFile()
|
||||
py ycm_state.OnFileReadyToParse()
|
||||
endif
|
||||
let b:ycm_changedtick.file_ready_to_parse = b:changedtick
|
||||
@ -327,7 +328,7 @@ function! s:OnCursorMovedNormalMode()
|
||||
return
|
||||
endif
|
||||
|
||||
" call s:UpdateDiagnosticNotifications()
|
||||
call s:UpdateDiagnosticNotifications()
|
||||
call s:OnFileReadyToParse()
|
||||
endfunction
|
||||
|
||||
@ -338,7 +339,7 @@ function! s:OnInsertLeave()
|
||||
endif
|
||||
|
||||
let s:omnifunc_mode = 0
|
||||
" call s:UpdateDiagnosticNotifications()
|
||||
call s:UpdateDiagnosticNotifications()
|
||||
call s:OnFileReadyToParse()
|
||||
py ycm_state.OnInsertLeave()
|
||||
if g:ycm_autoclose_preview_window_after_completion ||
|
||||
@ -408,10 +409,16 @@ endfunction
|
||||
|
||||
|
||||
function! s:UpdateDiagnosticNotifications()
|
||||
if get( g:, 'loaded_syntastic_plugin', 0 ) &&
|
||||
\ pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' ) &&
|
||||
\ pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' ) &&
|
||||
\ g:ycm_register_as_syntastic_checker
|
||||
let should_display_diagnostics =
|
||||
\ get( g:, 'loaded_syntastic_plugin', 0 ) &&
|
||||
\ g:ycm_register_as_syntastic_checker &&
|
||||
\ pyeval( 'ycm_state.NativeFiletypeCompletionUsable()' )
|
||||
|
||||
if !should_display_diagnostics
|
||||
return
|
||||
endif
|
||||
|
||||
if pyeval( 'ycm_state.DiagnosticsForCurrentFileReady()' )
|
||||
SyntasticCheck
|
||||
endif
|
||||
endfunction
|
||||
@ -566,9 +573,7 @@ command! YcmShowDetailedDiagnostic call s:ShowDetailedDiagnostic()
|
||||
" required (currently that's on buffer save) OR when the SyntasticCheck command
|
||||
" is invoked
|
||||
function! youcompleteme#CurrentFileDiagnostics()
|
||||
" TODO: Make this work again.
|
||||
" return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' )
|
||||
return []
|
||||
return pyeval( 'ycm_state.GetDiagnosticsFromStoredRequest()' )
|
||||
endfunction
|
||||
|
||||
|
||||
|
@ -92,25 +92,11 @@ void TranslationUnit::Destroy() {
|
||||
|
||||
|
||||
std::vector< Diagnostic > TranslationUnit::LatestDiagnostics() {
|
||||
std::vector< Diagnostic > diagnostics;
|
||||
|
||||
if ( !clang_translation_unit_ )
|
||||
return diagnostics;
|
||||
return std::vector< Diagnostic >();
|
||||
|
||||
unique_lock< mutex > lock( diagnostics_mutex_ );
|
||||
|
||||
// We don't need the latest diags after we return them once so we swap the
|
||||
// internal data with a new, empty diag vector. This vector is then returned
|
||||
// and on C++11 compilers a move ctor is invoked, thus no copy is created.
|
||||
// Theoretically, just returning the value of a
|
||||
// [boost::|std::]move(latest_diagnostics_) call _should_ leave the
|
||||
// latest_diagnostics_ vector in an emtpy, valid state but I'm not going to
|
||||
// rely on that. I just had to look this up in the standard to be sure, and
|
||||
// future readers of this code (myself included) should not be forced to do
|
||||
// that to understand what the hell is going on.
|
||||
|
||||
std::swap( latest_diagnostics_, diagnostics );
|
||||
return diagnostics;
|
||||
return latest_diagnostics_;
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,7 +30,7 @@ class CommandRequest( BaseRequest ):
|
||||
self._completer_target = ( completer_target if completer_target
|
||||
else 'filetype_default' )
|
||||
self._is_goto_command = (
|
||||
True if arguments and arguments[ 0 ].startswith( 'GoTo' ) else False )
|
||||
arguments and arguments[ 0 ].startswith( 'GoTo' ) )
|
||||
self._response = None
|
||||
|
||||
|
||||
|
75
python/ycm/client/diagnostics_request.py
Normal file
75
python/ycm/client/diagnostics_request.py
Normal file
@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2013 Strahinja Val Markovic <val@markovic.io>
|
||||
#
|
||||
# This file is part of YouCompleteMe.
|
||||
#
|
||||
# YouCompleteMe is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# YouCompleteMe is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import traceback
|
||||
from ycm import vimsupport
|
||||
from ycm.client.base_request import ( BaseRequest, BuildRequestData,
|
||||
JsonFromFuture )
|
||||
|
||||
|
||||
class DiagnosticsRequest( BaseRequest ):
|
||||
def __init__( self ):
|
||||
super( DiagnosticsRequest, self ).__init__()
|
||||
self._cached_response = None
|
||||
|
||||
|
||||
def Start( self ):
|
||||
request_data = BuildRequestData()
|
||||
|
||||
try:
|
||||
self._response_future = self.PostDataToHandlerAsync( request_data,
|
||||
'diagnostics' )
|
||||
except:
|
||||
vimsupport.EchoText( traceback.format_exc() )
|
||||
|
||||
|
||||
def Done( self ):
|
||||
return self._response_future.done()
|
||||
|
||||
|
||||
def Response( self ):
|
||||
if self._cached_response:
|
||||
return self._cached_response
|
||||
|
||||
if not self._response_future:
|
||||
return []
|
||||
try:
|
||||
self._cached_response = [ _ConvertDiagnosticDataToVimData( x )
|
||||
for x in JsonFromFuture(
|
||||
self._response_future ) ]
|
||||
return self._cached_response
|
||||
except Exception as e:
|
||||
vimsupport.PostVimMessage( str( e ) )
|
||||
return []
|
||||
|
||||
|
||||
def _ConvertDiagnosticDataToVimData( diagnostic ):
|
||||
# see :h getqflist for a description of the dictionary fields
|
||||
# Note that, as usual, Vim is completely inconsistent about whether
|
||||
# line/column numbers are 1 or 0 based in its various APIs. Here, it wants
|
||||
# them to be 1-based.
|
||||
return {
|
||||
'bufnr' : vimsupport.GetBufferNumberForFilename( diagnostic[ 'filepath' ]),
|
||||
'lnum' : diagnostic[ 'line_num' ] + 1,
|
||||
'col' : diagnostic[ 'column_num' ] + 1,
|
||||
'text' : diagnostic[ 'text' ],
|
||||
'type' : diagnostic[ 'kind' ],
|
||||
'valid' : 1
|
||||
}
|
||||
|
@ -233,11 +233,7 @@ class Completer( object ):
|
||||
pass
|
||||
|
||||
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
return False
|
||||
|
||||
|
||||
def GetDiagnosticsForCurrentFile( self ):
|
||||
def GetDiagnosticsForCurrentFile( self, request_data ):
|
||||
return []
|
||||
|
||||
|
||||
|
@ -44,7 +44,6 @@ class ClangCompleter( Completer ):
|
||||
self._max_diagnostics_to_display = user_options[
|
||||
'max_diagnostics_to_display' ]
|
||||
self._completer = ycm_core.ClangCompleter()
|
||||
self._last_prepared_diagnostics = []
|
||||
self._flags = Flags()
|
||||
self._diagnostic_store = None
|
||||
self._logger = logging.getLogger( __name__ )
|
||||
@ -213,13 +212,6 @@ class ClangCompleter( Completer ):
|
||||
ToUtf8IfNeeded( request_data[ 'unloaded_buffer' ] ) )
|
||||
|
||||
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
# if not self.parse_future:
|
||||
# return False
|
||||
# return self.parse_future.ResultsReady()
|
||||
pass
|
||||
|
||||
|
||||
def GettingCompletions( self, request_data ):
|
||||
return self._completer.UpdatingTranslationUnit(
|
||||
ToUtf8IfNeeded( request_data[ 'filepath' ] ) )
|
||||
@ -227,19 +219,11 @@ class ClangCompleter( Completer ):
|
||||
|
||||
def GetDiagnosticsForCurrentFile( self, request_data ):
|
||||
filename = request_data[ 'filepath' ]
|
||||
if self.DiagnosticsForCurrentFileReady():
|
||||
diagnostics = self._completer.DiagnosticsForFile(
|
||||
ToUtf8IfNeeded( filename ) )
|
||||
self._diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
|
||||
self._last_prepared_diagnostics = [
|
||||
responses.BuildDiagnosticData( x ) for x in
|
||||
diagnostics[ : self._max_diagnostics_to_display ] ]
|
||||
# self.parse_future = None
|
||||
|
||||
# if self.extra_parse_desired:
|
||||
# self.OnFileReadyToParse( request_data )
|
||||
|
||||
return self._last_prepared_diagnostics
|
||||
diagnostics = self._completer.DiagnosticsForFile(
|
||||
ToUtf8IfNeeded( filename ) )
|
||||
self._diagnostic_store = DiagnosticsToDiagStructure( diagnostics )
|
||||
return [ ConvertToDiagnosticResponse( x ) for x in
|
||||
diagnostics[ : self._max_diagnostics_to_display ] ]
|
||||
|
||||
|
||||
def GetDetailedDiagnostic( self, request_data ):
|
||||
@ -279,6 +263,7 @@ class ClangCompleter( Completer ):
|
||||
source,
|
||||
list( flags ) )
|
||||
|
||||
|
||||
def _FlagsForRequest( self, request_data ):
|
||||
filename = request_data[ 'filepath' ]
|
||||
if 'compilation_flags' in request_data:
|
||||
@ -286,20 +271,6 @@ class ClangCompleter( Completer ):
|
||||
filename )
|
||||
return self._flags.FlagsForFile( filename )
|
||||
|
||||
# TODO: Make this work again
|
||||
# def DiagnosticToDict( diagnostic ):
|
||||
# # see :h getqflist for a description of the dictionary fields
|
||||
# return {
|
||||
# # TODO: wrap the bufnr generation into a function
|
||||
# 'bufnr' : int( vim.eval( "bufnr('{0}', 1)".format(
|
||||
# diagnostic.filename_ ) ) ),
|
||||
# 'lnum' : diagnostic.line_number_,
|
||||
# 'col' : diagnostic.column_number_,
|
||||
# 'text' : diagnostic.text_,
|
||||
# 'type' : diagnostic.kind_,
|
||||
# 'valid' : 1
|
||||
# }
|
||||
|
||||
|
||||
def ConvertCompletionData( completion_data ):
|
||||
return responses.BuildCompletionData(
|
||||
@ -326,3 +297,11 @@ def InCFamilyFile( filetypes ):
|
||||
return ClangAvailableForFiletypes( filetypes )
|
||||
|
||||
|
||||
def ConvertToDiagnosticResponse( diagnostic ):
|
||||
return responses.BuildDiagnosticData( diagnostic.filename_,
|
||||
diagnostic.line_number_ - 1,
|
||||
diagnostic.column_number_ - 1,
|
||||
diagnostic.text_,
|
||||
diagnostic.kind_ )
|
||||
|
||||
|
||||
|
@ -1 +1 @@
|
||||
{
"filepath_completion_use_working_dir": 0,
"min_num_of_chars_for_completion": 2,
"semantic_triggers": {},
"collect_identifiers_from_comments_and_strings": 0,
"filetype_specific_completion_to_disable": {},
"collect_identifiers_from_tags_files": 0,
"extra_conf_globlist": [],
"global_ycm_extra_conf": "",
"confirm_extra_conf": 1,
"complete_in_comments": 0,
"complete_in_strings": 1,
"min_num_identifier_candidate_chars": 0,
"max_diagnostics_to_display": 30,
"auto_stop_csharp_server": 1,
"seed_identifiers_with_syntax": 0,
"csharp_server_port": 2000,
"filetype_whitelist": {
"*": "1"
},
"auto_start_csharp_server": 1,
"filetype_blacklist": {
"tagbar": "1",
"qf": "1",
"gitcommit": "1",
"notes": "1",
"markdown": "1",
"unite": "1",
"text": "1"
}
}
|
||||
{
"filepath_completion_use_working_dir": 0,
"min_num_of_chars_for_completion": 2,
"semantic_triggers": {},
"collect_identifiers_from_comments_and_strings": 0,
"filetype_specific_completion_to_disable": {
"gitcommit": 1,
},
"collect_identifiers_from_tags_files": 0,
"extra_conf_globlist": [],
"global_ycm_extra_conf": "",
"confirm_extra_conf": 1,
"complete_in_comments": 0,
"complete_in_strings": 1,
"min_num_identifier_candidate_chars": 0,
"max_diagnostics_to_display": 30,
"auto_stop_csharp_server": 1,
"seed_identifiers_with_syntax": 0,
"csharp_server_port": 2000,
"filetype_whitelist": {
"*": "1"
},
"auto_start_csharp_server": 1,
"filetype_blacklist": {
"tagbar": "1",
"qf": "1",
"notes": "1",
"markdown": "1",
"unite": "1",
"text": "1"
}
}
|
@ -21,7 +21,8 @@ from webtest import TestApp
|
||||
from .. import ycmd
|
||||
from ..responses import BuildCompletionData
|
||||
from nose.tools import ok_, eq_, with_setup
|
||||
from hamcrest import assert_that, has_items, has_entry
|
||||
from hamcrest import ( assert_that, has_items, has_entry, contains,
|
||||
contains_string, has_entries )
|
||||
import bottle
|
||||
|
||||
bottle.debug( True )
|
||||
@ -281,6 +282,47 @@ def DefinedSubcommands_WorksWhenNoExplicitCompleterTargetSpecified_test():
|
||||
app.post_json( '/defined_subcommands', subcommands_data ).json )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def GetDiagnostics_ClangCompleter_ZeroBasedLineAndColumn_test():
|
||||
app = TestApp( ycmd.app )
|
||||
contents = """
|
||||
struct Foo {
|
||||
int x // semicolon missing here!
|
||||
int y;
|
||||
int c;
|
||||
int d;
|
||||
};
|
||||
"""
|
||||
|
||||
filename = '/foo.cpp'
|
||||
diag_data = {
|
||||
'compilation_flags': ['-x', 'c++'],
|
||||
'line_num': 0,
|
||||
'column_num': 0,
|
||||
'filetypes': ['cpp'],
|
||||
'filepath': filename,
|
||||
'file_data': {
|
||||
filename: {
|
||||
'contents': contents,
|
||||
'filetypes': ['cpp']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event_data = diag_data.copy()
|
||||
event_data.update( {
|
||||
'event_name': 'FileReadyToParse',
|
||||
} )
|
||||
|
||||
app.post_json( '/event_notification', event_data )
|
||||
results = app.post_json( '/diagnostics', diag_data ).json
|
||||
assert_that( results,
|
||||
contains(
|
||||
has_entries( { 'text': contains_string( "expected ';'" ),
|
||||
'line_num': 2,
|
||||
'column_num': 7 } ) ) )
|
||||
|
||||
|
||||
@with_setup( Setup )
|
||||
def FiletypeCompletionAvailable_Works_test():
|
||||
app = TestApp( ycmd.app )
|
||||
|
@ -94,18 +94,35 @@ def GetCompletions():
|
||||
return _JsonResponse( completer.ComputeCandidates( request_data ) )
|
||||
|
||||
|
||||
@app.post( '/diagnostics' )
|
||||
def GetDiagnostics():
|
||||
LOGGER.info( 'Received diagnostics request')
|
||||
request_data = request.json
|
||||
completer = _GetCompleterForRequestData( request_data )
|
||||
|
||||
return _JsonResponse( completer.GetDiagnosticsForCurrentFile(
|
||||
request_data ) )
|
||||
|
||||
|
||||
@app.get( '/user_options' )
|
||||
def GetUserOptions():
|
||||
LOGGER.info( 'Received user options GET request')
|
||||
return _JsonResponse( dict( SERVER_STATE.user_options ) )
|
||||
|
||||
|
||||
@app.get( '/healthy' )
|
||||
def GetHealthy():
|
||||
LOGGER.info( 'Received health request')
|
||||
return _JsonResponse( True )
|
||||
|
||||
|
||||
@app.post( '/user_options' )
|
||||
def SetUserOptions():
|
||||
LOGGER.info( 'Received user options POST request')
|
||||
_SetUserOptions( request.json )
|
||||
|
||||
|
||||
# TODO: Rename this to 'semantic_completion_available'
|
||||
@app.post( '/filetype_completion_available')
|
||||
def FiletypeCompletionAvailable():
|
||||
LOGGER.info( 'Received filetype completion available request')
|
||||
|
@ -21,6 +21,7 @@ import tempfile
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import functools
|
||||
|
||||
def IsIdentifierChar( char ):
|
||||
return char.isalnum() or char == '_'
|
||||
@ -63,4 +64,13 @@ def AddThirdPartyFoldersToSysPath():
|
||||
sys.path.insert( 0, os.path.realpath( os.path.join( path_to_third_party,
|
||||
folder ) ) )
|
||||
|
||||
def Memoize( obj ):
|
||||
cache = obj.cache = {}
|
||||
|
||||
@functools.wraps( obj )
|
||||
def memoizer( *args, **kwargs ):
|
||||
key = str( args ) + str( kwargs )
|
||||
if key not in cache:
|
||||
cache[ key ] = obj( *args, **kwargs )
|
||||
return cache[ key ]
|
||||
return memoizer
|
||||
|
@ -18,6 +18,7 @@
|
||||
# along with YouCompleteMe. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import vim
|
||||
import os
|
||||
|
||||
def CurrentLineAndColumn():
|
||||
"""Returns the 0-based current line and 0-based current column."""
|
||||
@ -75,6 +76,12 @@ def GetUnsavedAndCurrentBufferData():
|
||||
return buffers_data
|
||||
|
||||
|
||||
def GetBufferNumberForFilename( filename, open_file_if_needed = True ):
|
||||
return int( vim.eval( "bufnr('{0}', {1})".format(
|
||||
os.path.realpath( filename ),
|
||||
int( open_file_if_needed ) ) ) )
|
||||
|
||||
|
||||
# Both |line| and |column| need to be 1-based
|
||||
def JumpToLocation( filename, line, column ):
|
||||
# Add an entry to the jumplist
|
||||
|
@ -29,6 +29,7 @@ from ycm.completers.general import syntax_parse
|
||||
from ycm.client.base_request import BaseRequest, BuildRequestData
|
||||
from ycm.client.command_request import SendCommandRequest
|
||||
from ycm.client.completion_request import CompletionRequest
|
||||
from ycm.client.diagnostics_request import DiagnosticsRequest
|
||||
from ycm.client.event_notification import SendEventNotificationAsync
|
||||
|
||||
try:
|
||||
@ -43,7 +44,8 @@ class YouCompleteMe( object ):
|
||||
def __init__( self, user_options ):
|
||||
self._user_options = user_options
|
||||
self._omnicomp = OmniCompleter( user_options )
|
||||
self._current_completion_request = None
|
||||
self._latest_completion_request = None
|
||||
self._latest_diagnostics_request = None
|
||||
self._server_stdout = None
|
||||
self._server_stderr = None
|
||||
self._server_popen = None
|
||||
@ -91,8 +93,8 @@ class YouCompleteMe( object ):
|
||||
# We have to store a reference to the newly created CompletionRequest
|
||||
# because VimScript can't store a reference to a Python object across
|
||||
# function calls... Thus we need to keep this request somewhere.
|
||||
self._current_completion_request = CompletionRequest()
|
||||
return self._current_completion_request
|
||||
self._latest_completion_request = CompletionRequest()
|
||||
return self._latest_completion_request
|
||||
|
||||
|
||||
def SendCommandRequest( self, arguments, completer ):
|
||||
@ -105,7 +107,7 @@ class YouCompleteMe( object ):
|
||||
|
||||
|
||||
def GetCurrentCompletionRequest( self ):
|
||||
return self._current_completion_request
|
||||
return self._latest_completion_request
|
||||
|
||||
|
||||
def GetOmniCompleter( self ):
|
||||
@ -114,8 +116,7 @@ class YouCompleteMe( object ):
|
||||
|
||||
def NativeFiletypeCompletionAvailable( self ):
|
||||
try:
|
||||
return BaseRequest.PostDataToHandler( BuildRequestData(),
|
||||
'filetype_completion_available')
|
||||
return _NativeFiletypeCompletionAvailableForFile( vim.current.buffer.name )
|
||||
except:
|
||||
return False
|
||||
|
||||
@ -174,17 +175,25 @@ class YouCompleteMe( object ):
|
||||
SendEventNotificationAsync( 'CurrentIdentifierFinished' )
|
||||
|
||||
|
||||
# TODO: Make this work again.
|
||||
def DiagnosticsForCurrentFileReady( self ):
|
||||
# if self.FiletypeCompletionUsable():
|
||||
# return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
|
||||
return False
|
||||
return bool( self._latest_diagnostics_request and
|
||||
self._latest_diagnostics_request.Done() )
|
||||
|
||||
|
||||
# TODO: Make this work again.
|
||||
def GetDiagnosticsForCurrentFile( self ):
|
||||
# if self.FiletypeCompletionUsable():
|
||||
# return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
|
||||
def RequestDiagnosticsForCurrentFile( self ):
|
||||
self._latest_diagnostics_request = DiagnosticsRequest()
|
||||
self._latest_diagnostics_request.Start()
|
||||
|
||||
|
||||
def GetDiagnosticsFromStoredRequest( self ):
|
||||
if self._latest_diagnostics_request:
|
||||
to_return = self._latest_diagnostics_request.Response()
|
||||
# We set the diagnostics request to None because we want to prevent
|
||||
# Syntastic from repeatedly refreshing the buffer with the same diags.
|
||||
# Setting this to None makes DiagnosticsForCurrentFileReady return False
|
||||
# until the next request is created.
|
||||
self._latest_diagnostics_request = None
|
||||
return to_return
|
||||
return []
|
||||
|
||||
|
||||
@ -260,3 +269,12 @@ def _AddUltiSnipsDataIfNeeded( extra_data ):
|
||||
} for x in rawsnips ]
|
||||
|
||||
|
||||
# 'filepath' is here only as a key for Memoize
|
||||
# This can't be a nested function inside NativeFiletypeCompletionAvailable
|
||||
# because then the Memoize decorator wouldn't work (nested functions are
|
||||
# re-created on every call to the outer function).
|
||||
@utils.Memoize
|
||||
def _NativeFiletypeCompletionAvailableForFile( filepath ):
|
||||
return BaseRequest.PostDataToHandler( BuildRequestData(),
|
||||
'filetype_completion_available')
|
||||
|
||||
|
80
third_party/retries/retries.py
vendored
Normal file
80
third_party/retries/retries.py
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2012 by Jeff Laughlin Consulting LLC
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
|
||||
import sys
|
||||
from time import sleep
|
||||
|
||||
# Source: https://gist.github.com/n1ywb/2570004
|
||||
|
||||
def example_exc_handler(tries_remaining, exception, delay):
|
||||
"""Example exception handler; prints a warning to stderr.
|
||||
|
||||
tries_remaining: The number of tries remaining.
|
||||
exception: The exception instance which was raised.
|
||||
"""
|
||||
print >> sys.stderr, "Caught '%s', %d tries remaining, sleeping for %s seconds" % (exception, tries_remaining, delay)
|
||||
|
||||
|
||||
def retries(max_tries, delay=1, backoff=2, exceptions=(Exception,), hook=None):
|
||||
"""Function decorator implementing retrying logic.
|
||||
|
||||
delay: Sleep this many seconds * backoff * try number after failure
|
||||
backoff: Multiply delay by this factor after each failure
|
||||
exceptions: A tuple of exception classes; default (Exception,)
|
||||
hook: A function with the signature myhook(tries_remaining, exception);
|
||||
default None
|
||||
|
||||
The decorator will call the function up to max_tries times if it raises
|
||||
an exception.
|
||||
|
||||
By default it catches instances of the Exception class and subclasses.
|
||||
This will recover after all but the most fatal errors. You may specify a
|
||||
custom tuple of exception classes with the 'exceptions' argument; the
|
||||
function will only be retried if it raises one of the specified
|
||||
exceptions.
|
||||
|
||||
Additionally you may specify a hook function which will be called prior
|
||||
to retrying with the number of remaining tries and the exception instance;
|
||||
see given example. This is primarily intended to give the opportunity to
|
||||
log the failure. Hook is not called after failure if no retries remain.
|
||||
"""
|
||||
def dec(func):
|
||||
def f2(*args, **kwargs):
|
||||
mydelay = delay
|
||||
tries = range(max_tries)
|
||||
tries.reverse()
|
||||
for tries_remaining in tries:
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except exceptions as e:
|
||||
if tries_remaining > 0:
|
||||
if hook is not None:
|
||||
hook(tries_remaining, e, mydelay)
|
||||
sleep(mydelay)
|
||||
mydelay = mydelay * backoff
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
break
|
||||
return f2
|
||||
return dec
|
Loading…
Reference in New Issue
Block a user