@@ -25,6 +25,7 @@ import (
2525 "log/slog"
2626 "slices"
2727 "strings"
28+ "unicode"
2829
2930 "github.com/bufbuild/buf/private/bufpkg/bufmodule"
3031 "github.com/bufbuild/protocompile/experimental/ast"
@@ -351,27 +352,11 @@ func (s *symbol) getDocsFromComments() string {
351352 // traversing backwards for leading comemnts only.
352353 _ , start := def .Context ().Stream ().Around (def .Span ().Start )
353354 cursor := token .NewCursorAt (start )
354- t := cursor .PrevSkippable ()
355- for ! t .IsZero () {
356- switch t .Kind () {
357- case token .Comment :
358- comments = append (comments , commentToMarkdown (t .Text ()))
359- }
360- prev := cursor .PeekPrevSkippable ()
361- if ! prev .Kind ().IsSkippable () {
362- break
363- }
364- if prev .Kind () == token .Space {
365- // Check if the whitespace contains a newline. If so, then we break. This is to prevent
366- // picking up comments that are not contiguous to the declaration.
367- if strings .Contains (prev .Text (), "\n " ) {
368- break
369- }
370- }
371- t = cursor .PrevSkippable ()
355+ for t := cursor .PrevSkippable (); t .Kind () == token .Comment ; t = cursor .PrevSkippable () {
356+ comments = append (comments , commentToMarkdown (t .Text ()))
372357 }
373358 // Reverse the list and return joined.
374- slices .Reverse (comments )
359+ slices .Reverse (lineUpComments ( comments ) )
375360
376361 var docs strings.Builder
377362 for _ , comment := range comments {
@@ -450,6 +435,44 @@ func commentToMarkdown(comment string) string {
450435 return strings .TrimSuffix (strings .TrimPrefix (comment , "/*" ), "*/" )
451436}
452437
438+ // lineUpComments is a helper function for lining up the comments for docs, since some users
439+ // may start their comments with spaces, e.g.
440+ //
441+ // // Foo is a ...
442+ // //
443+ // // Foo is used for ...
444+ // message Foo { ... }
445+ //
446+ // vs.
447+ //
448+ // //Foo is a ...
449+ // //
450+ // //Foo is used for ...
451+ // message Foo { ... }
452+ //
453+ // When different LSP clients render these docs, they treat leading spaces differently. To
454+ // help mitigate this, we use the following heuristic to line up the comments:
455+ //
456+ // If all lines containing one or more non-space characters all start with a single space
457+ // character, we trim the space character for each of these lines. Otherwise, do nothing.
458+ func lineUpComments (comments []string ) []string {
459+ linedUp := make ([]string , len (comments ))
460+ for i , comment := range comments {
461+ if strings .ContainsFunc (comment , func (r rune ) bool {
462+ return ! unicode .IsSpace (r )
463+ }) {
464+ // We are only checking for " " and do not count nbsp's.
465+ if ! strings .HasPrefix (comment , " " ) {
466+ return comments
467+ }
468+ linedUp [i ] = strings .TrimPrefix (comment , " " )
469+ } else {
470+ linedUp [i ] = comment
471+ }
472+ }
473+ return linedUp
474+ }
475+
453476// irMemberDoc returns the documentation for a message field, enum value or extension field.
454477func irMemberDoc (irMember ir.Member ) string {
455478 number := irMember .Number ()
0 commit comments