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' diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 08315f4084..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,14 +245,10 @@ 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_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 ) diff --git a/ycmd/completers/cs/cs_completer.py b/ycmd/completers/cs/cs_completer.py index 484d548166..f8b6cd5cc3 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' ) ), } @@ -631,36 +634,48 @@ 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: 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: + goto_locations.append( + responses.BuildGoToResponseFromLocation( + _BuildLocation( request_data, + ref[ 'FileName' ], + ref[ 'Line' ], + ref[ 'Column' ] ), + ref[ 'Text' ] ) ) + if len( goto_locations ) > 1: + return goto_locations + return goto_locations[ 0 ] + 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/completers/language_server/language_server_completer.py b/ycmd/completers/language_server/language_server_completer.py index 8667e41b7c..2f46e43de6 100644 --- a/ycmd/completers/language_server/language_server_completer.py +++ b/ycmd/completers/language_server/language_server_completer.py @@ -2536,9 +2536,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 @@ -2553,15 +2551,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 ) 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/cs/subcommands_test.py b/ycmd/tests/cs/subcommands_test.py index 3205714f52..0f40c91d4a 100644 --- a/ycmd/tests/cs/subcommands_test.py +++ b/ycmd/tests/cs/subcommands_test.py @@ -909,3 +909,68 @@ def test_Subcommands_OrganizeImports( self, app ): LocationMatcher( filepath, 3, 1 ), ) ) } ) ) } ) ) + + + @SharedYcmd + def test_Subcommands_GoToDocumentOutline( self, app ): + + 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 ), + 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 ) ) ) + ]: + with self.subTest( filepath = filepath, expected = expected ): + 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, expected ) 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/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 7bba3cb813..6e2787b5ba 100644 --- a/ycmd/tests/cs/testdata/testy/testy.csproj +++ b/ycmd/tests/cs/testdata/testy/testy.csproj @@ -48,6 +48,8 @@ + + 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 [ 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, {