diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index a4ed3095..18856e25 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -74,6 +74,9 @@ function! youcompleteme#Enable() inoremap endif + " TODO: make this a nicer, customizable map + nnoremap d :call ShowDetailedDiagnostic() + " Calling this once solves the problem of BufRead/BufEnter not triggering for " the first loaded file. This should be the last command executed in this " function! @@ -350,6 +353,11 @@ function! youcompleteme#OmniComplete( findstart, base ) endfunction +function! s:ShowDetailedDiagnostic() + py ycm_state.ShowDetailedDiagnostic() +endfunction + + " This is what Syntastic calls indirectly when it decides an auto-check is " required (currently that's on buffer save) OR when the SyntasticCheck command " is invoked @@ -357,6 +365,7 @@ function! youcompleteme#CurrentFileDiagnostics() return pyeval( 'ycm_state.GetDiagnosticsForCurrentFile()' ) endfunction + " This is basic vim plugin boilerplate let &cpo = s:save_cpo unlet s:save_cpo diff --git a/cpp/ycm/ClangUtils.cpp b/cpp/ycm/ClangUtils.cpp index 2d4e1b07..14fcf48a 100644 --- a/cpp/ycm/ClangUtils.cpp +++ b/cpp/ycm/ClangUtils.cpp @@ -123,6 +123,38 @@ std::vector< CompletionData > ToCompletionDataVector( } +// NOTE: The passed in pointer should never be NULL! +// TODO: move all functions that are not external into an unnamed namespace +std::string FullDiagnosticText( CXDiagnostic cxdiagnostic ) +{ + std::string full_text = CXStringToString( clang_formatDiagnostic( + cxdiagnostic, + clang_defaultDiagnosticDisplayOptions() ) ); + + // Note: clang docs say that a CXDiagnosticSet retrieved with + // clang_getChildDiagnostics do NOT need to be released with + // clang_diposeDiagnosticSet + CXDiagnosticSet diag_set = clang_getChildDiagnostics( cxdiagnostic ); + if ( !diag_set ) + return full_text; + + uint num_child_diagnostics = clang_getNumDiagnosticsInSet( diag_set ); + if ( !num_child_diagnostics ) + return full_text; + + for ( uint i = 0; i < num_child_diagnostics; ++i ) + { + CXDiagnostic diagnostic = clang_getDiagnosticInSet( diag_set, i ); + if ( !diagnostic ) + continue; + + full_text.append( FullDiagnosticText( diagnostic ) ); + } + + return full_text; +} + + Diagnostic CXDiagnosticToDiagnostic( CXDiagnostic cxdiagnostic ) { Diagnostic diagnostic; @@ -145,9 +177,11 @@ Diagnostic CXDiagnosticToDiagnostic( CXDiagnostic cxdiagnostic ) &diagnostic.line_number_, &diagnostic.column_number_, &unused_offset ); + diagnostic.filename_ = CXStringToString( clang_getFileName( file ) ); diagnostic.text_ = CXStringToString( clang_getDiagnosticSpelling( cxdiagnostic ) ); + diagnostic.long_formatted_text_ = FullDiagnosticText( cxdiagnostic ); clang_disposeDiagnostic( cxdiagnostic ); return diagnostic; diff --git a/cpp/ycm/Diagnostic.h b/cpp/ycm/Diagnostic.h index 30ea2e33..e8ba1ffc 100644 --- a/cpp/ycm/Diagnostic.h +++ b/cpp/ycm/Diagnostic.h @@ -47,6 +47,8 @@ struct Diagnostic std::string filename_; std::string text_; + + std::string long_formatted_text_; }; } // namespace YouCompleteMe diff --git a/cpp/ycm/ycm_core.cpp b/cpp/ycm/ycm_core.cpp index 7e077109..41731162 100644 --- a/cpp/ycm/ycm_core.cpp +++ b/cpp/ycm/ycm_core.cpp @@ -54,7 +54,8 @@ BOOST_PYTHON_MODULE(ycm_core) .def_readonly( "column_number_", &Diagnostic::column_number_ ) .def_readonly( "kind_", &Diagnostic::kind_ ) .def_readonly( "filename_", &Diagnostic::filename_ ) - .def_readonly( "text_", &Diagnostic::text_ ); + .def_readonly( "text_", &Diagnostic::text_ ) + .def_readonly( "long_formatted_text_", &Diagnostic::long_formatted_text_ ); class_< std::vector< Diagnostic > >( "DiagnosticVec" ) .def( vector_indexing_suite< std::vector< Diagnostic > >() ); diff --git a/python/completers/completer.py b/python/completers/completer.py index 2db18fff..77100c7a 100644 --- a/python/completers/completer.py +++ b/python/completers/completer.py @@ -78,6 +78,10 @@ class Completer( object ): return [] + def ShowDetailedDiagnostic( self ): + pass + + @abc.abstractmethod def SupportedFiletypes( self ): pass diff --git a/python/completers/cpp/clang_completer.py b/python/completers/cpp/clang_completer.py index a1913699..b0427a84 100644 --- a/python/completers/cpp/clang_completer.py +++ b/python/completers/cpp/clang_completer.py @@ -18,6 +18,7 @@ # along with YouCompleteMe. If not, see . from completers.completer import Completer +from collections import defaultdict import vim import vimsupport import ycm_core @@ -32,7 +33,7 @@ class ClangCompleter( Completer ): self.completer.EnableThreading() self.contents_holder = [] self.filename_holder = [] - self.last_diagnostics = [] + self.last_prepared_diagnostics = [] self.parse_future = None self.flags = Flags() @@ -127,11 +128,38 @@ class ClangCompleter( Completer ): def GetDiagnosticsForCurrentFile( self ): if self.DiagnosticsForCurrentFileReady(): - self.last_diagnostics = [ DiagnosticToDict( x ) for x in - self.completer.DiagnosticsForFile( - vim.current.buffer.name ) ] + diagnostics = self.completer.DiagnosticsForFile( vim.current.buffer.name ) + self.diagnostic_store = DiagnosticsToDiagStructure( diagnostics ) + self.last_prepared_diagnostics = [ DiagnosticToDict( x ) for x in + diagnostics ] self.parse_future = None - return self.last_diagnostics + return self.last_prepared_diagnostics + + + def ShowDetailedDiagnostic( self ): + current_line, current_column = vimsupport.CurrentLineAndColumn() + + # CurrentLineAndColumn() numbers are 0-based, clang numbers are 1-based + current_line += 1 + current_column += 1 + + current_file = vim.current.buffer.name + diagnostics = self.diagnostic_store[ current_file ][ current_line ] + + if not diagnostics: + vimsupport.PostVimMessage( "No diagnostic for current line!" ) + return + + closest_diagnostic = None + distance_to_closest_diagnostic = 999 + + for diagnostic in diagnostics: + distance = abs( current_column - diagnostic.column_number_ ) + if distance < distance_to_closest_diagnostic: + distance_to_closest_diagnostic = distance + closest_diagnostic = diagnostic + + vimsupport.EchoText( closest_diagnostic.long_formatted_text_ ) def ShouldUseNow( self, start_column ): @@ -165,6 +193,14 @@ def DiagnosticToDict( diagnostic ): } +def DiagnosticsToDiagStructure( diagnostics ): + structure = defaultdict(lambda : defaultdict(list)) + for diagnostic in diagnostics: + structure[ diagnostic.filename_ ][ diagnostic.line_number_ ].append( + diagnostic ) + return structure + + def ClangAvailableForBuffer( buffer_object ): filetype = vim.eval( 'getbufvar({0}, "&ft")'.format( buffer_object.number ) ) return filetype in CLANG_FILETYPES diff --git a/python/vimsupport.py b/python/vimsupport.py index 827b5f32..fca45c99 100644 --- a/python/vimsupport.py +++ b/python/vimsupport.py @@ -20,6 +20,7 @@ import vim def CurrentLineAndColumn(): + """Returns the 0-based current line.""" # See the comment in CurrentColumn about the calculation for the line and # column number line, column = vim.current.window.cursor @@ -28,8 +29,9 @@ def CurrentLineAndColumn(): def CurrentColumn(): - """Do NOT access the CurrentColumn in vim.current.line. It doesn't exist yet. - Only the chars before the current column exist in vim.current.line.""" + """Returns the 0-based current column. Do NOT access the CurrentColumn in + vim.current.line. It doesn't exist yet. Only the chars before the current + column exist in vim.current.line.""" # vim's columns are 1-based while vim.current.line columns are 0-based # ... but vim.current.window.cursor (which returns a (line, column) tuple) @@ -58,6 +60,10 @@ def PostVimMessage( message ): .format( message ) ) +def EchoText( text ): + vim.command( "echom '{0}'".format( text.replace( "'", r"''") ) ) + + def EscapeForVim( text ): return text.replace( "'", "''" ) diff --git a/python/ycm.py b/python/ycm.py index 3333c83b..914639c0 100644 --- a/python/ycm.py +++ b/python/ycm.py @@ -106,6 +106,11 @@ class YouCompleteMe( object ): return [] + def ShowDetailedDiagnostic( self ): + if self.FiletypeCompletionEnabledForCurrentFile(): + return self.GetFiletypeCompleterForCurrentFile().ShowDetailedDiagnostic() + + def OnCurrentIdentifierFinished( self ): self.identcomp.OnCurrentIdentifierFinished()