From 958a008462858873108b98eb8229e80b064310ff Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Sat, 28 Jul 2012 15:27:30 -0700 Subject: [PATCH] Adding diagnostic extraction support Next step is to add support to Syntastic so that it uses this new functionality --- autoload/youcompleteme.vim | 9 +++++ cpp/ycm/CMakeLists.txt | 4 +- cpp/ycm/ClangCompleter.cpp | 76 ++++++++++++++++++++++++++++++++++++++ cpp/ycm/ClangCompleter.h | 3 ++ cpp/ycm/CompletionData.h | 2 + cpp/ycm/Diagnostic.h | 54 +++++++++++++++++++++++++++ cpp/ycm/Utils.h | 4 +- cpp/ycm/indexer.cpp | 12 ++++++ plugin/youcompleteme.vim | 3 +- python/ycm.py | 18 +++++++++ 10 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 cpp/ycm/Diagnostic.h diff --git a/autoload/youcompleteme.vim b/autoload/youcompleteme.vim index 315d91a3..ccb8d151 100644 --- a/autoload/youcompleteme.vim +++ b/autoload/youcompleteme.vim @@ -132,6 +132,7 @@ function! s:OnInsertLeave() let s:omnifunc_mode = 0 endfunction + function! s:IdentifierFinishedOperations() if !pyeval( 'ycm.CurrentIdentifierFinished()' ) return @@ -273,6 +274,14 @@ function! youcompleteme#ClangOmniComplete( findstart, base ) endif endfunction + +function! youcompleteme#CurrentFileDiagnostics() + if s:ClangEnabledForCurrentFile() + return pyeval( 'clangcomp.GetDiagnosticsForCurrentFile()' ) + endif + return [] +endfunction + " This is basic vim plugin boilerplate let &cpo = s:save_cpo unlet s:save_cpo diff --git a/cpp/ycm/CMakeLists.txt b/cpp/ycm/CMakeLists.txt index 00a23e6f..5ade9691 100644 --- a/cpp/ycm/CMakeLists.txt +++ b/cpp/ycm/CMakeLists.txt @@ -80,14 +80,14 @@ set_target_properties( ${PROJECT_NAME} PROPERTIES if( CMAKE_COMPILER_IS_GNUCXX OR COMPILER_IS_CLANG ) # We want all warnings, and warnings should be treated as errors - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Werror" ) endif() ############################################################################# # We want warnings if we accidentally use C++11 features if ( COMPILER_IS_CLANG ) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wc++98-compat") + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wc++98-compat" ) endif() ############################################################################# diff --git a/cpp/ycm/ClangCompleter.cpp b/cpp/ycm/ClangCompleter.cpp index 6f746f4f..afc5112d 100644 --- a/cpp/ycm/ClangCompleter.cpp +++ b/cpp/ycm/ClangCompleter.cpp @@ -168,6 +168,28 @@ char CursorKindToVimKind( CXCursorKind kind ) } +char DiagnosticSeverityToType( CXDiagnosticSeverity severity ) +{ + switch ( severity ) + { + case CXDiagnostic_Ignored: + case CXDiagnostic_Note: + return 'I'; + + case CXDiagnostic_Warning: + return 'W'; + + case CXDiagnostic_Error: + case CXDiagnostic_Fatal: + return 'E'; + + default: + return 'E'; + } +} + + +// TODO: this should be a constructor CompletionData CompletionResultToCompletionData( const CXCompletionResult &completion_result ) { @@ -238,6 +260,34 @@ std::vector< CompletionData > ToCompletionDataVector( } +Diagnostic CXDiagnosticToDiagnostic( CXDiagnostic cxdiagnostic ) +{ + Diagnostic diagnostic; + diagnostic.kind_ = DiagnosticSeverityToType( + clang_getDiagnosticSeverity( cxdiagnostic ) ); + + // If this is an "ignored" diagnostic, there's no point in continuing since we + // won't display those to the user + if ( diagnostic.kind_ == 'I' ) + return diagnostic; + + CXSourceLocation location = clang_getDiagnosticLocation( cxdiagnostic ); + CXFile file; + uint unused_offset; + clang_getSpellingLocation( location, + &file, + &diagnostic.line_number_, + &diagnostic.column_number_, + &unused_offset ); + diagnostic.filename_ = CXStringToString( clang_getFileName( file ) ); + diagnostic.text_ = CXStringToString( + clang_getDiagnosticSpelling( cxdiagnostic ) ); + + clang_disposeDiagnostic( cxdiagnostic ); + return diagnostic; +} + + } // unnamed namespace @@ -287,6 +337,32 @@ void ClangCompleter::SetFileCompileFlags( } +std::vector< Diagnostic > ClangCompleter::DiagnosticsForFile( + const std::string &filename ) +{ + CXTranslationUnit unit = FindWithDefault( filename_to_translation_unit_, + filename, + NULL ); + std::vector< Diagnostic > diagnostics; + if ( !unit ) + return diagnostics; + + uint num_diagnostics = clang_getNumDiagnostics( unit ); + diagnostics.reserve( num_diagnostics ); + + for ( uint i = 0; i < num_diagnostics; ++i ) + { + Diagnostic diagnostic = CXDiagnosticToDiagnostic( + clang_getDiagnostic( unit, i ) ); + + if ( diagnostic.kind_ != 'I' ) + diagnostics.push_back( diagnostic ); + } + + return diagnostics; +} + + bool ClangCompleter::UpdatingTranslationUnit() { lock_guard< mutex > lock( file_parse_task_mutex_ ); diff --git a/cpp/ycm/ClangCompleter.h b/cpp/ycm/ClangCompleter.h index 2afade60..9f1c04db 100644 --- a/cpp/ycm/ClangCompleter.h +++ b/cpp/ycm/ClangCompleter.h @@ -21,6 +21,7 @@ #include "ConcurrentLatestValue.h" #include "Future.h" #include "UnsavedFile.h" +#include "Diagnostic.h" #include #include @@ -60,6 +61,8 @@ public: void SetFileCompileFlags( const std::string &filename, const std::vector< std::string > &flags ); + std::vector< Diagnostic > DiagnosticsForFile( const std::string &filename ); + bool UpdatingTranslationUnit(); void UpdateTranslationUnit( const std::string &filename, diff --git a/cpp/ycm/CompletionData.h b/cpp/ycm/CompletionData.h index 10253494..41b54f84 100644 --- a/cpp/ycm/CompletionData.h +++ b/cpp/ycm/CompletionData.h @@ -18,6 +18,8 @@ #ifndef COMPLETIONDATA_H_2JCTF1NU #define COMPLETIONDATA_H_2JCTF1NU +#include + namespace YouCompleteMe { diff --git a/cpp/ycm/Diagnostic.h b/cpp/ycm/Diagnostic.h new file mode 100644 index 00000000..30ea2e33 --- /dev/null +++ b/cpp/ycm/Diagnostic.h @@ -0,0 +1,54 @@ +// Copyright (C) 2011, 2012 Strahinja Val Markovic +// +// 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 . + +#ifndef DIAGNOSTIC_H_BZH3BWIZ +#define DIAGNOSTIC_H_BZH3BWIZ + +#include "standard.h" +#include + +namespace YouCompleteMe +{ + +struct Diagnostic +{ + bool operator== ( const Diagnostic &other ) const + { + return + line_number_ == other.line_number_ && + column_number_ == other.column_number_ && + kind_ == other.kind_ && + text_ == other.text_; + } + + uint line_number_; + uint column_number_; + + // Vim's error "kind" + // 'I' -> informational + // 'W' -> warning + // 'E' -> error + char kind_; + + std::string filename_; + + std::string text_; +}; + +} // namespace YouCompleteMe + +#endif /* end of include guard: DIAGNOSTIC_H_BZH3BWIZ */ diff --git a/cpp/ycm/Utils.h b/cpp/ycm/Utils.h index 699a24fa..a1d2586f 100644 --- a/cpp/ycm/Utils.h +++ b/cpp/ycm/Utils.h @@ -55,13 +55,13 @@ bool ContainsKey( Container &container, const Key &key ) template -const typename Container::mapped_type & +typename Container::mapped_type FindWithDefault( Container &container, const Key &key, const typename Container::mapped_type &value ) { typename Container::iterator it = container.find( key ); - return it != container.end() ? *it : value; + return it != container.end() ? it->second : value; } } // namespace YouCompleteMe diff --git a/cpp/ycm/indexer.cpp b/cpp/ycm/indexer.cpp index ab9a029d..ab969ed2 100644 --- a/cpp/ycm/indexer.cpp +++ b/cpp/ycm/indexer.cpp @@ -19,6 +19,7 @@ #include "ClangCompleter.h" #include "Future.h" #include "CompletionData.h" +#include "Diagnostic.h" #include "UnsavedFile.h" #include @@ -47,6 +48,16 @@ BOOST_PYTHON_MODULE(indexer) "CompletionVec" ) .def( vector_indexing_suite< std::vector< CompletionData > >() ); + class_< Diagnostic >( "Diagnostic" ) + .def_readonly( "line_number_", &Diagnostic::line_number_ ) + .def_readonly( "column_number_", &Diagnostic::column_number_ ) + .def_readonly( "kind_", &Diagnostic::kind_ ) + .def_readonly( "filename_", &Diagnostic::filename_ ) + .def_readonly( "text_", &Diagnostic::text_ ); + + class_< std::vector< Diagnostic > >( "DiagnosticVec" ) + .def( vector_indexing_suite< std::vector< Diagnostic > >() ); + class_< Future< AsyncResults > >( "Future" ) .def( "ResultsReady", &Future< AsyncResults >::ResultsReady ) .def( "GetResults", &Future< AsyncResults >::GetResults ); @@ -86,6 +97,7 @@ BOOST_PYTHON_MODULE(indexer) .def( "EnableThreading", &ClangCompleter::EnableThreading ) .def( "SetGlobalCompileFlags", &ClangCompleter::SetGlobalCompileFlags ) .def( "SetFileCompileFlags", &ClangCompleter::SetFileCompileFlags ) + .def( "DiagnosticsForFile", &ClangCompleter::DiagnosticsForFile ) .def( "UpdatingTranslationUnit", &ClangCompleter::UpdatingTranslationUnit ) .def( "UpdateTranslationUnitAsync", &ClangCompleter::UpdateTranslationUnitAsync ) diff --git a/plugin/youcompleteme.vim b/plugin/youcompleteme.vim index 1c00b15c..b5150e22 100644 --- a/plugin/youcompleteme.vim +++ b/plugin/youcompleteme.vim @@ -15,8 +15,9 @@ " You should have received a copy of the GNU General Public License " along with YouCompleteMe. If not, see . -if exists("g:loaded_youcompleteme") +if exists( "g:loaded_youcompleteme" ) finish +" TODO: check for python too elseif v:version < 703 || !has( 'patch584' ) echohl WarningMsg | \ echomsg "YouCompleteMe unavailable: requires Vim 7.3.584+" | diff --git a/python/ycm.py b/python/ycm.py index 9842a797..ef952306 100644 --- a/python/ycm.py +++ b/python/ycm.py @@ -184,6 +184,12 @@ class ClangCompleter( Completer ): self.GetUnsavedFilesVector() ) + def GetDiagnosticsForCurrentFile( self ): + return [ DiagnosticToDict( x ) for x in + self.completer.DiagnosticsForFile( vim.current.buffer.name ) ] + + + def PostVimMessage( message ): # TODO: escape the message string before formating it vim.command( 'echohl WarningMsg | echomsg "{0}" | echohl None' @@ -210,6 +216,18 @@ def CompletionDataToDict( completion_data ): } +def DiagnosticToDict( diagnostic ): + # see :h getqflist for a description of the dictionary fields + return { + 'bufnr' : int( vim.eval( "bufnr('" + diagnostic.filename_ + "', 1)" ) ), + 'lnum' : diagnostic.line_number_, + 'col' : diagnostic.column_number_, + 'text' : diagnostic.text_, + 'type' : diagnostic.kind_, + 'valid' : 1 + } + + 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."""