@@ -237,11 +237,12 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
237237 )
238238
239239 switch def .Classify () {
240- case ast .DefKindMessage , ast .DefKindService , ast .DefKindEnum :
241- return nil // ignored
242- case ast .DefKindField , ast .DefKindInvalid :
243- // Use DefKindField and DefKindInvalid as completion starts.
244- // An invalid field is caused from partial values, with still invalid syntax.
240+ case ast .DefKindMessage , ast .DefKindService , ast .DefKindEnum , ast .DefKindGroup :
241+ // Ignore these kinds as this will be a completion for the name of the declaration.
242+ return nil
243+ case ast .DefKindField , ast .DefKindMethod , ast .DefKindInvalid :
244+ // Use these kinds as completion starts.
245+ // An invalid kind is caused from partial values, which may be any kind.
245246 default :
246247 file .lsp .logger .DebugContext (ctx , "completion: unknown definition type" , slog .String ("kind" , def .Classify ().String ()))
247248 return nil
@@ -257,56 +258,47 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
257258 // Use the token stream to capture invalid declarations.
258259 beforeCount , afterCount := 0 , 0
259260 hasBeforeGap , hasAfterGap := false , false
260- hasBeforeNewline , hasAfterNewline := false , false
261- hasFieldModifier := false
261+ hasStart := false // Start is a newline or open parenthesis for the start of a definition
262+ hasTypeModifier := false
262263 hasDeclaration := false
263264 typeSpan := extractAroundOffset (file , offset ,
264265 func (tok token.Token ) bool {
266+ if isTokenTypeDelimiter (tok ) {
267+ hasStart = true
268+ return false
269+ }
265270 if hasBeforeGap {
266271 beforeCount += 1
267272 hasBeforeGap = false
268273 if kw := tok .Keyword (); kw != keyword .Unknown {
269274 _ , isDeclaration := declarationSet [tok .Keyword ()]
270275 hasDeclaration = hasDeclaration || isDeclaration
271- _ , isFieldModifier := fieldModifierSet [tok .Keyword ()]
272- hasFieldModifier = hasFieldModifier || isFieldModifier
276+ _ , isFieldModifier := typeModifierSet [tok .Keyword ()]
277+ hasTypeModifier = hasTypeModifier || isFieldModifier
273278 }
274279 }
275- if isTokenNewline (tok ) {
276- hasBeforeNewline = true
277- return false
278- }
279280 if isTokenSpace (tok ) {
280281 hasBeforeGap = true
281282 return true
282283 }
283- return isTokenType (tok )
284+ return isTokenType (tok ) || isTokenParen ( tok )
284285 },
285286 func (tok token.Token ) bool {
286287 if hasAfterGap {
287288 afterCount += 1
288289 hasAfterGap = false
289290 }
290- if isTokenNewline (tok ) {
291- hasAfterNewline = true
291+ if isTokenTypeDelimiter (tok ) {
292292 return false
293293 }
294294 if isTokenSpace (tok ) {
295295 hasAfterGap = true
296296 return true
297297 }
298- return isTokenType (tok )
298+ return isTokenType (tok ) || isTokenParen ( tok )
299299 },
300300 )
301301 typePrefix , typeSuffix := splitSpan (typeSpan , offset )
302- if ! hasBeforeNewline {
303- file .lsp .logger .DebugContext (
304- ctx ,
305- "completion: ignoring definition type not on newline" ,
306- slog .String ("kind" , def .Classify ().String ()),
307- )
308- return nil
309- }
310302 file .lsp .logger .DebugContext (
311303 ctx , "completion: definition value" ,
312304 slog .String ("token" , tokenSpan .Text ()),
@@ -317,15 +309,23 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
317309 slog .String ("type_suffix" , typeSuffix ),
318310 slog .Int ("before_count" , beforeCount ),
319311 slog .Int ("after_count" , afterCount ),
320- slog .Bool ("has_before_newline" , hasBeforeNewline ),
321- slog .Bool ("has_after_newline" , hasAfterNewline ),
322- slog .Bool ("has_field_modifier" , hasFieldModifier ),
312+ slog .Bool ("has_start" , hasStart ),
313+ slog .Bool ("has_field_modifier" , hasTypeModifier ),
323314 slog .Bool ("has_declaration" , hasDeclaration ),
324315 )
316+ if ! hasStart {
317+ file .lsp .logger .DebugContext (
318+ ctx ,
319+ "completion: ignoring definition type unable to find start" ,
320+ slog .String ("kind" , def .Classify ().String ()),
321+ )
322+ return nil
323+ }
325324
326325 // If at the top level, and on the first item, return top level keywords.
327326 if len (declPath ) == 1 {
328- if beforeCount == 0 {
327+ showKeywords := beforeCount == 0
328+ if showKeywords {
329329 file .lsp .logger .DebugContext (ctx , "completion: definition returning top-level keywords" )
330330 return slices .Collect (keywordToCompletionItem (
331331 topLevelKeywords (),
@@ -337,6 +337,7 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
337337 return nil // unknown
338338 }
339339
340+ // Parent declaration determines child completions.
340341 parent := declPath [len (declPath )- 2 ]
341342 if parent .Kind () != ast .DeclKindDef {
342343 return nil
@@ -347,22 +348,20 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
347348 slog .String ("kind" , parentDef .Classify ().String ()),
348349 )
349350
350- if offsetInSpan (def .Options ().Span (), offset ) {
351+ if offsetInSpan (offset , def .Options ().Span ()) == 0 {
351352 // TODO: Handle option completions within options block.
352353 file .lsp .logger .DebugContext (ctx , "completion: ignoring options block completion" )
353354 return nil
354355 }
355356
356- // Limit completions based on the following heuristic:
357- // - Show keywords for the first values
358- // - Show types if no type declaration and at first, or second position with field modifier.
359- showKeywords := beforeCount == 0
360- showTypes := ! hasDeclaration &&
361- ((hasFieldModifier && beforeCount == 1 ) || (beforeCount == 0 ))
362-
363357 var iters []iter.Seq [protocol.CompletionItem ]
364358 switch parentDef .Classify () {
365359 case ast .DefKindMessage :
360+ // Limit completions based on the following heuristics:
361+ // - Show keywords for the first values
362+ // - Show types if no type declaration and at first, or second position with field modifier.
363+ showKeywords := beforeCount == 0
364+ showTypes := ! hasDeclaration && (beforeCount == 0 || (hasTypeModifier && beforeCount == 1 ))
366365 if showKeywords {
367366 isProto2 := isProto2 (file )
368367 iters = append (iters ,
@@ -393,36 +392,89 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
393392 findTypeFullName (file , parentDef ),
394393 tokenSpan ,
395394 offset ,
395+ true , // Allow enums.
396396 ),
397397 )
398398 }
399399 case ast .DefKindService :
400+ // Method types are only shown within args of the method definition.
401+ // Use the type to handle invalid defs.
402+ defMethod := def .AsMethod ()
403+ isRPC := defMethod .Keyword .Keyword () == keyword .RPC ||
404+ strings .HasPrefix (typeSpan .Text (), keyword .RPC .String ())
405+
406+ // Limit complitions based on the following heuristics:
407+ // - Show keywords for the first values.
408+ // - Show return keyword if an RPC method and at the third position.
409+ // - Show types if an RPC method and within the Input or Output args.
410+ showKeywords := beforeCount == 0
411+ showReturnKeyword := isRPC &&
412+ (beforeCount == 2 || offsetInSpan (offset , defMethod .Signature .Returns ().Span ()) == 0 )
413+ showTypes := isRPC && ! hasDeclaration &&
414+ (beforeCount == 0 || (hasTypeModifier && beforeCount == 1 )) &&
415+ (offsetInSpan (offset , defMethod .Signature .Inputs ().Span ()) == 0 ||
416+ offsetInSpan (offset , defMethod .Signature .Outputs ().Span ()) == 0 )
417+
400418 if showKeywords {
419+ // If both showKeywords and showTypes is set, we are in a services method args.
420+ if showTypes {
421+ iters = append (iters ,
422+ keywordToCompletionItem (
423+ methodArgLevelKeywords (),
424+ protocol .CompletionItemKindKeyword ,
425+ tokenSpan ,
426+ offset ,
427+ ),
428+ )
429+ } else {
430+ iters = append (iters ,
431+ keywordToCompletionItem (
432+ serviceLevelKeywords (),
433+ protocol .CompletionItemKindKeyword ,
434+ tokenSpan ,
435+ offset ,
436+ ),
437+ )
438+ }
439+ } else if showReturnKeyword {
401440 iters = append (iters ,
402441 keywordToCompletionItem (
403- serviceLevelKeywords (),
442+ serviceReturnKeyword (),
404443 protocol .CompletionItemKindKeyword ,
405444 tokenSpan ,
406445 offset ,
407446 ),
408447 )
409448 }
449+ if showTypes {
450+ iters = append (iters ,
451+ typeReferencesToCompletionItems (
452+ file ,
453+ "" , // No parent type within a service declaration.
454+ tokenSpan ,
455+ offset ,
456+ false , // Disallow enums.
457+ ),
458+ )
459+ }
410460 case ast .DefKindMethod :
461+ showKeywords := beforeCount == 0
411462 if showKeywords {
412463 iters = append (iters ,
413464 keywordToCompletionItem (
414- optionKeyword (),
465+ optionKeywords (),
415466 protocol .CompletionItemKindKeyword ,
416467 tokenSpan ,
417468 offset ,
418469 ),
419470 )
420471 }
421472 case ast .DefKindEnum :
473+ showKeywords := beforeCount == 0
422474 if showKeywords {
423475 iters = append (iters ,
424476 keywordToCompletionItem (
425- optionKeyword (),
477+ optionKeywords (),
426478 protocol .CompletionItemKindKeyword ,
427479 tokenSpan ,
428480 offset ,
@@ -544,10 +596,13 @@ var declarationSet = func() map[keyword.Keyword]struct{} {
544596 return m
545597}()
546598
547- // fieldModifierSet is the set of keywords for type modifiers.
548- var fieldModifierSet = func () map [keyword.Keyword ]struct {} {
599+ // typeModifierSet is the set of keywords for type modifiers.
600+ var typeModifierSet = func () map [keyword.Keyword ]struct {} {
549601 m := make (map [keyword.Keyword ]struct {})
550- for keyword := range messageLevelFieldKeywords () {
602+ for keyword := range joinSequences (
603+ messageLevelFieldKeywords (),
604+ methodArgLevelKeywords (),
605+ ) {
551606 m [keyword ] = struct {}{}
552607 }
553608 return m
@@ -621,8 +676,22 @@ func serviceLevelKeywords() iter.Seq[keyword.Keyword] {
621676 }
622677}
623678
624- // optionKeyword returns the option keywords for methods and enums.
625- func optionKeyword () iter.Seq [keyword.Keyword ] {
679+ // serviceReturnKeyword returns keyword for service "return" value.
680+ func serviceReturnKeyword () iter.Seq [keyword.Keyword ] {
681+ return func (yield func (keyword.Keyword ) bool ) {
682+ _ = yield (keyword .Returns )
683+ }
684+ }
685+
686+ // methodArgLevelKeywords returns keyword for methods.
687+ func methodArgLevelKeywords () iter.Seq [keyword.Keyword ] {
688+ return func (yield func (keyword.Keyword ) bool ) {
689+ _ = yield (keyword .Stream )
690+ }
691+ }
692+
693+ // optionKeywords returns the option keywords for methods and enums.
694+ func optionKeywords () iter.Seq [keyword.Keyword ] {
626695 return func (yield func (keyword.Keyword ) bool ) {
627696 _ = yield (keyword .Option )
628697 }
@@ -663,6 +732,7 @@ func typeReferencesToCompletionItems(
663732 parentFullName ir.FullName ,
664733 span report.Span ,
665734 offset int ,
735+ allowEnums bool ,
666736) iter.Seq [protocol.CompletionItem ] {
667737 fileSymbolTypesIter := func (yield func (* file , * symbol ) bool ) {
668738 for _ , imported := range current .workspace .PathToFile () {
@@ -713,12 +783,15 @@ func typeReferencesToCompletionItems(
713783 case ir .SymbolKindMessage :
714784 kind = protocol .CompletionItemKindClass // Messages are like classes
715785 case ir .SymbolKindEnum :
786+ if ! allowEnums {
787+ continue
788+ }
716789 kind = protocol .CompletionItemKindEnum
717790 default :
718791 continue // Unsupported kind, skip it.
719792 }
720793 label := string (symbol .ir .FullName ())
721- if strings .HasPrefix (label , parentPrefix ) {
794+ if len ( parentFullName ) > 0 && strings .HasPrefix (label , parentPrefix ) {
722795 label = label [len (parentPrefix ):]
723796 } else if strings .HasPrefix (label , packagePrefix ) {
724797 label = label [len (packagePrefix ):]
@@ -829,8 +902,17 @@ func isTokenSpace(tok token.Token) bool {
829902 return tok .Kind () == token .Space && strings .IndexByte (tok .Text (), '\n' ) == - 1
830903}
831904
832- func isTokenNewline (tok token.Token ) bool {
833- return tok .Kind () == token .Space && strings .IndexByte (tok .Text (), '\n' ) != - 1
905+ func isTokenParen (tok token.Token ) bool {
906+ return tok .Kind () == token .Punct &&
907+ (strings .HasPrefix (tok .Text (), "(" ) ||
908+ strings .HasSuffix (tok .Text (), ")" ))
909+ }
910+
911+ func isTokenTypeDelimiter (tok token.Token ) bool {
912+ kind := tok .Kind ()
913+ return (kind == token .Unrecognized && tok .IsZero ()) ||
914+ (kind == token .Space && strings .IndexByte (tok .Text (), '\n' ) != - 1 ) ||
915+ (kind == token .Comment )
834916}
835917
836918// extractAroundOffset extracts the value around the offset by querying the token stream.
@@ -852,7 +934,7 @@ func extractAroundOffset(file *file, offset int, isTokenBefore, isTokenAfter fun
852934 Start : offset ,
853935 End : offset ,
854936 }
855- if ! before .IsZero () {
937+ if ! before .IsZero () && isTokenBefore != nil {
856938 var firstToken token.Token
857939 for cursor := token .NewCursorAt (before ); isTokenBefore (before ); before = cursor .PrevSkippable () {
858940 firstToken = before
@@ -861,7 +943,7 @@ func extractAroundOffset(file *file, offset int, isTokenBefore, isTokenAfter fun
861943 span .Start = firstToken .Span ().Start
862944 }
863945 }
864- if ! after .IsZero () {
946+ if ! after .IsZero () && isTokenAfter != nil {
865947 var lastToken token.Token
866948 for cursor := token .NewCursorAt (after ); isTokenAfter (after ); after = cursor .NextSkippable () {
867949 lastToken = after
@@ -874,16 +956,22 @@ func extractAroundOffset(file *file, offset int, isTokenBefore, isTokenAfter fun
874956}
875957
876958func splitSpan (span report.Span , offset int ) (prefix string , suffix string ) {
877- if ! offsetInSpan (span , offset ) {
959+ if offsetInSpan (offset , span ) != 0 {
878960 return "" , ""
879961 }
880962 index := offset - span .Start
881963 text := span .Text ()
882964 return text [:index ], text [index :]
883965}
884966
885- func offsetInSpan (span report.Span , offset int ) bool {
886- return span .Start <= offset && offset <= span .End // End is inclusive for completions_
967+ func offsetInSpan (offset int , span report.Span ) int {
968+ if offset < span .Start {
969+ return - 1
970+ } else if offset > span .End {
971+ // End is inclusive for completions _
972+ return 1
973+ }
974+ return 0
887975}
888976
889977// isProto2 returns true if the file has a syntax declaration of proto2.
0 commit comments