| // Copyright 2023 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 | package mod | 
 |  | 
 | import ( | 
 | 	"context" | 
 | 	"fmt" | 
 |  | 
 | 	"golang.org/x/mod/modfile" | 
 | 	"golang.org/x/tools/gopls/internal/cache" | 
 | 	"golang.org/x/tools/gopls/internal/file" | 
 | 	"golang.org/x/tools/gopls/internal/protocol" | 
 | ) | 
 |  | 
 | func InlayHint(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, _ protocol.Range) ([]protocol.InlayHint, error) { | 
 | 	// Inlay hints are enabled if the client supports them. | 
 | 	pm, err := snapshot.ParseMod(ctx, fh) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	// Compare the version of the module used in the snapshot's | 
 | 	// metadata (i.e. the solution to the MVS constraints computed | 
 | 	// by go list) with the version requested by the module, in | 
 | 	// both cases, taking replaces into account. Produce an | 
 | 	// InlayHint when the version of the module is not the one | 
 | 	// used. | 
 |  | 
 | 	replaces := make(map[string]*modfile.Replace) | 
 | 	for _, x := range pm.File.Replace { | 
 | 		replaces[x.Old.Path] = x | 
 | 	} | 
 |  | 
 | 	requires := make(map[string]*modfile.Require) | 
 | 	for _, x := range pm.File.Require { | 
 | 		requires[x.Mod.Path] = x | 
 | 	} | 
 |  | 
 | 	am, err := snapshot.AllMetadata(ctx) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 |  | 
 | 	var ans []protocol.InlayHint | 
 | 	seen := make(map[string]bool) | 
 | 	for _, meta := range am { | 
 | 		if meta.Module == nil || seen[meta.Module.Path] { | 
 | 			continue | 
 | 		} | 
 | 		seen[meta.Module.Path] = true | 
 | 		metaVersion := meta.Module.Version | 
 | 		if meta.Module.Replace != nil { | 
 | 			metaVersion = meta.Module.Replace.Version | 
 | 		} | 
 | 		// These versions can be blank, as in gopls/go.mod's local replace | 
 | 		if oldrepl, ok := replaces[meta.Module.Path]; ok && oldrepl.New.Version != metaVersion { | 
 | 			ih := genHint(oldrepl.Syntax, oldrepl.New.Version, metaVersion, pm.Mapper) | 
 | 			if ih != nil { | 
 | 				ans = append(ans, *ih) | 
 | 			} | 
 | 		} else if oldreq, ok := requires[meta.Module.Path]; ok && oldreq.Mod.Version != metaVersion { | 
 | 			// maybe it was replaced: | 
 | 			if _, ok := replaces[meta.Module.Path]; ok { | 
 | 				continue | 
 | 			} | 
 | 			ih := genHint(oldreq.Syntax, oldreq.Mod.Version, metaVersion, pm.Mapper) | 
 | 			if ih != nil { | 
 | 				ans = append(ans, *ih) | 
 | 			} | 
 | 		} | 
 | 	} | 
 | 	return ans, nil | 
 | } | 
 |  | 
 | func genHint(mline *modfile.Line, oldVersion, newVersion string, m *protocol.Mapper) *protocol.InlayHint { | 
 | 	x := mline.End.Byte // the parser has removed trailing whitespace and comments (see modfile_test.go) | 
 | 	x -= len(mline.Token[len(mline.Token)-1]) | 
 | 	line, err := m.OffsetPosition(x) | 
 | 	if err != nil { | 
 | 		return nil | 
 | 	} | 
 | 	part := protocol.InlayHintLabelPart{ | 
 | 		Value: newVersion, | 
 | 		Tooltip: &protocol.OrPTooltipPLabel{ | 
 | 			Value: fmt.Sprintf("The build selects version %s rather than go.mod's version %s.", newVersion, oldVersion), | 
 | 		}, | 
 | 	} | 
 | 	rng, err := m.OffsetRange(x, mline.End.Byte) | 
 | 	if err != nil { | 
 | 		return nil | 
 | 	} | 
 | 	te := protocol.TextEdit{ | 
 | 		Range:   rng, | 
 | 		NewText: newVersion, | 
 | 	} | 
 | 	return &protocol.InlayHint{ | 
 | 		Position:     line, | 
 | 		Label:        []protocol.InlayHintLabelPart{part}, | 
 | 		Kind:         protocol.Parameter, | 
 | 		PaddingRight: true, | 
 | 		TextEdits:    []protocol.TextEdit{te}, | 
 | 	} | 
 | } |