From 1f094e50d03ea2655ac8adc8e37e43461cac3466 Mon Sep 17 00:00:00 2001 From: Strahinja Val Markovic Date: Sun, 31 Mar 2013 20:38:29 -0700 Subject: [PATCH] GoToDefinition/Declaration commands for C-family These are accessible through the :YcmCompleter command. The docs have more information. --- README.md | 78 ++++++++++--- cpp/ycm/ClangCompleter/ClangCompleter.cpp | 28 +++++ cpp/ycm/ClangCompleter/ClangCompleter.h | 15 +++ cpp/ycm/ClangCompleter/ClangUtils.cpp | 19 +++- cpp/ycm/ClangCompleter/ClangUtils.h | 8 ++ cpp/ycm/ClangCompleter/Location.h | 59 ++++++++++ cpp/ycm/ClangCompleter/TranslationUnit.cpp | 103 +++++++++++++++++- cpp/ycm/ClangCompleter/TranslationUnit.h | 26 ++++- .../ClangCompleter/TranslationUnit_test.cpp | 72 +++++++++++- cpp/ycm/tests/testdata/goto.cpp | 18 +++ cpp/ycm/ycm_core.cpp | 11 +- python/completers/cpp/clang_completer.py | 78 +++++++++++++ python/vimsupport.py | 19 ++++ python/ycm.py | 2 +- 14 files changed, 504 insertions(+), 32 deletions(-) create mode 100644 cpp/ycm/ClangCompleter/Location.h create mode 100644 cpp/ycm/tests/testdata/goto.cpp diff --git a/README.md b/README.md index 79202ce3..688fe34b 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Command Line Tools (that you install from within Xcode). Install CMake. Preferably with [Homebrew][brew], but here's the [stand-alone CMake installer][cmake-download]. -_If_ you have installed a Homebrew Python and/or Homebrew MacVim, see the FAQ +_If_ you have installed a Homebrew Python and/or Homebrew MacVim, see the _FAQ_ for details. Compiling YCM **with** semantic support for C-family languages: @@ -100,10 +100,10 @@ Compiling YCM **without** semantic support for C-family languages: cd ~/.vim/bundle/YouCompleteMe ./install.sh -That's it. You're done. Refer to the User Guide section on how to use YCM. Don't -forget that if you want the C-family semantic completion engine to work, you -will need to provide the compilation flags for your project to YCM. It's all in -the User Guide. +That's it. You're done. Refer to the _User Guide_ section on how to use YCM. +Don't forget that if you want the C-family semantic completion engine to work, +you will need to provide the compilation flags for your project to YCM. It's all +in the User Guide. YCM comes with sane defaults for its options, but you still may want to take a look at what's available for configuration. There are a few interesting options @@ -139,10 +139,10 @@ Compiling YCM **without** semantic support for C-family languages: cd ~/.vim/bundle/YouCompleteMe ./install.sh -That's it. You're done. Refer to the User Guide section on how to use YCM. Don't -forget that if you want the C-family semantic completion engine to work, you -will need to provide the compilation flags for your project to YCM. It's all in -the User Guide. +That's it. You're done. Refer to the _User Guide_ section on how to use YCM. +Don't forget that if you want the C-family semantic completion engine to work, +you will need to provide the compilation flags for your project to YCM. It's all +in the User Guide. YCM comes with sane defaults for its options, but you still may want to take a look at what's available for configuration. There are a few interesting options @@ -164,7 +164,7 @@ code is platform agnostic, so if everything is configured correctly, YCM _should_ work on Windows without issues (but as of writing, it's untested on that platform). -See the FAQ if you have any issues. +See the _FAQ_ if you have any issues. **Remember:** YCM is a plugin with a compiled component. If you **update** YCM using Vundle and the ycm_core library API has changed (happens rarely), YCM will @@ -270,10 +270,10 @@ notify you to recompile it. You should then rerun the install process. version 3.2 into the `YouCompleteMe/python` folder then YCM _will not work_ if you selected C-family support during YCM compilation. -That's it. You're done. Refer to the User Guide section on how to use YCM. Don't -forget that if you want the C-family semantic completion engine to work, you -will need to provide the compilation flags for your project to YCM. It's all in -the User Guide. +That's it. You're done. Refer to the _User Guide_ section on how to use YCM. +Don't forget that if you want the C-family semantic completion engine to work, +you will need to provide the compilation flags for your project to YCM. It's all +in the User Guide. YCM comes with sane defaults for its options, but you still may want to take a look at what's available for configuration. There are a few interesting options @@ -294,7 +294,7 @@ User Guide through the completions. Use Shift-TAB to cycle backwards. Note that if you're using console Vim (that is, not Gvim or MacVim) then it's likely that the Shift-TAB binding will not work because the console will not pass it to Vim. - You can remap the keys; see the options section below. + You can remap the keys; see the _Options_ section below. ### Completion string ranking @@ -328,7 +328,7 @@ compile the current file. You can also provide a path to a global `.ycm_extra_conf.py` file, which will be used as a fallback. To prevent the execution of malicious code from a file you didn't write YCM will ask you once per `.ycm_extra_conf.py` if it is safe to load. This can be disabled and you can -white-/blacklist files. See the Options section for more details. +white-/blacklist files. See the _Options_ section for more details. This system was designed this way so that the user can perform any arbitrary sequence of operations to produce a list of compilation flags YCM should hand @@ -492,6 +492,50 @@ used. Call `YcmCompleter` without further arguments for information about the commands you can call for the selected completer. +See the _YcmCompleter subcommands_ section for more information on the available +subcommands. + +YcmCompleter subcommands +------------------------ + +[See the docs for the `YcmCompleter` command before tackling this section.] + +The invoked subcommand is automatically routed to the currently active semantic +completer, so `:YcmCommand GoToDefinition` will invoke the `GoToDefinition` +subcommand on the Python semantic completer if the currently active file is a +Python one and on the Clang completer if the currently active file is a +C/C++/Objective-C one. + +You may also want to map the subcommands to something less verbose; for +instance, `nnoremap jd :YcmCommand GoToDefinitionElseDeclaration` +maps the `jd` sequence to the longer subcommand invocation. + +### The `GoToDeclaration` subcommand + +Looks up the symbol under the cursor and jumps to its declaration. + +Supported in filetypes: `c, cpp, objc, objcpp` + +### The `GoToDefinition` subcommand + +Looks up the symbol under the cursor and jumps to its definition. + +NOTE: For C-family languages **this only works in certain situations**, namely when +the definition of the symbol is in the current translation unit. A translation +unit consists of the file you are editing and all the files you are including +with `#include` directives (directly or indirectly) in that file. + +Supported in filetypes: `c, cpp, objc, objcpp` + +### The `GoToDefinitionElseDeclaration` subcommand + +Looks up the symbol under the cursor and jumps to its definition if possible; if +the definition is not accessible from the current translation unit, jumps to the +symbol's declaration. + +Supported in filetypes: `c, cpp, objc, objcpp` + + Options ------- @@ -904,7 +948,7 @@ This means that YCM tried to set up a key mapping but failed because you already had something mapped to that key combination. The `` part of the message will tell you what was the key combination that failed. -Look in the options section and see if which of the default mappings conflict +Look in the _Options_ section and see if which of the default mappings conflict with your own. Then change that option value to something else so that the conflict goes away. diff --git a/cpp/ycm/ClangCompleter/ClangCompleter.cpp b/cpp/ycm/ClangCompleter/ClangCompleter.cpp index 7165e27f..cc6a8240 100644 --- a/cpp/ycm/ClangCompleter/ClangCompleter.cpp +++ b/cpp/ycm/ClangCompleter/ClangCompleter.cpp @@ -262,6 +262,34 @@ ClangCompleter::CandidatesForQueryAndLocationInFileAsync( } +Location ClangCompleter::GetDeclarationLocation( + const std::string &filename, + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files, + const std::vector< std::string > &flags ) { + shared_ptr< TranslationUnit > unit = GetTranslationUnitForFile( + filename, + unsaved_files, + flags ); + return unit->GetDeclarationLocation( line, column, unsaved_files ); +} + + +Location ClangCompleter::GetDefinitionLocation( + const std::string &filename, + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files, + const std::vector< std::string > &flags ) { + shared_ptr< TranslationUnit > unit = GetTranslationUnitForFile( + filename, + unsaved_files, + flags ); + return unit->GetDefinitionLocation( line, column, unsaved_files ); +} + + void ClangCompleter::DeleteCachesForFileAsync( const std::string &filename ) { file_cache_delete_stack_.Push( filename ); } diff --git a/cpp/ycm/ClangCompleter/ClangCompleter.h b/cpp/ycm/ClangCompleter/ClangCompleter.h index 47f8aa82..22b4d92c 100644 --- a/cpp/ycm/ClangCompleter/ClangCompleter.h +++ b/cpp/ycm/ClangCompleter/ClangCompleter.h @@ -40,6 +40,7 @@ namespace YouCompleteMe { class CandidateRepository; class TranslationUnit; struct CompletionData; +struct Location; typedef std::vector< CompletionData > CompletionDatas; @@ -94,6 +95,20 @@ public: const std::vector< UnsavedFile > &unsaved_files, const std::vector< std::string > &flags ); + Location GetDeclarationLocation( + const std::string &filename, + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files, + const std::vector< std::string > &flags ); + + Location GetDefinitionLocation( + const std::string &filename, + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files, + const std::vector< std::string > &flags ); + void DeleteCachesForFileAsync( const std::string &filename ); private: diff --git a/cpp/ycm/ClangCompleter/ClangUtils.cpp b/cpp/ycm/ClangCompleter/ClangUtils.cpp index 447c3dee..30ec2767 100644 --- a/cpp/ycm/ClangCompleter/ClangUtils.cpp +++ b/cpp/ycm/ClangCompleter/ClangUtils.cpp @@ -188,7 +188,7 @@ Diagnostic DiagnosticWrapToDiagnostic( DiagnosticWrap diagnostic_wrap ) { &diagnostic.column_number_, &unused_offset ); - diagnostic.filename_ = CXStringToString( clang_getFileName( file ) ); + diagnostic.filename_ = CXFileToFilepath( file ); diagnostic.text_ = CXStringToString( clang_getDiagnosticSpelling( diagnostic_wrap.get() ) ); diagnostic.long_formatted_text_ = FullDiagnosticText( diagnostic_wrap.get() ); @@ -196,6 +196,23 @@ Diagnostic DiagnosticWrapToDiagnostic( DiagnosticWrap diagnostic_wrap ) { return diagnostic; } +bool CursorIsValid( CXCursor cursor ) { + return !clang_Cursor_isNull( cursor ) && + !clang_isInvalid( clang_getCursorKind( cursor ) ); +} + +bool CursorIsReference( CXCursor cursor ) { + return clang_isReference( clang_getCursorKind( cursor ) ); +} + +bool CursorIsDeclaration( CXCursor cursor ) { + return clang_isDeclaration( clang_getCursorKind( cursor ) ); +} + +std::string CXFileToFilepath( CXFile file ) { + return CXStringToString( clang_getFileName( file ) ); +} + std::string ClangVersion() { return CXStringToString( clang_getClangVersion() ); } diff --git a/cpp/ycm/ClangCompleter/ClangUtils.h b/cpp/ycm/ClangCompleter/ClangUtils.h index 09660728..b7dd7a43 100644 --- a/cpp/ycm/ClangCompleter/ClangUtils.h +++ b/cpp/ycm/ClangCompleter/ClangUtils.h @@ -42,6 +42,14 @@ std::vector< CXUnsavedFile > ToCXUnsavedFiles( Diagnostic DiagnosticWrapToDiagnostic( DiagnosticWrap diagnostic_wrap ); +bool CursorIsValid( CXCursor cursor ); + +bool CursorIsReference( CXCursor cursor ); + +bool CursorIsDeclaration( CXCursor cursor ); + +std::string CXFileToFilepath( CXFile file ); + std::string ClangVersion(); } // namespace YouCompleteMe diff --git a/cpp/ycm/ClangCompleter/Location.h b/cpp/ycm/ClangCompleter/Location.h new file mode 100644 index 00000000..a325d743 --- /dev/null +++ b/cpp/ycm/ClangCompleter/Location.h @@ -0,0 +1,59 @@ +// 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 LOCATION_H_6TLFQH4I +#define LOCATION_H_6TLFQH4I + +#include "standard.h" +#include +#include + +namespace YouCompleteMe { + +struct Location { + // Creates an invalid location + Location() + : line_number_( 0 ), + column_number_( 0 ), + filename_( "" ) {} + + Location( const std::string &filename, uint line, uint column ) + : line_number_( line ), + column_number_( column ), + filename_( filename ) {} + + bool operator== ( const Location &other ) const { + return + line_number_ == other.line_number_ && + column_number_ == other.column_number_ && + filename_ == other.filename_; + } + + bool IsValid() { + return !filename_.empty(); + } + + uint line_number_; + uint column_number_; + + // The full, absolute path + std::string filename_; +}; + +} // namespace YouCompleteMe + +#endif /* end of include guard: LOCATION_H_6TLFQH4I */ diff --git a/cpp/ycm/ClangCompleter/TranslationUnit.cpp b/cpp/ycm/ClangCompleter/TranslationUnit.cpp index 8b7aca00..3366d46c 100644 --- a/cpp/ycm/ClangCompleter/TranslationUnit.cpp +++ b/cpp/ycm/ClangCompleter/TranslationUnit.cpp @@ -21,7 +21,6 @@ #include "exceptions.h" #include "ClangUtils.h" -#include #include #include @@ -133,6 +132,17 @@ void TranslationUnit::Reparse( } +void TranslationUnit::ReparseForIndexing( + const std::vector< UnsavedFile > &unsaved_files ) { + std::vector< CXUnsavedFile > cxunsaved_files = + ToCXUnsavedFiles( unsaved_files ); + + Reparse( cxunsaved_files, + CXTranslationUnit_PrecompiledPreamble | + CXTranslationUnit_SkipFunctionBodies ); +} + + std::vector< CompletionData > TranslationUnit::CandidatesForLocation( int line, int column, @@ -170,22 +180,74 @@ std::vector< CompletionData > TranslationUnit::CandidatesForLocation( return candidates; } +Location TranslationUnit::GetDeclarationLocation( + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files ) { + ReparseForIndexing( unsaved_files ); + unique_lock< mutex > lock( clang_access_mutex_ ); + + if ( !clang_translation_unit_ ) + return Location(); + + CXCursor cursor = GetCursor( line, column ); + if ( !CursorIsValid( cursor ) ) + return Location(); + + CXCursor referenced_cursor = clang_getCursorReferenced( cursor ); + if ( !CursorIsValid( referenced_cursor ) ) + return Location(); + + return LocationFromSourceLocation( + clang_getCursorLocation( referenced_cursor ) ); +} + +Location TranslationUnit::GetDefinitionLocation( + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files ) { + ReparseForIndexing( unsaved_files ); + unique_lock< mutex > lock( clang_access_mutex_ ); + + if ( !clang_translation_unit_ ) + return Location(); + + CXCursor cursor = GetCursor( line, column ); + if ( !CursorIsValid( cursor ) ) + return Location(); + + CXCursor definition_cursor = clang_getCursorDefinition( cursor ); + if ( !CursorIsValid( definition_cursor ) ) + return Location(); + + return LocationFromSourceLocation( + clang_getCursorLocation( definition_cursor ) ); +} + // Argument taken as non-const ref because we need to be able to pass a // non-const pointer to clang. This function (and clang too) will not modify the // param though. void TranslationUnit::Reparse( std::vector< CXUnsavedFile > &unsaved_files ) { + Reparse( unsaved_files, clang_defaultEditingTranslationUnitOptions() ); +} + + +// Argument taken as non-const ref because we need to be able to pass a +// non-const pointer to clang. This function (and clang too) will not modify the +// param though. +void TranslationUnit::Reparse( std::vector< CXUnsavedFile > &unsaved_files, + uint parse_options ) { unique_lock< mutex > lock( clang_access_mutex_ ); if ( !clang_translation_unit_ ) return; - int failure = clang_reparseTranslationUnit( - clang_translation_unit_, - unsaved_files.size(), - &unsaved_files[ 0 ], - clang_defaultEditingTranslationUnitOptions() ); + int failure = clang_reparseTranslationUnit( clang_translation_unit_, + unsaved_files.size(), + &unsaved_files[ 0 ], + parse_options ); if ( failure ) { Destroy(); @@ -215,4 +277,33 @@ void TranslationUnit::UpdateLatestDiagnostics() { } } +CXCursor TranslationUnit::GetCursor( int line, int column ) { + // ASSUMES A LOCK IS ALREADY HELD ON clang_access_mutex_! + if ( !clang_translation_unit_ ) + return clang_getNullCursor(); + + CXFile file = clang_getFile( clang_translation_unit_, filename_.c_str() ); + CXSourceLocation source_location = clang_getLocation( + clang_translation_unit_, + file, + line, + column ); + + return clang_getCursor( clang_translation_unit_, source_location ); +} + +Location TranslationUnit::LocationFromSourceLocation( + CXSourceLocation source_location ) { + // ASSUMES A LOCK IS ALREADY HELD ON clang_access_mutex_! + if ( !clang_translation_unit_ ) + return Location(); + + CXFile file; + uint line; + uint column; + uint offset; + clang_getExpansionLocation( source_location, &file, &line, &column, &offset ); + return Location( CXFileToFilepath( file ), line, column ); +} + } // namespace YouCompleteMe diff --git a/cpp/ycm/ClangCompleter/TranslationUnit.h b/cpp/ycm/ClangCompleter/TranslationUnit.h index 313e7d60..53baf989 100644 --- a/cpp/ycm/ClangCompleter/TranslationUnit.h +++ b/cpp/ycm/ClangCompleter/TranslationUnit.h @@ -22,17 +22,15 @@ #include "Future.h" #include "UnsavedFile.h" #include "Diagnostic.h" +#include "Location.h" +#include #include #include #include #include -typedef void *CXIndex; -typedef struct CXTranslationUnitImpl *CXTranslationUnit; -struct CXUnsavedFile; - namespace YouCompleteMe { struct CompletionData; @@ -62,17 +60,35 @@ public: void Reparse( const std::vector< UnsavedFile > &unsaved_files ); + void ReparseForIndexing( const std::vector< UnsavedFile > &unsaved_files ); + std::vector< CompletionData > CandidatesForLocation( int line, int column, const std::vector< UnsavedFile > &unsaved_files ); -private: + Location GetDeclarationLocation( + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files ); + Location GetDefinitionLocation( + int line, + int column, + const std::vector< UnsavedFile > &unsaved_files ); + +private: void Reparse( std::vector< CXUnsavedFile > &unsaved_files ); + void Reparse( std::vector< CXUnsavedFile > &unsaved_files, + uint parse_options ); + void UpdateLatestDiagnostics(); + CXCursor GetCursor( int line, int column ); + + Location LocationFromSourceLocation( CXSourceLocation source_location ); + ///////////////////////////// // PRIVATE MEMBER VARIABLES ///////////////////////////// diff --git a/cpp/ycm/tests/ClangCompleter/TranslationUnit_test.cpp b/cpp/ycm/tests/ClangCompleter/TranslationUnit_test.cpp index d5dbc172..2ce0c3c6 100644 --- a/cpp/ycm/tests/ClangCompleter/TranslationUnit_test.cpp +++ b/cpp/ycm/tests/ClangCompleter/TranslationUnit_test.cpp @@ -21,6 +21,8 @@ #include #include +#include + #include namespace fs = boost::filesystem; @@ -29,7 +31,21 @@ using ::testing::WhenSorted; namespace YouCompleteMe { -TEST( TranslationUnitTest, ExceptionThrownOnParseFailure ) { +class TranslationUnitTest : public ::testing::Test { +protected: + virtual void SetUp() { + clang_index_ = clang_createIndex( 0, 0 ); + } + + virtual void TearDown() { + clang_disposeIndex( clang_index_ ); + } + + CXIndex clang_index_; +}; + + +TEST_F( TranslationUnitTest, ExceptionThrownOnParseFailure ) { fs::path test_file = fs::temp_directory_path() / fs::unique_path(); std::string junk = "#&9112(^(^#>@(^@!@(&#@a}}}}{nthoeu\n&&^^&^&!#%%@@!aeu"; WriteUtf8File( test_file, junk ); @@ -44,5 +60,59 @@ TEST( TranslationUnitTest, ExceptionThrownOnParseFailure ) { ClangParseError ); } +TEST_F( TranslationUnitTest, GoToDefinitionWorks ) { + fs::path path_to_testdata = fs::current_path() / fs::path( "testdata" ); + fs::path test_file = path_to_testdata / fs::path( "goto.cpp" ); + + TranslationUnit unit( test_file.string(), + std::vector< UnsavedFile >(), + std::vector< std::string >(), + clang_index_ ); + + Location location = unit.GetDefinitionLocation( + 15, + 3, + std::vector< UnsavedFile >() ); + + EXPECT_EQ( 1, location.line_number_ ); + EXPECT_EQ( 8, location.column_number_ ); + EXPECT_TRUE( !location.filename_.empty() ); +} + +TEST_F( TranslationUnitTest, GoToDefinitionFails ) { + fs::path path_to_testdata = fs::current_path() / fs::path( "testdata" ); + fs::path test_file = path_to_testdata / fs::path( "goto.cpp" ); + + TranslationUnit unit( test_file.string(), + std::vector< UnsavedFile >(), + std::vector< std::string >(), + clang_index_ ); + + Location location = unit.GetDefinitionLocation( + 17, + 3, + std::vector< UnsavedFile >() ); + + EXPECT_FALSE( location.IsValid() ); +} + +TEST_F( TranslationUnitTest, GoToDeclarationWorks ) { + fs::path path_to_testdata = fs::current_path() / fs::path( "testdata" ); + fs::path test_file = path_to_testdata / fs::path( "goto.cpp" ); + + TranslationUnit unit( test_file.string(), + std::vector< UnsavedFile >(), + std::vector< std::string >(), + clang_index_ ); + + Location location = unit.GetDeclarationLocation( + 17, + 3, + std::vector< UnsavedFile >() ); + + EXPECT_EQ( 12, location.line_number_ ); + EXPECT_EQ( 8, location.column_number_ ); + EXPECT_TRUE( !location.filename_.empty() ); +} } // namespace YouCompleteMe diff --git a/cpp/ycm/tests/testdata/goto.cpp b/cpp/ycm/tests/testdata/goto.cpp new file mode 100644 index 00000000..3e668b8d --- /dev/null +++ b/cpp/ycm/tests/testdata/goto.cpp @@ -0,0 +1,18 @@ +struct Foo { + int bar; + int zoo; +}; + +struct Bar { + int foo; + int zoo; +}; + +struct Foo; +struct Zoo; + +void func() { + Foo foo; + foo.bar = 5; + Zoo *zoo = 0; +} diff --git a/cpp/ycm/ycm_core.cpp b/cpp/ycm/ycm_core.cpp index f0a9b2a8..50504bc2 100644 --- a/cpp/ycm/ycm_core.cpp +++ b/cpp/ycm/ycm_core.cpp @@ -24,6 +24,7 @@ # include "ClangUtils.h" # include "CompletionData.h" # include "Diagnostic.h" +# include "Location.h" # include "UnsavedFile.h" # include "CompilationDatabase.h" #endif // USE_CLANG_COMPLETER @@ -45,7 +46,7 @@ int YcmCoreVersion() { // We increment this every time when we want to force users to recompile // ycm_core. - return 2; + return 3; } @@ -115,6 +116,8 @@ BOOST_PYTHON_MODULE(ycm_core) class_< ClangCompleter, boost::noncopyable >( "ClangCompleter" ) .def( "EnableThreading", &ClangCompleter::EnableThreading ) .def( "DiagnosticsForFile", &ClangCompleter::DiagnosticsForFile ) + .def( "GetDeclarationLocation", &ClangCompleter::GetDeclarationLocation ) + .def( "GetDefinitionLocation", &ClangCompleter::GetDefinitionLocation ) .def( "DeleteCachesForFileAsync", &ClangCompleter::DeleteCachesForFileAsync ) .def( "UpdatingTranslationUnit", &ClangCompleter::UpdatingTranslationUnit ) @@ -144,6 +147,12 @@ BOOST_PYTHON_MODULE(ycm_core) .def_readonly( "text_", &Diagnostic::text_ ) .def_readonly( "long_formatted_text_", &Diagnostic::long_formatted_text_ ); + class_< Location >( "Location" ) + .def_readonly( "line_number_", &Location::line_number_ ) + .def_readonly( "column_number_", &Location::column_number_ ) + .def_readonly( "filename_", &Location::filename_ ) + .def( "IsValid", &Location::IsValid ); + class_< std::vector< Diagnostic > >( "DiagnosticVec" ) .def( vector_indexing_suite< std::vector< Diagnostic > >() ); diff --git a/python/completers/cpp/clang_completer.py b/python/completers/cpp/clang_completer.py index 3b22716e..78a5589c 100644 --- a/python/completers/cpp/clang_completer.py +++ b/python/completers/cpp/clang_completer.py @@ -27,6 +27,12 @@ from flags import Flags CLANG_FILETYPES = set( [ 'c', 'cpp', 'objc', 'objcpp' ] ) MAX_DIAGNOSTICS_TO_DISPLAY = int( vimsupport.GetVariableValue( "g:ycm_max_diagnostics_to_display" ) ) +USER_COMMANDS_HELP_MESSAGE = """ +Supported commands are: + GoToDefinition + GoToDeclaration + GoToDefinitionElseDeclaration +See the docs for information on what they do.""" class ClangCompleter( Completer ): @@ -124,6 +130,78 @@ class ClangCompleter( Completer ): return results + def OnUserCommand( self, arguments ): + if not arguments: + vimsupport.EchoText( USER_COMMANDS_HELP_MESSAGE ) + return + + command = arguments[ 0 ] + if command == 'GoToDefinition': + self._GoToDefinition() + elif command == 'GoToDeclaration': + self._GoToDeclaration() + elif command == 'GoToDefinitionElseDeclaration': + self._GoToDefinitionElseDeclaration() + + + def _LocationForGoTo( self, goto_function ): + filename = vim.current.buffer.name + if not filename: + return None + + flags = self.flags.FlagsForFile( filename ) + if not flags: + vimsupport.PostVimMessage( 'Still no compile flags, can\'t compile.' ) + return None + + files = self.GetUnsavedFilesVector() + line, column = vimsupport.CurrentLineAndColumn() + # Making the line & column 1-based instead of 0-based + line += 1 + column += 1 + return getattr( self.completer, goto_function )( + filename, + line, + column, + files, + flags ) + + + def _GoToDefinition( self ): + location = self._LocationForGoTo( 'GetDefinitionLocation' ) + if not location.IsValid(): + vimsupport.PostVimMessage( 'Can\'t jump to definition.' ) + return + + vimsupport.JumpToLocation( location.filename_, + location.line_number_, + location.column_number_ ) + + + def _GoToDeclaration( self ): + location = self._LocationForGoTo( 'GetDeclarationLocation' ) + if not location.IsValid(): + vimsupport.PostVimMessage( 'Can\'t jump to declaration.' ) + return + + vimsupport.JumpToLocation( location.filename_, + location.line_number_, + location.column_number_ ) + + + def _GoToDefinitionElseDeclaration( self ): + location = self._LocationForGoTo( 'GetDefinitionLocation' ) + if not location.IsValid(): + location = self._LocationForGoTo( 'GetDeclarationLocation' ) + if not location.IsValid(): + vimsupport.PostVimMessage( 'Can\'t jump to definition or declaration.' ) + return + + vimsupport.JumpToLocation( location.filename_, + location.line_number_, + location.column_number_ ) + + def OnFileReadyToParse( self ): if vimsupport.NumLinesInBuffer( vim.current.buffer ) < 5: self.parse_future = None diff --git a/python/vimsupport.py b/python/vimsupport.py index 5a720c7b..a76906c7 100644 --- a/python/vimsupport.py +++ b/python/vimsupport.py @@ -49,6 +49,25 @@ def GetUnsavedBuffers(): return ( x for x in vim.buffers if BufferModified( x.number ) ) +# Both |line| and |column| need to be 1-based +def JumpToLocation( filename, line, column ): + # Add an entry to the jumplist + vim.command( "normal m'" ) + + if filename != vim.current.buffer.name: + # We prefix the command with 'keepjumps' so that opening the file is not + # recorded in the jumplist. So when we open the file and move the cursor to + # a location in it, the user can use CTRL-O to jump back to the original + # location, not to the start of the newly opened file. + # Sadly this fails on random occasions and the undesired jump remains in the + # jumplist. + vim.command( 'keepjumps edit {0}'.format( filename ) ) + vim.current.window.cursor = ( line, column - 1 ) + + # Center the screen on the jumped-to location + vim.command( 'normal! zz' ) + + def NumLinesInBuffer( buffer ): # This is actually less than obvious, that's why it's wrapped in a function return len( buffer ) diff --git a/python/ycm.py b/python/ycm.py index 501429a3..c8d346aa 100644 --- a/python/ycm.py +++ b/python/ycm.py @@ -257,7 +257,7 @@ def CurrentIdentifierFinished(): return line[ : current_column ].isspace() -COMPATIBLE_WITH_CORE_VERSION = 2 +COMPATIBLE_WITH_CORE_VERSION = 3 def CompatibleWithYcmCore(): try: