From 1c87c7424df27a8e4cc0b65d6353139cb700c5b8 Mon Sep 17 00:00:00 2001 From: Troy Date: Mon, 1 Jan 2024 14:09:21 +1100 Subject: [PATCH] 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 @@ +