Skip to content

Commit e81c15e

Browse files
authored
Merge pull request #1733 from bstaletic/hierarchies
[READY] Implement LSP type/call hierarchies as actual hierarchies
2 parents 03b2c82 + 5b00c18 commit e81c15e

File tree

24 files changed

+1386
-89
lines changed

24 files changed

+1386
-89
lines changed

docs/bundle.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.html

Lines changed: 196 additions & 0 deletions
Large diffs are not rendered by default.

docs/main.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/openapi.yml

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,61 @@ definitions:
190190
191191
additionalProperties:
192192
$ref: "#/definitions/FileData"
193-
193+
Hierarchy:
194+
type: object
195+
required:
196+
- kind
197+
- locations
198+
- name
199+
description: |-
200+
An object returned, in a list, as a response to hierarchy requests.
201+
The object may contain additional properties, which are used to identify
202+
the hierarchy tree node, by the subservers.
203+
properties:
204+
kind:
205+
type: string
206+
enum:
207+
- File
208+
- Module
209+
- Namespace
210+
- Package
211+
- Class
212+
- Method
213+
- Property
214+
- Field
215+
- Constructor
216+
- Enum
217+
- Interface
218+
- Function
219+
- Variable
220+
- Constant
221+
- String
222+
- Number
223+
- Boolean
224+
- Array
225+
- Object
226+
- Key
227+
- Null
228+
- EnumMember
229+
- Struct
230+
- Event
231+
- Operator
232+
- TypeParameter
233+
locations:
234+
type: array
235+
items:
236+
$ref: "#/defintions/Location"
237+
name:
238+
type: string
239+
description: |-
240+
The name of the symbol represented by the hierarchy item.
241+
root_location:
242+
type: object
243+
description: |-
244+
In call hierarchies, it is useful to differentiate between the call
245+
site (which end up in the `locations` property) and the calling
246+
function. This property, if present, represents the latter.
247+
$ref: "#/definitions/Location"
194248
# Due to the way the API combines keys at the top level, we are not able to
195249
# compose this item per-request. So this definition must be copy-pasted for
196250
# some requests.
@@ -368,6 +422,22 @@ definitions:
368422
If present, this is a single *GoTo response* and this value
369423
contains the absolute path of the buffer containing the
370424
target location (identified in `line_num` and `column_num`).
425+
kind:
426+
type: string
427+
description: |-
428+
If present, this is a *Hierarchy response* and this value
429+
describes the kind of symbol a node in the call/type hierarchy
430+
tree is referring to.
431+
hierarchy details:
432+
$ref: "#/definitions/Hierarchy"
433+
locations:
434+
type: array
435+
description: |-
436+
If present, this is a *Hierarchy response* and this value
437+
contains a list of locations that a node in the call/type
438+
hierarchy tree is referring to.
439+
hierarchy details:
440+
$ref: "#/definitions/Hierarchy"
371441
line_num:
372442
$ref: "#/definitions/LineNumber"
373443
column_num:
@@ -886,6 +956,21 @@ paths:
886956
messages are typically multiple lines (such as the documentation and
887957
signature for a method) and are best displayed in a panel or preview
888958
area (or equivalent).
959+
- A *Hierarchy* response. This is identified where the type of the
960+
response is a list of objects containing `kind` and `locations`
961+
properties.
962+
Hierarchy requests work similarly to those in the LSP specification.
963+
If a completer supports call/type hierarchies, the initial request
964+
should be sent through `TypeHierarchy`/`CallHierarchy` subcommand.
965+
Resolving any item in the hierarchy tree is done by subsequent
966+
`ResolveCallHierarchyItem` and `ResolveTypeHierarchyItem` requests.
967+
When resolving a hierarchy item, the arguments following
968+
the subcommand name are the item being resolved and the direction in
969+
which the item should be resolved.
970+
For call hierarchies, the directions are `incoming` and `outgoing`.
971+
For type hierarchies, the directions are `subtypes` and `supertypes`.
972+
Note that, except for the root node of the hierarchy, resolving an
973+
item in both directions is discouraged.
889974
- A *GoTo* response. This is identified where the response type cannot
890975
be determined by any of the above methods. A GoTo response may contain
891976
either a single location (e.g. for `GoToDeclaration`), or a list of

update_api_docs.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
#!/usr/bin/env python3
22

3-
from __future__ import print_function
4-
from __future__ import division
5-
from __future__ import unicode_literals
6-
from __future__ import absolute_import
7-
83
import os
94
import platform
105
import sys

ycmd/completers/java/java_completer.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,3 +650,18 @@ def GetServerName( self ):
650650

651651
def GetCommandLine( self ):
652652
return self._command
653+
654+
655+
def Hierarchy( self, request_data, args ):
656+
# JDT.LS is a special snowflake and needs special snowflake treatement
657+
# See: https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3184
658+
result = super().Hierarchy( request_data, args )
659+
preparation_item, direction, kind = args
660+
if kind == 'call' and direction == 'incoming':
661+
for item in result:
662+
# The base class does almost the same,
663+
# but uses `selectionRange` instead of `range`.
664+
item[ 'root_location' ] = responses.BuildGoToResponseFromLocation(
665+
*language_server_completer._LspLocationToLocationAndDescription(
666+
request_data, item[ 'from' ] ) )
667+
return result

