@@ -16,7 +16,7 @@ use zed_extension_api::{
1616 self as zed, CodeLabel , CodeLabelSpan , DebugAdapterBinary , DebugTaskDefinition , Extension ,
1717 LanguageServerId , LanguageServerInstallationStatus , StartDebuggingRequestArguments ,
1818 StartDebuggingRequestArgumentsRequest , Worktree ,
19- lsp:: { Completion , CompletionKind } ,
19+ lsp:: { Completion , CompletionKind , Symbol , SymbolKind } ,
2020 register_extension,
2121 serde_json:: { Value , json} ,
2222 set_language_server_installation_status,
@@ -535,6 +535,199 @@ impl Extension for Java {
535535 _ => None ,
536536 } )
537537 }
538+
539+ fn label_for_symbol (
540+ & self ,
541+ _language_server_id : & LanguageServerId ,
542+ symbol : Symbol ,
543+ ) -> Option < CodeLabel > {
544+ let name = & symbol. name ;
545+
546+ match symbol. kind {
547+ SymbolKind :: Class => {
548+ // code: "class Name {}" → Tree-sitter: class_declaration
549+ // display: "class Name"
550+ let keyword = "class " ;
551+ let code = format ! ( "{keyword}{name} {{}}" ) ;
552+
553+ Some ( CodeLabel {
554+ spans : vec ! [ CodeLabelSpan :: code_range( 0 ..keyword. len( ) + name. len( ) ) ] ,
555+ filter_range : ( keyword. len ( ) ..keyword. len ( ) + name. len ( ) ) . into ( ) ,
556+ code,
557+ } )
558+ }
559+ SymbolKind :: Interface => {
560+ let keyword = "interface " ;
561+ let code = format ! ( "{keyword}{name} {{}}" ) ;
562+
563+ Some ( CodeLabel {
564+ spans : vec ! [ CodeLabelSpan :: code_range( 0 ..keyword. len( ) + name. len( ) ) ] ,
565+ filter_range : ( keyword. len ( ) ..keyword. len ( ) + name. len ( ) ) . into ( ) ,
566+ code,
567+ } )
568+ }
569+ SymbolKind :: Enum => {
570+ let keyword = "enum " ;
571+ let code = format ! ( "{keyword}{name} {{}}" ) ;
572+
573+ Some ( CodeLabel {
574+ spans : vec ! [ CodeLabelSpan :: code_range( 0 ..keyword. len( ) + name. len( ) ) ] ,
575+ filter_range : ( keyword. len ( ) ..keyword. len ( ) + name. len ( ) ) . into ( ) ,
576+ code,
577+ } )
578+ }
579+ SymbolKind :: Constructor => {
580+ // jdtls: "ClassName(Type, Type)"
581+ let ctor_name = name. split ( '(' ) . next ( ) . unwrap_or ( name) ;
582+ let rest = & name[ ctor_name. len ( ) ..] ;
583+ // Wrap in matching class for constructor_declaration AST node
584+ let prefix = format ! ( "class {ctor_name} {{ " ) ;
585+ let code = format ! ( "{prefix}{ctor_name}() {{}} }}" ) ;
586+ let ctor_start = prefix. len ( ) ;
587+
588+ let mut spans = vec ! [
589+ CodeLabelSpan :: code_range( ctor_start..ctor_start + ctor_name. len( ) ) ,
590+ ] ;
591+ if !rest. is_empty ( ) {
592+ spans. push ( CodeLabelSpan :: literal ( rest. to_string ( ) , None ) ) ;
593+ }
594+
595+ Some ( CodeLabel {
596+ spans,
597+ filter_range : ( 0 ..name. len ( ) ) . into ( ) ,
598+ code,
599+ } )
600+ }
601+ SymbolKind :: Method | SymbolKind :: Function => {
602+ // jdtls: "methodName(Type, Type) : ReturnType" or "methodName(Type)"
603+ // display: "ReturnType methodName(Type, Type)" (Java declaration order)
604+ let method_name = name. split ( '(' ) . next ( ) . unwrap_or ( name) ;
605+ let after_name = & name[ method_name. len ( ) ..] ;
606+
607+ let ( params, return_type) = if let Some ( ( p, r) ) = after_name. split_once ( " : " ) {
608+ ( p, Some ( r) )
609+ } else {
610+ ( after_name, None )
611+ } ;
612+
613+ let ret = return_type. unwrap_or ( "void" ) ;
614+ let class_open = "class _ { " ;
615+ let code = format ! ( "{class_open}{ret} {method_name}() {{}} }}" ) ;
616+
617+ let ret_start = class_open. len ( ) ;
618+ let name_start = ret_start + ret. len ( ) + 1 ;
619+
620+ // Display: "void methodName(String, int)"
621+ let mut spans = vec ! [
622+ CodeLabelSpan :: code_range( ret_start..ret_start + ret. len( ) ) ,
623+ CodeLabelSpan :: literal( " " . to_string( ) , None ) ,
624+ CodeLabelSpan :: code_range( name_start..name_start + method_name. len( ) ) ,
625+ ] ;
626+ if !params. is_empty ( ) {
627+ spans. push ( CodeLabelSpan :: literal ( params. to_string ( ) , None ) ) ;
628+ }
629+
630+ // filter on "methodName(params)" portion of displayed text
631+ let type_prefix_len = ret. len ( ) + 1 ; // "void "
632+ let filter_end = type_prefix_len + method_name. len ( ) + params. len ( ) ;
633+ Some ( CodeLabel {
634+ spans,
635+ filter_range : ( type_prefix_len..filter_end) . into ( ) ,
636+ code,
637+ } )
638+ }
639+ SymbolKind :: Field | SymbolKind :: Property => {
640+ // jdtls: "fieldName : Type" or just "fieldName"
641+ // display: "Type fieldName" (Java declaration order)
642+ if let Some ( ( field_name, field_type) ) = name. split_once ( " : " ) {
643+ let class_open = "class _ { " ;
644+ let code = format ! ( "{class_open}{field_type} {field_name}; }}" ) ;
645+
646+ let type_start = class_open. len ( ) ;
647+ let name_start = type_start + field_type. len ( ) + 1 ;
648+
649+ // Display: "String fieldName"
650+ let spans = vec ! [
651+ CodeLabelSpan :: code_range( type_start..type_start + field_type. len( ) ) ,
652+ CodeLabelSpan :: literal( " " . to_string( ) , None ) ,
653+ CodeLabelSpan :: code_range( name_start..name_start + field_name. len( ) ) ,
654+ ] ;
655+
656+ let type_prefix_len = field_type. len ( ) + 1 ; // "String "
657+ Some ( CodeLabel {
658+ spans,
659+ filter_range : ( type_prefix_len..type_prefix_len + field_name. len ( ) ) . into ( ) ,
660+ code,
661+ } )
662+ } else {
663+ // No type info, just show the name
664+ let class_open = "class _ { int " ;
665+ let code = format ! ( "{class_open}{name}; }}" ) ;
666+ let name_start = class_open. len ( ) ;
667+
668+ Some ( CodeLabel {
669+ spans : vec ! [ CodeLabelSpan :: code_range(
670+ name_start..name_start + name. len( ) ,
671+ ) ] ,
672+ filter_range : ( 0 ..name. len ( ) ) . into ( ) ,
673+ code,
674+ } )
675+ }
676+ }
677+ SymbolKind :: Constant => {
678+ // Wrap in class; ALL_CAPS names get @constant from highlights.scm regex
679+ let class_open = "class _ { static final int " ;
680+ let code = format ! ( "{class_open}{name}; }}" ) ;
681+ let name_start = class_open. len ( ) ;
682+
683+ Some ( CodeLabel {
684+ spans : vec ! [ CodeLabelSpan :: code_range(
685+ name_start..name_start + name. len( ) ,
686+ ) ] ,
687+ filter_range : ( 0 ..name. len ( ) ) . into ( ) ,
688+ code,
689+ } )
690+ }
691+ SymbolKind :: EnumMember => {
692+ // Wrap in enum for enum_constant AST node → @constant highlight
693+ let prefix = "enum _ { " ;
694+ let code = format ! ( "{prefix}{name} }}" ) ;
695+ let name_start = prefix. len ( ) ;
696+
697+ Some ( CodeLabel {
698+ spans : vec ! [ CodeLabelSpan :: code_range(
699+ name_start..name_start + name. len( ) ,
700+ ) ] ,
701+ filter_range : ( 0 ..name. len ( ) ) . into ( ) ,
702+ code,
703+ } )
704+ }
705+ SymbolKind :: Variable => {
706+ let class_open = "class _ { int " ;
707+ let code = format ! ( "{class_open}{name}; }}" ) ;
708+ let name_start = class_open. len ( ) ;
709+
710+ Some ( CodeLabel {
711+ spans : vec ! [ CodeLabelSpan :: code_range(
712+ name_start..name_start + name. len( ) ,
713+ ) ] ,
714+ filter_range : ( 0 ..name. len ( ) ) . into ( ) ,
715+ code,
716+ } )
717+ }
718+ SymbolKind :: Package | SymbolKind :: Module | SymbolKind :: Namespace => {
719+ let keyword = "package " ;
720+ let code = format ! ( "{keyword}{name};" ) ;
721+
722+ Some ( CodeLabel {
723+ spans : vec ! [ CodeLabelSpan :: code_range( 0 ..keyword. len( ) + name. len( ) ) ] ,
724+ filter_range : ( keyword. len ( ) ..keyword. len ( ) + name. len ( ) ) . into ( ) ,
725+ code,
726+ } )
727+ }
728+ _ => None ,
729+ }
730+ }
538731}
539732
540733register_extension ! ( Java ) ;
0 commit comments