@@ -56,7 +56,8 @@ func getCompletionItems(
5656
5757 // This grabs the contents of the file as the top-level [ast.DeclBody], see [ast.File].Decls()
5858 // for reference.
59- declPath := getDeclForPosition (id .Wrap (file .ir .AST (), id.ID [ast.DeclBody ](1 )), position )
59+ offset := positionToOffset (file , position )
60+ declPath := getDeclForOffset (id .Wrap (file .ir .AST (), id.ID [ast.DeclBody ](1 )), offset )
6061 if len (declPath ) > 0 {
6162 decl := declPath [len (declPath )- 1 ]
6263 file .lsp .logger .DebugContext (
@@ -112,9 +113,7 @@ func completionItemsForDeclPath(ctx context.Context, file *file, declPath []ast.
112113func completionItemsForSyntax (ctx context.Context , file * file , syntaxDecl ast.DeclSyntax , position protocol.Position ) []protocol.CompletionItem {
113114 file .lsp .logger .DebugContext (ctx , "completion: syntax declaration" , slog .Bool ("is_edition" , syntaxDecl .IsEdition ()))
114115
115- positionLocation := file .file .InverseLocation (int (position .Line )+ 1 , int (position .Character )+ 1 , positionalEncoding )
116- offset := positionLocation .Offset
117-
116+ offset := positionToOffset (file , position )
118117 valueSpan := syntaxDecl .Value ().Span ()
119118 start , end := valueSpan .Start , valueSpan .End
120119 valueText := valueSpan .Text ()
@@ -227,12 +226,27 @@ func completionItemsForPackage(ctx context.Context, file *file, syntaxPackage as
227226
228227// completionItemsForDef returns completion items for definition declarations (message, enum, service, etc.).
229228func completionItemsForDef (ctx context.Context , file * file , declPath []ast.DeclAny , def ast.DeclDef , position protocol.Position ) []protocol.CompletionItem {
229+ offset := positionToOffset (file , position )
230+ inBody := offsetInSpan (offset , def .Body ().Span ()) == 0
231+
232+ // Parent declaration determines child completions.
233+ var parentDef ast.DeclDef
234+ if len (declPath ) >= 2 {
235+ parentDef = declPath [len (declPath )- 2 ].AsDef ()
236+ }
237+ if inBody {
238+ parentDef = def
239+ def = ast.DeclDef {} // Mark current def as invalid.
240+ }
241+
230242 file .lsp .logger .DebugContext (
231243 ctx ,
232244 "completion: definition" ,
233245 slog .String ("type" , def .Type ().Span ().String ()),
234246 slog .String ("name" , def .Name ().Span ().String ()),
235247 slog .String ("kind" , def .Classify ().String ()),
248+ slog .String ("parent_kind" , parentDef .Classify ().String ()),
249+ slog .Bool ("in_body" , inBody ),
236250 slog .Int ("path_depth" , len (declPath )),
237251 )
238252
@@ -248,9 +262,6 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
248262 return nil
249263 }
250264
251- positionLocation := file .file .InverseLocation (int (position .Line )+ 1 , int (position .Character )+ 1 , positionalEncoding )
252- offset := positionLocation .Offset
253-
254265 tokenSpan := extractAroundOffset (file , offset , isTokenType , isTokenType )
255266 tokenPrefix , tokenSuffix := splitSpan (tokenSpan , offset )
256267
@@ -323,7 +334,7 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
323334 }
324335
325336 // If at the top level, and on the first item, return top level keywords.
326- if len ( declPath ) == 1 {
337+ if parentDef . IsZero () {
327338 showKeywords := beforeCount == 0
328339 if showKeywords {
329340 file .lsp .logger .DebugContext (ctx , "completion: definition returning top-level keywords" )
@@ -337,17 +348,6 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
337348 return nil // unknown
338349 }
339350
340- // Parent declaration determines child completions.
341- parent := declPath [len (declPath )- 2 ]
342- if parent .Kind () != ast .DeclKindDef {
343- return nil
344- }
345- parentDef := parent .AsDef ()
346- file .lsp .logger .DebugContext (
347- ctx , "completion: definition nested declaration" ,
348- slog .String ("kind" , parentDef .Classify ().String ()),
349- )
350-
351351 if offsetInSpan (offset , def .Options ().Span ()) == 0 {
352352 // TODO: Handle option completions within options block.
353353 file .lsp .logger .DebugContext (ctx , "completion: ignoring options block completion" )
@@ -496,9 +496,7 @@ func completionItemsForDef(ctx context.Context, file *file, declPath []ast.DeclA
496496func completionItemsForImport (ctx context.Context , file * file , declImport ast.DeclImport , position protocol.Position ) []protocol.CompletionItem {
497497 file .lsp .logger .DebugContext (ctx , "completion: import declaration" , slog .Int ("importable_count" , len (file .workspace .PathToFile ())))
498498
499- positionLocation := file .file .InverseLocation (int (position .Line )+ 1 , int (position .Character )+ 1 , positionalEncoding )
500- offset := positionLocation .Offset
501-
499+ offset := positionToOffset (file , position )
502500 importPathSpan := declImport .ImportPath ().Span ()
503501 start , end := importPathSpan .Start , importPathSpan .End
504502 importPathText := importPathSpan .Text ()
@@ -845,15 +843,15 @@ func joinSequences[T any](itemIters ...iter.Seq[T]) iter.Seq[T] {
845843 }
846844}
847845
848- // getDeclForPosition finds the path of AST declarations from parent to smallest that contains the given protocol position .
846+ // getDeclForOffset finds the path of AST declarations from parent to smallest that contains the given offset .
849847// Returns a slice where [0] is the top-level declaration and [len-1] is the smallest/innermost declaration.
850- // Returns nil if no declaration contains the position .
851- func getDeclForPosition (body ast.DeclBody , position protocol. Position ) []ast.DeclAny {
852- return getDeclForPositionHelper (body , position , nil )
848+ // Returns nil if no declaration contains the offset .
849+ func getDeclForOffset (body ast.DeclBody , offset int ) []ast.DeclAny {
850+ return getDeclForOffsetHelper (body , offset , nil )
853851}
854852
855- // getDeclForPositionHelper is the recursive helper for getDeclForPosition .
856- func getDeclForPositionHelper (body ast.DeclBody , position protocol. Position , path []ast.DeclAny ) []ast.DeclAny {
853+ // getDeclForOffsetHelper is the recursive helper for getDeclForOffset .
854+ func getDeclForOffsetHelper (body ast.DeclBody , offset int , path []ast.DeclAny ) []ast.DeclAny {
857855 smallestSize := - 1
858856 var bestPath []ast.DeclAny
859857
@@ -865,10 +863,8 @@ func getDeclForPositionHelper(body ast.DeclBody, position protocol.Position, pat
865863 if span .IsZero () {
866864 continue
867865 }
868-
869866 // Check if the position is within this declaration's span.
870- within := reportSpanToProtocolRange (span )
871- if positionInRange (position , within ) == 0 {
867+ if offsetInSpan (offset , span ) == 0 {
872868 // Build the new path including this declaration.
873869 newPath := append (append ([]ast.DeclAny (nil ), path ... ), decl )
874870 size := span .End - span .Start
@@ -879,7 +875,7 @@ func getDeclForPositionHelper(body ast.DeclBody, position protocol.Position, pat
879875
880876 // If this is a definition with a body, search recursively.
881877 if decl .Kind () == ast .DeclKindDef && ! decl .AsDef ().Body ().IsZero () {
882- childPath := getDeclForPositionHelper (decl .AsDef ().Body (), position , newPath )
878+ childPath := getDeclForOffsetHelper (decl .AsDef ().Body (), offset , newPath )
883879 if len (childPath ) > 0 {
884880 childSize := childPath [len (childPath )- 1 ].Span ().End - childPath [len (childPath )- 1 ].Span ().Start
885881 if childSize < smallestSize {
@@ -974,6 +970,12 @@ func offsetInSpan(offset int, span report.Span) int {
974970 return 0
975971}
976972
973+ // positionToOffset returns the offset from the protocol position.
974+ func positionToOffset (file * file , position protocol.Position ) int {
975+ positionLocation := file .file .InverseLocation (int (position .Line )+ 1 , int (position .Character )+ 1 , positionalEncoding )
976+ return positionLocation .Offset
977+ }
978+
977979// isProto2 returns true if the file has a syntax declaration of proto2.
978980func isProto2 (file * file ) bool {
979981 return file .ir .Syntax () == syntax .Proto2
0 commit comments