ycmd/completers/language_server/language_server_completer.py

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1685,7 +1685,7 @@ def GetDetailedDiagnostic( self, request_data ):
16851685
message = diagnostic[ 'message' ]
16861686
try:
16871687
code = diagnostic[ 'code' ]
1688-
message += f' [{ code }]'
1688+
message += f' [{ code }]' # noqa
16891689
except KeyError:
16901690
pass
16911691

@@ -1785,6 +1785,18 @@ def GetSubcommandsMap( self ):
17851785
lambda self, request_data, args: self.GetType( request_data )
17861786
)
17871787

1788+
if ( self._server_capabilities and
1789+
_IsCapabilityProvided( self._server_capabilities,
1790+
'typeHierarchyProvider' ) ):
1791+
commands[ 'TypeHierarchy' ] = (
1792+
lambda self, request_data, args:
1793+
self.InitialHierarchy( request_data, [ 'type' ] )
1794+
)
1795+
commands[ 'ResolveTypeHierarchyItem' ] = (
1796+
lambda self, request_data, args:
1797+
self.Hierarchy( request_data, [ *args, 'type' ] )
1798+
)
1799+
17881800
if ( self._server_capabilities and
17891801
_IsCapabilityProvided( self._server_capabilities,
17901802
'callHierarchyProvider' ) ):
@@ -1796,6 +1808,14 @@ def GetSubcommandsMap( self ):
17961808
lambda self, request_data, args:
17971809
self.CallHierarchy( request_data, [ 'incoming' ] )
17981810
)
1811+
commands[ 'CallHierarchy' ] = (
1812+
lambda self, request_data, args:
1813+
self.InitialHierarchy( request_data, [ 'call' ] )
1814+
)
1815+
commands[ 'ResolveCallHierarchyItem' ] = (
1816+
lambda self, request_data, args:
1817+
self.Hierarchy( request_data, [ *args, 'call' ] )
1818+
)
17991819

18001820
commands.update( self.GetCustomSubcommands() )
18011821

@@ -2643,14 +2663,105 @@ def GoToDocumentOutline( self, request_data ):
26432663
return _SymbolInfoListToGoTo( request_data, result )
26442664

26452665

2666+
def InitialHierarchy( self, request_data, args ):
2667+
if not self.ServerIsReady():
2668+
raise RuntimeError( 'Server is initializing. Please wait.' )
2669+
2670+
kind = args[ 0 ]
2671+
2672+
self._UpdateServerWithFileContents( request_data )
2673+
request_id = self.GetConnection().NextRequestId()
2674+
message = lsp.PrepareHierarchy( request_id, request_data, kind.title() )
2675+
prepare_response = self.GetConnection().GetResponse(
2676+
request_id,
2677+
message,
2678+
REQUEST_TIMEOUT_COMMAND )
2679+
preparation_item = prepare_response.get( 'result' ) or []
2680+
if not preparation_item:
2681+
raise RuntimeError( f'No { kind } hierarchy found.' )
2682+
2683+
assert len( preparation_item ) == 1, (
2684+
'Not available: Multiple hierarchies were received, '
2685+
'this is not currently supported.' )
2686+
2687+
preparation_item[ 0 ][ 'locations' ] = [
2688+
responses.BuildGoToResponseFromLocation(
2689+
*_LspLocationToLocationAndDescription( request_data,
2690+
location,
2691+
'selectionRange' ) )
2692+
for location in preparation_item ]
2693+
kind_string = lsp.SYMBOL_KIND[ preparation_item[ 0 ][ 'kind' ] ]
2694+
preparation_item[ 0 ][ 'kind' ] = kind_string
2695+
return preparation_item
2696+
2697+
2698+
def Hierarchy( self, request_data, args ):
2699+
if not self.ServerIsReady():
2700+
raise RuntimeError( 'Server is initializing. Please wait.' )
2701+
2702+
preparation_item, direction, kind = args
2703+
2704+
if item := ( preparation_item.get( 'from' ) or
2705+
preparation_item.get( 'to' ) ):
2706+
preparation_item = item
2707+
else:
2708+
del preparation_item[ 'locations' ]
2709+
kind_number = lsp.SYMBOL_KIND.index( preparation_item[ 'kind' ] )
2710+
preparation_item[ 'kind' ] = kind_number
2711+
2712+
if kind == 'call':
2713+
direction += 'Calls'
2714+
self._UpdateServerWithFileContents( request_data )
2715+
request_id = self.GetConnection().NextRequestId()
2716+
message = lsp.Hierarchy( request_id, kind, direction, preparation_item )
2717+
response = self.GetConnection().GetResponse( request_id,
2718+
message,
2719+
REQUEST_TIMEOUT_COMMAND )
2720+
2721+
result = response.get( 'result' )
2722+
if result:
2723+
for item in result:
2724+
if kind == 'call':
2725+
name_and_kind_key = 'to' if direction == 'outgoingCalls' else 'from'
2726+
hierarchy_item = item[ name_and_kind_key ]
2727+
kind_string = lsp.SYMBOL_KIND[ hierarchy_item[ 'kind' ] ]
2728+
item[ 'kind' ] = kind_string
2729+
item[ 'name' ] = hierarchy_item[ 'name' ]
2730+
lsp_locations = [ {
2731+
'uri': hierarchy_item[ 'uri' ],
2732+
'range': r }
2733+
for r in item[ 'fromRanges' ] ]
2734+
item[ 'locations' ] = [
2735+
responses.BuildGoToResponseFromLocation(
2736+
*_LspLocationToLocationAndDescription( request_data, location ) )
2737+
for location in lsp_locations ]
2738+
2739+
if direction == 'incomingCalls':
2740+
item[ 'root_location' ] = responses.BuildGoToResponseFromLocation(
2741+
*_LspLocationToLocationAndDescription( request_data,
2742+
hierarchy_item,
2743+
'selectionRange' ) )
2744+
else:
2745+
item[ 'kind' ] = lsp.SYMBOL_KIND[ item[ 'kind' ] ]
2746+
item[ 'locations' ] = [
2747+
responses.BuildGoToResponseFromLocation(
2748+
*_LspLocationToLocationAndDescription( request_data, location ) )
2749+
for location in [ item ] ]
2750+
return result
2751+
if kind == 'call':
2752+
raise RuntimeError(
2753+
f'No { direction.rstrip( "Calls" ) } { kind }s found.' )
2754+
else:
2755+
raise RuntimeError( f'No { direction } found.' )
2756+
26462757

