From cba73cbd284e4f2006d603f39cf9a5cd5c747e06 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sat, 14 Oct 2023 09:45:27 +0200 Subject: [PATCH 1/9] Display tsserver tags from docstrings in GetDoc and extra_menu_info --- .../typescript/typescript_completer.py | 20 ++++++++++++ .../javascriptreact/get_completions_test.py | 6 +++- ycmd/tests/typescript/get_completions_test.py | 31 +++++++++++++++++++ ycmd/tests/typescript/subcommands_test.py | 23 ++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/ycmd/completers/typescript/typescript_completer.py b/ycmd/completers/typescript/typescript_completer.py index 99439421da..0d4cd699ca 100644 --- a/ycmd/completers/typescript/typescript_completer.py +++ b/ycmd/completers/typescript/typescript_completer.py @@ -911,7 +911,12 @@ def _GetDoc( self, request_data ): 'offset': request_data[ 'column_codepoint' ] } ) + extra_info = _AggregateTagsFromDocstring( info ) + message = f'{ info[ "displayString" ] }\n\n{ info[ "documentation" ] }' + if extra_info: + message += f'\n\n{ extra_info }' + return responses.BuildDetailedInfoResponse( message ) @@ -1117,6 +1122,16 @@ def _LogLevel(): return 'verbose' if LOGGER.isEnabledFor( logging.DEBUG ) else 'normal' +def _AggregateTagsFromDocstring( info ): + extra_info = [] + for tag in info.get( 'tags', [] ): + tag_name = tag[ 'name' ] + tag_text = tag.get( 'text' ) + formated_tag = tag_name + ( f': { tag_text }' if tag_text else '' ) + extra_info.append( formated_tag ) + return '\n'.join( extra_info ) + + def _BuildCompletionExtraMenuAndDetailedInfo( request_data, entry ): signature = _DisplayPartsToString( entry[ 'displayParts' ] ) if entry[ 'name' ] == signature: @@ -1130,6 +1145,11 @@ def _BuildCompletionExtraMenuAndDetailedInfo( request_data, entry ): docs = entry.get( 'documentation', [] ) detailed_info += [ doc[ 'text' ].strip() for doc in docs if doc ] + + extra_info = _AggregateTagsFromDocstring( entry ) + if extra_info: + detailed_info.append( extra_info ) + detailed_info = '\n\n'.join( detailed_info ) return extra_menu_info, detailed_info diff --git a/ycmd/tests/javascriptreact/get_completions_test.py b/ycmd/tests/javascriptreact/get_completions_test.py index 958413df05..40b9993489 100644 --- a/ycmd/tests/javascriptreact/get_completions_test.py +++ b/ycmd/tests/javascriptreact/get_completions_test.py @@ -75,7 +75,11 @@ def test_GetCompletions_JavaScriptReact_DefaultTriggers( self, app ): 'detailed_info': '(property) Document.alinkColor: string\n' '\n' 'Sets or gets the color of all active links ' - 'in the document.', + 'in the document.\n' + '\n' + 'deprecated: [MDN Reference]' + '(https://developer.mozilla.org/docs/Web/' + 'API/Document/alinkColor)', 'kind': 'property', } ) ) } ) diff --git a/ycmd/tests/typescript/get_completions_test.py b/ycmd/tests/typescript/get_completions_test.py index 8a8f961ad7..2956f19800 100644 --- a/ycmd/tests/typescript/get_completions_test.py +++ b/ycmd/tests/typescript/get_completions_test.py @@ -357,3 +357,34 @@ def test_GetCompletions_TypeScriptReact_DefaultTriggers( self, app ): } ) } } ) + + + @SharedYcmd + def test_GetCompletions_WithTags( self, app ): + filepath = PathToTestFile( 'signatures.ts' ) + RunTest( app, { + 'description': 'No need to force after a semantic trigger', + 'request': { + 'line_num': 101, + 'column_num': 24, + 'filepath': filepath, + 'filetype': 'typescript' + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'completions': has_item( has_entries( { + 'insertion_text': 'single_argument_with_doc', + 'extra_data': {}, + 'extra_menu_info': + 'function single_argument_with_doc(a: string): string', + 'detailed_info': + 'function single_argument_with_doc(a: string): string\n\n' + 'A function with a single argument\n\n' + 'param: a - The argument\n' + 'returns: - The hashed input', + 'kind': 'function' + } ) ) + } ) + } + } ) diff --git a/ycmd/tests/typescript/subcommands_test.py b/ycmd/tests/typescript/subcommands_test.py index e64acb2546..33464090df 100644 --- a/ycmd/tests/typescript/subcommands_test.py +++ b/ycmd/tests/typescript/subcommands_test.py @@ -610,6 +610,29 @@ def test_Subcommands_GetDoc_Class_Unicode( self, app ): } ) + @SharedYcmd + def test_Subcommands_GetDoc_FreeFunction_WithTags( self, app ): + RunTest( app, { + 'description': 'GetDoc shows documentation of param and returns tags', + 'request': { + 'command': 'GetDoc', + 'line_num': 101, + 'column_num': 12, + 'filepath': PathToTestFile( 'signatures.ts' ), + }, + 'expect': { + 'response': requests.codes.ok, + 'data': has_entries( { + 'detailed_info': + 'function single_argument_with_doc(a: string): string\n\n' + 'A function with a single argument\n\n' + 'param: a - The argument\n' + 'returns: - The hashed input' + } ) + } + } ) + + @SharedYcmd def test_Subcommands_GoToReferences( self, app ): RunTest( app, { From 1c87c7424df27a8e4cc0b65d6353139cb700c5b8 Mon Sep 17 00:00:00 2001 From: Troy Date: Mon, 1 Jan 2024 14:09:21 +1100 Subject: [PATCH 2/9] CsCompleter: Implement GoToDocumentOutline. --- ycmd/completers/cs/cs_completer.py | 25 +++++++ ycmd/tests/cs/subcommands_test.py | 85 +++++++++++++++++++++++ ycmd/tests/cs/testdata/testy/Empty.cs | 6 ++ ycmd/tests/cs/testdata/testy/testy.csproj | 1 + 4 files changed, 117 insertions(+) create mode 100644 ycmd/tests/cs/testdata/testy/Empty.cs diff --git a/ycmd/completers/cs/cs_completer.py b/ycmd/completers/cs/cs_completer.py index 7648feec99..f1061782ed 100644 --- a/ycmd/completers/cs/cs_completer.py +++ b/ycmd/completers/cs/cs_completer.py @@ -241,6 +241,9 @@ def GetSubcommandsMap( self ): self._SolutionSubcommand( request_data, method = '_RefactorRename', args = args ) ), + 'GoToDocumentOutline' : ( lambda self, request_data, args: + self._SolutionSubcommand( request_data, + method = '_GoToDocumentOutline' ) ), } @@ -660,6 +663,28 @@ def _GoToSymbol( self, request_data, args ): else: raise RuntimeError( 'No symbols found' ) + + def _GoToDocumentOutline( self, request_data ): + request = self._DefaultParameters( request_data ) + response = self._GetResponse( '/currentfilemembersasflat', request ) + if response is not None and len( response ) > 0: + goto_locations = [] + for ref in response: + ref_file = ref[ 'FileName' ] + ref_line = ref[ 'Line' ] + lines = GetFileLines( request_data, ref_file ) + line = lines[ min( len( lines ), ref_line - 1 ) ] + goto_locations.append( + responses.BuildGoToResponseFromLocation( + _BuildLocation( request_data, + ref_file, + ref_line, + ref[ 'Column' ] ), + line ) ) + return goto_locations + else: + raise RuntimeError( 'No symbols found' ) + def _GoToReferences( self, request_data ): """ Jump to references of identifier under cursor """ # _GetResponse can throw. Original code by @mispencer diff --git a/ycmd/tests/cs/subcommands_test.py b/ycmd/tests/cs/subcommands_test.py index 3205714f52..5f77f45b0b 100644 --- a/ycmd/tests/cs/subcommands_test.py +++ b/ycmd/tests/cs/subcommands_test.py @@ -909,3 +909,88 @@ def test_Subcommands_OrganizeImports( self, app ): LocationMatcher( filepath, 3, 1 ), ) ) } ) ) } ) ) + + + @SharedYcmd + def test_Subcommands_GoToDocumentOutline( self, app ): + + # we reuse the ImportTest.cs file as it contains a good selection of + # symbols/ symbol types. + filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) + with WrapOmniSharpServer( app, filepath ): + + # the command name and file are the only relevant arguments for this + # subcommand, our current cursor position in the file doesn't matter. + request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ], + line_num = 11, + column_num = 2, + contents = ReadFile( filepath ), + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', request ).json + + print( 'completer response = ', response ) + + assert_that( response, + has_items( + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 6, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 26, 12 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 30, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 35, 12 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 39, 12 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 43, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 48, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 8, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 13, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 17, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 21, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 27, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 31, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 36, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 40, 8 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 15 ), + LocationMatcher( + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 15 ), + ) + ) + + @SharedYcmd + def test_Subcommands_GoToDocumentOutline_Empty( self, app ): + + filepath = PathToTestFile( 'testy', 'Empty.cs' ) + with WrapOmniSharpServer( app, filepath ): + + # the command name and file are the only relevant arguments for this + # subcommand. our current cursor position in the file doesn't matter. + request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ], + line_num = 0, + column_num = 0, + contents = ReadFile( filepath ), + filetype = 'cs', + filepath = filepath ) + + response = app.post_json( '/run_completer_command', + request, + expect_errors = True ).json + + print( 'completer response = ', response ) + + assert_that( response, ErrorMatcher( RuntimeError, + 'No symbols found' ) ) diff --git a/ycmd/tests/cs/testdata/testy/Empty.cs b/ycmd/tests/cs/testdata/testy/Empty.cs new file mode 100644 index 0000000000..fbdadd554e --- /dev/null +++ b/ycmd/tests/cs/testdata/testy/Empty.cs @@ -0,0 +1,6 @@ +using System; +using System.Data; + +namespace testy +{ +} diff --git a/ycmd/tests/cs/testdata/testy/testy.csproj b/ycmd/tests/cs/testdata/testy/testy.csproj index 7bba3cb813..cdbfbe9f5f 100644 --- a/ycmd/tests/cs/testdata/testy/testy.csproj +++ b/ycmd/tests/cs/testdata/testy/testy.csproj @@ -48,6 +48,7 @@ + From cb16efaa34e3b71f4182aa053bd2601f021ea1a3 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Thu, 4 Jan 2024 20:01:22 +0100 Subject: [PATCH 3/9] Make C# completer consistent with other completers regarding GoToDocumentOutline All other semantic completers return a list only when more than one item is in the list. Otherwise return that one item. --- ycmd/completers/cs/cs_completer.py | 4 +- ycmd/tests/cs/subcommands_test.py | 68 +++++++------------- ycmd/tests/cs/testdata/testy/SingleEntity.cs | 9 +++ ycmd/tests/cs/testdata/testy/testy.csproj | 1 + 4 files changed, 37 insertions(+), 45 deletions(-) create mode 100644 ycmd/tests/cs/testdata/testy/SingleEntity.cs diff --git a/ycmd/completers/cs/cs_completer.py b/ycmd/completers/cs/cs_completer.py index f1061782ed..b023ae45b4 100644 --- a/ycmd/completers/cs/cs_completer.py +++ b/ycmd/completers/cs/cs_completer.py @@ -681,7 +681,9 @@ def _GoToDocumentOutline( self, request_data ): ref_line, ref[ 'Column' ] ), line ) ) - return goto_locations + if len( goto_locations ) > 1: + return goto_locations + return goto_locations[ 0 ] else: raise RuntimeError( 'No symbols found' ) diff --git a/ycmd/tests/cs/subcommands_test.py b/ycmd/tests/cs/subcommands_test.py index 5f77f45b0b..0f40c91d4a 100644 --- a/ycmd/tests/cs/subcommands_test.py +++ b/ycmd/tests/cs/subcommands_test.py @@ -914,25 +914,13 @@ def test_Subcommands_OrganizeImports( self, app ): @SharedYcmd def test_Subcommands_GoToDocumentOutline( self, app ): - # we reuse the ImportTest.cs file as it contains a good selection of - # symbols/ symbol types. - filepath = PathToTestFile( 'testy', 'GotoTestCase.cs' ) - with WrapOmniSharpServer( app, filepath ): - - # the command name and file are the only relevant arguments for this - # subcommand, our current cursor position in the file doesn't matter. - request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ], - line_num = 11, - column_num = 2, - contents = ReadFile( filepath ), - filetype = 'cs', - filepath = filepath ) - - response = app.post_json( '/run_completer_command', request ).json - - print( 'completer response = ', response ) - - assert_that( response, + for filepath, expected in [ + ( PathToTestFile( 'testy', 'Empty.cs' ), + ErrorMatcher( RuntimeError, 'No symbols found' ) ), + ( PathToTestFile( 'testy', 'SingleEntity.cs' ), + LocationMatcher( + PathToTestFile( 'testy', 'SingleEntity.cs' ), 6, 8 ) ), + ( PathToTestFile( 'testy', 'GotoTestCase.cs' ), has_items( LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 6, 8 ), @@ -967,30 +955,22 @@ def test_Subcommands_GoToDocumentOutline( self, app ): LocationMatcher( PathToTestFile( 'testy', 'GotoTestCase.cs' ), 44, 15 ), LocationMatcher( - PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 15 ), - ) - ) - - @SharedYcmd - def test_Subcommands_GoToDocumentOutline_Empty( self, app ): - - filepath = PathToTestFile( 'testy', 'Empty.cs' ) - with WrapOmniSharpServer( app, filepath ): - - # the command name and file are the only relevant arguments for this - # subcommand. our current cursor position in the file doesn't matter. - request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ], - line_num = 0, - column_num = 0, - contents = ReadFile( filepath ), - filetype = 'cs', - filepath = filepath ) - - response = app.post_json( '/run_completer_command', - request, - expect_errors = True ).json + PathToTestFile( 'testy', 'GotoTestCase.cs' ), 49, 15 ) ) ) + ]: + with self.subTest( filepath = filepath, expected = expected ): + with WrapOmniSharpServer( app, filepath ): - print( 'completer response = ', response ) + # the command name and file are the only relevant arguments for this + # subcommand. our current cursor position in the file doesn't matter. + request = BuildRequest( command_arguments = [ 'GoToDocumentOutline' ], + line_num = 0, + column_num = 0, + contents = ReadFile( filepath ), + filetype = 'cs', + filepath = filepath ) - assert_that( response, ErrorMatcher( RuntimeError, - 'No symbols found' ) ) + response = app.post_json( '/run_completer_command', + request, + expect_errors = True ).json + print( 'completer response = ', response ) + assert_that( response, expected ) diff --git a/ycmd/tests/cs/testdata/testy/SingleEntity.cs b/ycmd/tests/cs/testdata/testy/SingleEntity.cs new file mode 100644 index 0000000000..594f1a8fea --- /dev/null +++ b/ycmd/tests/cs/testdata/testy/SingleEntity.cs @@ -0,0 +1,9 @@ +using System; +using System.Data; + +namespace testy +{ + class GotoTestCase + { + } +} diff --git a/ycmd/tests/cs/testdata/testy/testy.csproj b/ycmd/tests/cs/testdata/testy/testy.csproj index cdbfbe9f5f..6e2787b5ba 100644 --- a/ycmd/tests/cs/testdata/testy/testy.csproj +++ b/ycmd/tests/cs/testdata/testy/testy.csproj @@ -49,6 +49,7 @@ + From 0f120b3fb376c805604aa9bcc7a3772b47027108 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 9 Jan 2024 02:05:49 +0100 Subject: [PATCH 4/9] Simplify CMake FetchContent usage Instead of reinventing FetchContent_MakeAvailable (badly), we can just use the CMake provided utility. It is introduced in cmake 3.14, which also matches our minim required cmake version. --- cpp/CMakeLists.txt | 6 +----- cpp/ycm/benchmarks/CMakeLists.txt | 6 +----- cpp/ycm/tests/CMakeLists.txt | 6 +----- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 08315f4084..38ad7c311e 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -247,11 +247,7 @@ else() GIT_TAG 3b4a16abad2c2ddc494371cc39a2946e36d35d11 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/absl ) - FetchContent_GetProperties( absl ) - if ( NOT absl_POPULATED ) - FetchContent_Populate( absl ) - endif() - add_subdirectory( absl ) + FetchContent_MakeAvailable( absl ) endif() add_subdirectory( ycm ) diff --git a/cpp/ycm/benchmarks/CMakeLists.txt b/cpp/ycm/benchmarks/CMakeLists.txt index 6009a0a872..75adde7b54 100644 --- a/cpp/ycm/benchmarks/CMakeLists.txt +++ b/cpp/ycm/benchmarks/CMakeLists.txt @@ -33,11 +33,7 @@ else() GIT_TAG 3b19d7222db7babfdc9b3949408b2294c3bbb540 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/benchmark ) - FetchContent_GetProperties( benchmark ) - if ( NOT benchmark_POPULATED ) - FetchContent_Populate( benchmark ) - endif() - add_subdirectory( benchmark ) + FetchContent_MakeAvailable( benchmark ) endif() file( GLOB SOURCES *.h *.cpp ) diff --git a/cpp/ycm/tests/CMakeLists.txt b/cpp/ycm/tests/CMakeLists.txt index 0c52e668ed..0415d717be 100644 --- a/cpp/ycm/tests/CMakeLists.txt +++ b/cpp/ycm/tests/CMakeLists.txt @@ -45,11 +45,7 @@ else() GIT_TAG f5e592d8ee5ffb1d9af5be7f715ce3576b8bf9c4 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/gmock ) - FetchContent_GetProperties( gmock ) - if ( NOT benchmark_POPULATED ) - FetchContent_Populate( gmock ) - endif() - add_subdirectory( gmock ) + FetchContent_MakeAvailable( gmock ) endif() file( GLOB SOURCES *.h *.cpp ) From 4d1accc6ff0d2128212a4164595e05d71ca682b8 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Tue, 9 Jan 2024 02:07:53 +0100 Subject: [PATCH 5/9] Update abseil The update to the latest LTS fixes compilation on some BSD systems. The new option before making abseil available has to do with dependencies propagating their needs for C++ standard up to the projects that depend on them. If a library needs C++14, then the application depending on that library needs at least C++14, but can opt for newer standards too. --- cpp/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 38ad7c311e..526491c96f 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -237,6 +237,7 @@ find_package( Python3 3.6 REQUIRED COMPONENTS Interpreter Development ) ############################################################################# set( CMAKE_POSITION_INDEPENDENT_CODE ON ) +set( ABSL_PROPAGATE_CXX_STD ON ) if ( USE_SYSTEM_ABSEIL ) find_package( absl REQUIRED ) else() @@ -244,7 +245,7 @@ else() FetchContent_Declare( absl GIT_REPOSITORY https://github.com/abseil/abseil-cpp - GIT_TAG 3b4a16abad2c2ddc494371cc39a2946e36d35d11 + GIT_TAG fb3621f4f897824c0dbe0615fa94543df6192f30 SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/absl ) FetchContent_MakeAvailable( absl ) From ed2afdb15fe09d42988a5787eb13b32c0a814267 Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Wed, 10 Jan 2024 00:31:14 +0100 Subject: [PATCH 6/9] Do not use a full line as description for symbol search This does lose a bit of context, but not much. Methods still have their signatures visible and variables are... well... variables. The idea is, if a client wants to later do filtering and sorting of symbols, we should not be doing that on the whole line, which might be left padded with tabs and have other punctuation. We already had a report that this does not work well. It almost works if a client `strip()`s the description before using it at all. LSP completer instead uses `extra_data` to supply identifier name and kind, but omnisharp-roslyn does not provide us with a symbol kind and thus we can only put `ref[ 'Text' ]` in the description. --- ycmd/completers/cs/cs_completer.py | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/ycmd/completers/cs/cs_completer.py b/ycmd/completers/cs/cs_completer.py index b023ae45b4..f7d0174e63 100644 --- a/ycmd/completers/cs/cs_completer.py +++ b/ycmd/completers/cs/cs_completer.py @@ -633,31 +633,23 @@ def _GoToSymbol( self, request_data, args ): if quickfixes: if len( quickfixes ) == 1: ref = quickfixes[ 0 ] - ref_file = ref[ 'FileName' ] - ref_line = ref[ 'Line' ] - lines = GetFileLines( request_data, ref_file ) - line = lines[ min( len( lines ), ref_line - 1 ) ] return responses.BuildGoToResponseFromLocation( _BuildLocation( request_data, - ref_file, - ref_line, + ref[ 'FileName' ], + ref[ 'Line' ], ref[ 'Column' ] ), - line ) + ref[ 'Text' ] ) else: goto_locations = [] for ref in quickfixes: - ref_file = ref[ 'FileName' ] - ref_line = ref[ 'Line' ] - lines = GetFileLines( request_data, ref_file ) - line = lines[ min( len( lines ), ref_line - 1 ) ] goto_locations.append( responses.BuildGoToResponseFromLocation( _BuildLocation( request_data, - ref_file, - ref_line, + ref[ 'FileName' ], + ref[ 'Line' ], ref[ 'Column' ] ), - line ) ) + ref[ 'Text' ] ) ) return goto_locations else: @@ -670,17 +662,13 @@ def _GoToDocumentOutline( self, request_data ): if response is not None and len( response ) > 0: goto_locations = [] for ref in response: - ref_file = ref[ 'FileName' ] - ref_line = ref[ 'Line' ] - lines = GetFileLines( request_data, ref_file ) - line = lines[ min( len( lines ), ref_line - 1 ) ] goto_locations.append( responses.BuildGoToResponseFromLocation( _BuildLocation( request_data, - ref_file, - ref_line, + ref[ 'FileName' ], + ref[ 'Line' ], ref[ 'Column' ] ), - line ) ) + ref[ 'Text' ] ) ) if len( goto_locations ) > 1: return goto_locations return goto_locations[ 0 ] From e5121243fe432299ea7eebc14a0016cdb38faf71 Mon Sep 17 00:00:00 2001 From: Debucquoy Anthony Date: Sat, 13 Jan 2024 17:55:03 +0100 Subject: [PATCH 7/9] Bump JDT.ls version to 1.31.0 Adding support to java 21 --- build.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.py b/build.py index e83875cd0a..1e0f9a20d1 100755 --- a/build.py +++ b/build.py @@ -89,10 +89,10 @@ def Exit( self ): )$ """ -JDTLS_MILESTONE = '1.26.0' -JDTLS_BUILD_STAMP = '202307271613' +JDTLS_MILESTONE = '1.31.0' +JDTLS_BUILD_STAMP = '202401111522' JDTLS_SHA256 = ( - 'ba5fe5ee3b2a8395287e24aef20ce6e17834cf8e877117e6caacac6a688a6c53' + '6c25f62d0b74d1dd92ab19afbafbe5041eb06c2b853eab57f7f42fe6feb01f7c' ) DEFAULT_RUST_TOOLCHAIN = 'nightly-2023-08-18' From 120fbe19d9da27a0ee75f3d58fc71f33d06567d6 Mon Sep 17 00:00:00 2001 From: Debucquoy Anthony Date: Sun, 14 Jan 2024 00:04:26 +0100 Subject: [PATCH 8/9] Fixing test Fixed-By: @bstaletic Related: #1727 --- ycmd/tests/java/subcommands_test.py | 95 +++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/ycmd/tests/java/subcommands_test.py b/ycmd/tests/java/subcommands_test.py index 02af0ae03f..d31657b265 100644 --- a/ycmd/tests/java/subcommands_test.py +++ b/ycmd/tests/java/subcommands_test.py @@ -537,6 +537,37 @@ def test_Subcommands_GetType_LiteralValue( self, app ): ErrorMatcher( RuntimeError, 'Unknown type' ) ) + @SharedYcmd + def test_Subcommands_GoToDeclaration_NoLocation( self, app ): + filepath = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestLauncher.java' ) + contents = ReadFile( filepath ) + + # Virtual call of getWidgetInfo - don't know the concrete implementation. + # Here GoToDefinition jumps to the interface method declaration and + # GoToDeclaration does nothing... + event_data = BuildRequest( filepath = filepath, + filetype = 'java', + line_num = 34, + column_num = 59, + contents = contents, + command_arguments = [ 'GoToDeclaration' ], + completer_target = 'filetype_default' ) + + response = app.post_json( '/run_completer_command', + event_data, + expect_errors = True ) + + assert_that( response.status_code, + equal_to( requests.codes.internal_server_error ) ) + + assert_that( response.json, + ErrorMatcher( RuntimeError, 'Cannot jump to location' ) ) + + @WithRetry() @SharedYcmd def test_Subcommands_GoTo_NoLocation( self, app ): @@ -1020,9 +1051,11 @@ def test_Subcommands_FixIt_SingleDiag_MultipleOption_Insertion( self, app ): 'text': "Create constant 'Wibble'", 'kind': 'quickfix', 'chunks': contains_exactly( - ChunkMatcher( '\n\nprivate static final String Wibble = null;', + ChunkMatcher( '\n\nprivate static final String Wibble = null;' + '\n\n private void Wimble( Wibble w ) {' + '\n if ( w == Wibble', LocationMatcher( filepath, 16, 4 ), - LocationMatcher( filepath, 16, 4 ) ), + LocationMatcher( filepath, 19, 21 ) ), ), } ), has_entries( { @@ -1056,27 +1089,29 @@ def test_Subcommands_FixIt_SingleDiag_MultipleOption_Insertion( self, app ): 'text': "Create local variable 'Wibble'", 'kind': 'quickfix', 'chunks': contains_exactly( - ChunkMatcher( 'Object Wibble;\n ', + ChunkMatcher( 'Object Wibble;\n if ( w == Wibble', LocationMatcher( filepath, 19, 5 ), - LocationMatcher( filepath, 19, 5 ) ), + LocationMatcher( filepath, 19, 21 ) ), ), } ), has_entries( { 'text': "Create field 'Wibble'", 'kind': 'quickfix', 'chunks': contains_exactly( - ChunkMatcher( '\n\nprivate Object Wibble;', + ChunkMatcher( '\n\nprivate Object Wibble;' + '\n\n private void Wimble( Wibble w ) {' + '\n if ( w == Wibble', LocationMatcher( filepath, 16, 4 ), - LocationMatcher( filepath, 16, 4 ) ), + LocationMatcher( filepath, 19, 21 ) ), ), } ), has_entries( { 'text': "Create parameter 'Wibble'", 'kind': 'quickfix', 'chunks': contains_exactly( - ChunkMatcher( ', Object Wibble', + ChunkMatcher( ', Object Wibble ) {\n if ( w == Wibble', LocationMatcher( filepath, 18, 32 ), - LocationMatcher( filepath, 18, 32 ) ), + LocationMatcher( filepath, 19, 21 ) ), ), } ), has_entries( { @@ -1485,6 +1520,10 @@ def test_Subcommands_FixIt_Range( self, app ): has_entries( { 'text': "Sort Members for 'TestLauncher.java'" } ), + has_entries( { + 'text': 'Surround with try/catch', + 'chunks': instance_of( list ) + } ), ) } ) } @@ -1545,8 +1584,17 @@ def test_Subcommands_FixIt_Unicode( self, app ): 'text': "Create method 'doUnicødeTes(String)'", 'kind': 'quickfix', 'chunks': contains_exactly( - ChunkMatcher( 'private void doUnicødeTes(String test2) {\n}\n\n\n', - LocationMatcher( TEST_JAVA, 20, 3 ), + ChunkMatcher( + 'doUnicødeTes( test );\n\n' + ' TéstClass tésting_with_unicøde = new TéstClass();\n' + ' return tésting_with_unicøde.a_test;\n' + ' }\n\n\n' + ' private void doUnicødeTes(String test2) {\n' + ' // TODO Auto-generated method stub\n' + ' throw new UnsupportedOperationException(' + '"Unimplemented method \'doUnicødeTes\'");\n' + '}\n\n\n', + LocationMatcher( TEST_JAVA, 13, 10 ), LocationMatcher( TEST_JAVA, 20, 3 ) ), ), } ), @@ -2012,7 +2060,7 @@ def test_Subcommands_GoTo( self, app ): 'filepath': TEST_JAVA }, 'description': 'GoTo works for unicode identifiers' } ], - [ 'GoTo', 'GoToDefinition', 'GoToDeclaration' ] ): + [ 'GoTo', 'GoToDefinition' ] ): with self.subTest( command = command, test = test ): RunGoToTest( app, test[ 'description' ], @@ -2023,6 +2071,31 @@ def test_Subcommands_GoTo( self, app ): has_entries( test[ 'response' ] ) ) + @SharedYcmd + def test_Subcommands_GoToDeclaration( self, app ): + source = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'TestWidgetImpl.java' ) + destination = PathToTestFile( 'simple_eclipse_project', + 'src', + 'com', + 'test', + 'AbstractTestWidget.java' ) + # Seems to be the only place GoToDeclaration actually works. + RunGoToTest( app, + 'GoToDeclaration works on an override definition.', + source, + 23, + 17, + 'GoToDeclaration', + has_entries( { + 'line_num': 17, + 'column_num': 17, + 'filepath': destination } ) ) + + @SharedYcmd def test_Subcommands_GoToType( self, app ): for test in [ From 36fc2fcc312ff829479184f636bf778d1a11545a Mon Sep 17 00:00:00 2001 From: Boris Staletic Date: Sun, 14 Jan 2024 01:11:59 +0100 Subject: [PATCH 9/9] Account for one of the multiple GoTo handlers throwing an exception The GoTo subcommand might have multiple LSP handlers associated with it. Mostly, it is `textDocument/definition` and `textDocument/declaration`. Normally, we cycle through handlers until one of two things happens: 1. The handler returns more than one location. 2. The handler returns a single location, but that doese not contain the cursor. This breaks if any of the handlers throws an exception. When that happens, we immediately propagate the error, instead of trying the next handler. However, we can not simply swallow exceptions, because there's a possibility for all handlers to throw. Or some throws and some return zero locations. Current idea: 1. Do not throw as soon as a handler reports zero locations. 2. If the current handler returns more than a single location, replace previous result with the current one. 3. If there's more than one location or the single location does not contain cursor, break out of the loop - we have valid results. Otherwise, move to the next handler. 4. After exiting the handler loop, if the results are falsey, *now* throw an exception. --- .../language_server_completer.py | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/ycmd/completers/language_server/language_server_completer.py b/ycmd/completers/language_server/language_server_completer.py index b5fd6ee182..aa7234079f 100644 --- a/ycmd/completers/language_server/language_server_completer.py +++ b/ycmd/completers/language_server/language_server_completer.py @@ -2527,9 +2527,7 @@ def _GoToRequest( self, request_data, handler ): except ResponseFailedException: result = None - if not result: - raise RuntimeError( 'Cannot jump to location' ) - if not isinstance( result, list ): + if result and not isinstance( result, list ): return [ result ] return result @@ -2544,15 +2542,18 @@ def GoTo( self, request_data, handlers ): self._UpdateServerWithFileContents( request_data ) - if len( handlers ) == 1: - result = self._GoToRequest( request_data, handlers[ 0 ] ) - else: - for handler in handlers: - result = self._GoToRequest( request_data, handler ) - if len( result ) > 1 or not _CursorInsideLocation( request_data, - result[ 0 ] ): - break + result = [] + for handler in handlers: + new_result = self._GoToRequest( request_data, handler ) + if new_result: + result = new_result + if len( result ) > 1 or ( result and + not _CursorInsideLocation( request_data, + result[ 0 ] ) ): + break + if not result: + raise RuntimeError( 'Cannot jump to location' ) return _LocationListToGoTo( request_data, result )