26472758
def CallHierarchy( self, request_data, args ):
26482759
if not self.ServerIsReady():
26492760
raise RuntimeError( 'Server is initializing. Please wait.' )
26502761

26512762
self._UpdateServerWithFileContents( request_data )
26522763
request_id = self.GetConnection().NextRequestId()
2653-
message = lsp.PrepareCallHierarchy( request_id, request_data )
2764+
message = lsp.PrepareHierarchy( request_id, request_data, 'Call' )
26542765
prepare_response = self.GetConnection().GetResponse(
26552766
request_id,
26562767
message,
@@ -2666,7 +2777,10 @@ def CallHierarchy( self, request_data, args ):
26662777
preparation_item = preparation_item[ 0 ]
26672778

26682779
request_id = self.GetConnection().NextRequestId()
2669-
message = lsp.CallHierarchy( request_id, args[ 0 ], preparation_item )
2780+
message = lsp.Hierarchy( request_id,
2781+
'call',
2782+
args[ 0 ] + 'Calls',
2783+
preparation_item )
26702784
response = self.GetConnection().GetResponse( request_id,
26712785
message,
26722786
REQUEST_TIMEOUT_COMMAND )
@@ -3282,7 +3396,9 @@ def BuildGoToLocationFromSymbol( symbol ):
32823396
return locations
32833397

32843398

3285-
def _LspLocationToLocationAndDescription( request_data, location ):
3399+
def _LspLocationToLocationAndDescription( request_data,
3400+
location,
3401+
range_property = 'range' ):
32863402
"""Convert a LSP Location to a ycmd location."""
32873403
try:
32883404
filename = lsp.UriToFilePath( location[ 'uri' ] )
@@ -3299,9 +3415,10 @@ def _LspLocationToLocationAndDescription( request_data, location ):
32993415
'GoTo location' )
33003416
file_contents = []
33013417

3418+
range = location[ range_property ]
33023419
return _BuildLocationAndDescription( filename,
33033420
file_contents,
3304-
location[ 'range' ][ 'start' ] )
3421+
range[ 'start' ] )
33053422

33063423

33073424
def _LspToYcmdLocation( file_contents, location ):

ycmd/completers/language_server/language_server_protocol.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -633,8 +633,8 @@ def Position( line_num, line_value, column_codepoint ):
633633
}
634634

635635

636-
def PrepareCallHierarchy( request_id, request_data ):
637-
return BuildRequest( request_id, 'textDocument/prepareCallHierarchy', {
636+
def PrepareHierarchy( request_id, request_data, kind ):
637+
return BuildRequest( request_id, f'textDocument/prepare{ kind }Hierarchy', {
638638
'textDocument': {
639639
'uri': FilePathToUri( request_data[ 'filepath' ] ),
640640
},
@@ -644,8 +644,8 @@ def PrepareCallHierarchy( request_id, request_data ):
644644
} )
645645

646646

647-
def CallHierarchy( request_id, direction, item ):
648-
return BuildRequest( request_id, f'callHierarchy/{ direction }Calls', {
647+
def Hierarchy( request_id, kind, direction, item ):
648+
return BuildRequest( request_id, f'{ kind }Hierarchy/{ direction }', {
649649
'item': item
650650
} )
651651

0 commit comments

Comments
 (0)