[tools] gopls/internal/protocol: replace position usage to range

1 view
Skip to first unread message

Hongxiang Jiang (Gerrit)

unread,
Dec 23, 2025, 5:03:10 AM (yesterday) Dec 23
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Hongxiang Jiang has uploaded the change for review

Commit message

gopls/internal/protocol: replace position usage to range

TextDocumentPositionParams has been extended to support
optional range, to avoid duplicating range validation
logic for all LSP methods, this CL deprecate the Position
field and replace all usage of position with range.

The completion is now an exception as it should only be
triggered based on the cursor position (which does not
have a range).
Change-Id: I758f105271f5da6fa10a996ab9339938f60e2eae

Change diff

diff --git a/gopls/internal/cmd/capabilities_test.go b/gopls/internal/cmd/capabilities_test.go
index 9809b72..a3a438f 100644
--- a/gopls/internal/cmd/capabilities_test.go
+++ b/gopls/internal/cmd/capabilities_test.go
@@ -124,9 +124,15 @@
TextDocument: protocol.TextDocumentIdentifier{
URI: uri,
},
- Position: protocol.Position{
- Line: 0,
- Character: 28,
+ Range: protocol.Range{
+ Start: protocol.Position{
+ Line: 0,
+ Character: 28,
+ },
+ End: protocol.Position{
+ Line: 0,
+ Character: 28,
+ },
},
},
})
diff --git a/gopls/internal/goasm/definition.go b/gopls/internal/goasm/definition.go
index b3c5251..74a27ce 100644
--- a/gopls/internal/goasm/definition.go
+++ b/gopls/internal/goasm/definition.go
@@ -21,7 +21,7 @@
)

// Definition handles the textDocument/definition request for Go assembly files.
-func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) {
+func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.Location, error) {
ctx, done := event.Start(ctx, "goasm.Definition")
defer done()

@@ -36,7 +36,7 @@
return nil, err
}
mapper := protocol.NewMapper(fh.URI(), content)
- offset, err := mapper.PositionOffset(position)
+ start, end, err := mapper.RangeOffsets(rng)
if err != nil {
return nil, err
}
@@ -51,7 +51,7 @@
// For now, just find the identifier around the cursor.
var found *asm.Ident
for _, id := range file.Idents {
- if id.Offset <= offset && offset <= id.End() {
+ if id.Offset <= start && end <= id.End() {
found = &id
break
}
diff --git a/gopls/internal/golang/call_hierarchy.go b/gopls/internal/golang/call_hierarchy.go
index 194935a..e0cfb60 100644
--- a/gopls/internal/golang/call_hierarchy.go
+++ b/gopls/internal/golang/call_hierarchy.go
@@ -26,7 +26,7 @@
)

// PrepareCallHierarchy returns an array of CallHierarchyItem for a file and the position within the file.
-func PrepareCallHierarchy(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.CallHierarchyItem, error) {
+func PrepareCallHierarchy(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.CallHierarchyItem, error) {
ctx, done := event.Start(ctx, "golang.PrepareCallHierarchy")
defer done()

@@ -34,12 +34,11 @@
if err != nil {
return nil, err
}
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
- // TODO(hxjiang): replace PrepareCallHierarchy's input position with range.
- obj, err := callHierarchyFuncAtPos(pkg.TypesInfo(), pgf, astutil.RangeOf(pos, pos))
+ obj, err := callHierarchyFuncAtPos(pkg.TypesInfo(), pgf, astutil.RangeOf(start, end))
if err != nil {
return nil, err
}
@@ -47,25 +46,27 @@
if err != nil {
return nil, err
}
- rng := declLoc.Range

+ // TODO(hxjiang): right now, the returned call hierachy item is limited to
+ // a single item where the range is. With the range support gopls can return
+ // a slice of CallHierarchyItem based on the input selected range.
return []protocol.CallHierarchyItem{{
Name: obj.Name(),
Kind: protocol.Function,
Tags: []protocol.SymbolTag{},
Detail: callHierarchyItemDetail(obj, declLoc),
URI: declLoc.URI,
- Range: rng,
- SelectionRange: rng,
+ Range: declLoc.Range,
+ SelectionRange: declLoc.Range,
}}, nil
}

// IncomingCalls returns an array of CallHierarchyIncomingCall for a file and the position within the file.
-func IncomingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pos protocol.Position) ([]protocol.CallHierarchyIncomingCall, error) {
+func IncomingCalls(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.CallHierarchyIncomingCall, error) {
ctx, done := event.Start(ctx, "golang.IncomingCalls")
defer done()

- refs, err := references(ctx, snapshot, fh, pos, false)
+ refs, err := references(ctx, snapshot, fh, rng, false)
if err != nil {
if errors.Is(err, ErrNoIdentFound) || errors.Is(err, errNoObjectFound) {
return nil, nil
diff --git a/gopls/internal/golang/comment.go b/gopls/internal/golang/comment.go
index 517910e..ccc0cfe 100644
--- a/gopls/internal/golang/comment.go
+++ b/gopls/internal/golang/comment.go
@@ -60,8 +60,8 @@
// If there is no reference at pos, returns errNoCommentReference.
//
// TODO(hxjiang): simplify the error handling.
-func docLinkDefinition(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol.Location, error) {
- obj, _, err := resolveDocLink(pkg, pgf, astutil.RangeOf(pos, pos))
+func docLinkDefinition(ctx context.Context, snapshot *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) ([]protocol.Location, error) {
+ obj, _, err := resolveDocLink(pkg, pgf, astutil.RangeOf(start, end))
if err != nil {
return nil, err
}
diff --git a/gopls/internal/golang/definition.go b/gopls/internal/golang/definition.go
index 1722d9d..42b60fa 100644
--- a/gopls/internal/golang/definition.go
+++ b/gopls/internal/golang/definition.go
@@ -26,7 +26,7 @@
)

// Definition handles the textDocument/definition request for Go files.
-func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.Location, error) {
+func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.Location, error) {
ctx, done := event.Start(ctx, "golang.Definition")
defer done()

@@ -34,14 +34,14 @@
if err != nil {
return nil, err
}
- pos, err := pgf.PositionPos(position)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
- cur, _ := pgf.Cursor.FindByPos(pos, pos) // can't fail
+ cur, _ := pgf.Cursor.FindByPos(start, end) // can't fail

- // Handle the case where the cursor is in an import.
- importLocations, err := importDefinition(ctx, snapshot, pkg, pgf, pos)
+ // Handle the case where the range is in an import.
+ importLocations, err := importDefinition(ctx, snapshot, pkg, pgf, start, end)
if err != nil {
return nil, err
}
@@ -51,7 +51,7 @@

// Handle the case where the cursor is in the package name.
// We use "<= End" to accept a query immediately after the package name.
- if pgf.File != nil && astutil.NodeContainsPos(pgf.File.Name, pos) {
+ if pgf.File != nil && astutil.NodeContains(pgf.File.Name, astutil.RangeOf(start, end)) {
// If there's no package documentation, just use current file.
declFile := pgf
for _, pgf := range pkg.CompiledGoFiles() {
@@ -68,19 +68,19 @@
}

// Handle the case where the cursor is in a linkname directive.
- locations, err := linknameDefinition(ctx, snapshot, pgf.Mapper, position)
+ locations, err := linknameDefinition(ctx, snapshot, pgf.Mapper, rng)
if !errors.Is(err, ErrNoLinkname) {
return locations, err // may be success or failure
}

// Handle the case where the cursor is in an embed directive.
- locations, err = embedDefinition(pgf.Mapper, position)
+ locations, err = embedDefinition(pgf.Mapper, rng)
if !errors.Is(err, ErrNoEmbed) {
return locations, err // may be success or failure
}

// Handle the case where the cursor is in a doc link.
- locations, err = docLinkDefinition(ctx, snapshot, pkg, pgf, pos)
+ locations, err = docLinkDefinition(ctx, snapshot, pkg, pgf, start, end)
if !errors.Is(err, errNoCommentReference) {
return locations, err // may be success or failure
}
@@ -195,7 +195,7 @@
for _, decl := range pgf.File.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok &&
decl.Body == nil &&
- astutil.NodeContainsPos(decl.Name, pos) {
+ astutil.NodeContains(decl.Name, astutil.RangeOf(start, end)) {
return nonGoDefinition(ctx, snapshot, pkg, decl.Name.Name)
}
}
@@ -326,11 +326,11 @@
// import spec containing pos.
//
// If pos is not inside an import spec, it returns nil, nil.
-func importDefinition(ctx context.Context, s *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, pos token.Pos) ([]protocol.Location, error) {
+func importDefinition(ctx context.Context, s *cache.Snapshot, pkg *cache.Package, pgf *parsego.File, start, end token.Pos) ([]protocol.Location, error) {
var imp *ast.ImportSpec
for _, spec := range pgf.File.Imports {
// We use "<= End" to accept a query immediately after an ImportSpec.
- if astutil.NodeContainsPos(spec.Path, pos) {
+ if astutil.NodeContains(spec.Path, astutil.RangeOf(start, end)) {
imp = spec
}
}
diff --git a/gopls/internal/golang/embeddirective.go b/gopls/internal/golang/embeddirective.go
index 33f4f5e..8a1051a 100644
--- a/gopls/internal/golang/embeddirective.go
+++ b/gopls/internal/golang/embeddirective.go
@@ -27,8 +27,8 @@
// embedDefinition finds a file matching the embed directive at pos in the mapped file.
// If there is no embed directive at pos, returns ErrNoEmbed.
// If multiple files match the embed pattern, one is picked at random.
-func embedDefinition(m *protocol.Mapper, pos protocol.Position) ([]protocol.Location, error) {
- pattern, _ := parseEmbedDirective(m, protocol.Range{Start: pos, End: pos})
+func embedDefinition(m *protocol.Mapper, rng protocol.Range) ([]protocol.Location, error) {
+ pattern, _ := parseEmbedDirective(m, rng)
if pattern == "" {
return nil, ErrNoEmbed
}
diff --git a/gopls/internal/golang/highlight.go b/gopls/internal/golang/highlight.go
index 874db3a..c7d135c 100644
--- a/gopls/internal/golang/highlight.go
+++ b/gopls/internal/golang/highlight.go
@@ -22,7 +22,7 @@
"golang.org/x/tools/internal/fmtstr"
)

-func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) ([]protocol.DocumentHighlight, error) {
+func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.DocumentHighlight, error) {
ctx, done := event.Start(ctx, "golang.Highlight")
defer done()

@@ -33,27 +33,27 @@
return nil, fmt.Errorf("getting package for Highlight: %w", err)
}

- pos, err := pgf.PositionPos(position)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
- path, _ := goastutil.PathEnclosingInterval(pgf.File, pos, pos)
+ path, _ := goastutil.PathEnclosingInterval(pgf.File, start, end)
if len(path) == 0 {
- return nil, fmt.Errorf("no enclosing position found for %v:%v", position.Line, position.Character)
+ return nil, fmt.Errorf("no enclosing position found for %v:%v-%v:%v", rng.Start.Line, rng.Start.Character, rng.End.Line, rng.End.Character)
}
// If start == end for astutil.PathEnclosingInterval, the 1-char interval
// following start is used instead. As a result, we might not get an exact
// match so we should check the 1-char interval to the left of the passed
// in position to see if that is an exact match.
if _, ok := path[0].(*ast.Ident); !ok {
- if p, _ := goastutil.PathEnclosingInterval(pgf.File, pos-1, pos-1); p != nil {
+ if p, _ := goastutil.PathEnclosingInterval(pgf.File, start-1, end-1); p != nil {
switch p[0].(type) {
case *ast.Ident, *ast.SelectorExpr:
path = p // use preceding ident/selector
}
}
}
- result, err := highlightPath(pkg.TypesInfo(), path, pos)
+ result, err := highlightPath(pkg.TypesInfo(), path, start)
if err != nil {
return nil, err
}
diff --git a/gopls/internal/golang/implementation.go b/gopls/internal/golang/implementation.go
index d1fd590..1863949 100644
--- a/gopls/internal/golang/implementation.go
+++ b/gopls/internal/golang/implementation.go
@@ -54,11 +54,11 @@
//
// If the position denotes a method, the computation is applied to its
// receiver type and then its corresponding methods are returned.
-func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) ([]protocol.Location, error) {
+func Implementation(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, rng protocol.Range) ([]protocol.Location, error) {
ctx, done := event.Start(ctx, "golang.Implementation")
defer done()

- locs, err := implementations(ctx, snapshot, f, pp)
+ locs, err := implementations(ctx, snapshot, f, rng)
if err != nil {
return nil, err
}
@@ -67,20 +67,20 @@
return locs, nil
}

-func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.Location, error) {
+func implementations(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.Location, error) {
// Type check the current package.
pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
if err != nil {
return nil, err
}
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
- cur, _ := pgf.Cursor.FindByPos(pos, pos) // can't fail
+ cur, _ := pgf.Cursor.FindByPos(start, end) // can't fail

// Find implementations based on func signatures.
- if locs, err := implFuncs(pkg, cur, pos); err != errNotHandled {
+ if locs, err := implFuncs(pkg, cur, start, end); err != errNotHandled {
return locs, err
}

@@ -890,7 +890,7 @@
//
// implFuncs returns errNotHandled to indicate that we should try the
// regular method-sets algorithm.
-func implFuncs(pkg *cache.Package, curSel inspector.Cursor, pos token.Pos) ([]protocol.Location, error) {
+func implFuncs(pkg *cache.Package, curSel inspector.Cursor, start, end token.Pos) ([]protocol.Location, error) {
info := pkg.TypesInfo()
if info.Types == nil || info.Defs == nil || info.Uses == nil {
panic("one of info.Types, .Defs or .Uses is nil")
@@ -918,7 +918,7 @@
) {
switch n := cur.Node().(type) {
case *ast.FuncDecl, *ast.FuncLit:
- if inToken(n.Pos(), "func", pos) {
+ if inToken(n.Pos(), "func", start, end) {
// Case 1: concrete function declaration.
// Report uses of corresponding function types.
switch n := n.(type) {
@@ -930,14 +930,14 @@
}

case *ast.FuncType:
- if n.Func.IsValid() && inToken(n.Func, "func", pos) && !beneathFuncDef(cur) {
+ if n.Func.IsValid() && inToken(n.Func, "func", start, end) && !beneathFuncDef(cur) {
// Case 2a: function type.
// Report declarations of corresponding concrete functions.
return funcDefs(pkg, info.TypeOf(n))
}

case *ast.CallExpr:
- if inToken(n.Lparen, "(", pos) {
+ if inToken(n.Lparen, "(", start, end) {
t := dynamicFuncCallType(info, n)
if t == nil {
return nil, fmt.Errorf("not a dynamic function call")
@@ -1059,8 +1059,8 @@
return nil
}

-// inToken reports whether pos is within the token of
+// inToken reports whether range is within the token of
// the specified position and string.
-func inToken(tokPos token.Pos, tokStr string, pos token.Pos) bool {
- return tokPos <= pos && pos <= tokPos+token.Pos(len(tokStr))
+func inToken(tokPos token.Pos, tokStr string, start, end token.Pos) bool {
+ return tokPos <= start && end <= tokPos+token.Pos(len(tokStr))
}
diff --git a/gopls/internal/golang/inline_all.go b/gopls/internal/golang/inline_all.go
index 59109cd..9b8697f 100644
--- a/gopls/internal/golang/inline_all.go
+++ b/gopls/internal/golang/inline_all.go
@@ -51,7 +51,7 @@
// Collect references.
var refs []protocol.Location
{
- funcPos, err := pgf.Mapper.PosPosition(pgf.Tok, origDecl.Name.NamePos)
+ funcRng, err := pgf.Mapper.PosRange(pgf.Tok, origDecl.Name.NamePos, origDecl.Name.NamePos)
if err != nil {
return nil, err
}
@@ -59,7 +59,7 @@
if err != nil {
return nil, err
}
- refs, err = References(ctx, snapshot, fh, funcPos, false)
+ refs, err = References(ctx, snapshot, fh, funcRng, false)
if err != nil {
return nil, fmt.Errorf("finding references to rewrite: %v", err)
}
diff --git a/gopls/internal/golang/linkname.go b/gopls/internal/golang/linkname.go
index f125092..1156d15 100644
--- a/gopls/internal/golang/linkname.go
+++ b/gopls/internal/golang/linkname.go
@@ -24,8 +24,8 @@

// linknameDefinition finds the definition of the linkname directive in m at pos.
// If there is no linkname directive at pos, returns ErrNoLinkname.
-func linknameDefinition(ctx context.Context, snapshot *cache.Snapshot, m *protocol.Mapper, from protocol.Position) ([]protocol.Location, error) {
- pkgPath, name, _ := parseLinkname(m, protocol.Range{Start: from, End: from})
+func linknameDefinition(ctx context.Context, snapshot *cache.Snapshot, m *protocol.Mapper, from protocol.Range) ([]protocol.Location, error) {
+ pkgPath, name, _ := parseLinkname(m, from)
if pkgPath == "" {
return nil, ErrNoLinkname
}
diff --git a/gopls/internal/golang/references.go b/gopls/internal/golang/references.go
index 140e20c..af226e8 100644
--- a/gopls/internal/golang/references.go
+++ b/gopls/internal/golang/references.go
@@ -42,8 +42,8 @@
// References returns a list of all references (sorted with
// definitions before uses) to the object denoted by the identifier at
// the given file/position, searching the entire workspace.
-func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position, includeDeclaration bool) ([]protocol.Location, error) {
- references, err := references(ctx, snapshot, fh, pp, includeDeclaration)
+func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range, includeDeclaration bool) ([]protocol.Location, error) {
+ references, err := references(ctx, snapshot, fh, rng, includeDeclaration)
if err != nil {
return nil, err
}
@@ -65,12 +65,12 @@
// references returns a list of all references (sorted with
// definitions before uses) to the object denoted by the identifier at
// the given file/position, searching the entire workspace.
-func references(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, includeDeclaration bool) ([]reference, error) {
+func references(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, rng protocol.Range, includeDeclaration bool) ([]reference, error) {
ctx, done := event.Start(ctx, "golang.references")
defer done()

// Is the cursor within the package name declaration?
- _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
+ _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, rng)
if err != nil {
return nil, err
}
@@ -79,7 +79,7 @@
if inPackageName {
refs, err = packageReferences(ctx, snapshot, f.URI())
} else {
- refs, err = ordinaryReferences(ctx, snapshot, f.URI(), pp)
+ refs, err = ordinaryReferences(ctx, snapshot, f.URI(), rng)
}
if err != nil {
return nil, err
@@ -215,7 +215,7 @@
}

// ordinaryReferences computes references for all ordinary objects (not package declarations).
-func ordinaryReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, pp protocol.Position) ([]reference, error) {
+func ordinaryReferences(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, rng protocol.Range) ([]reference, error) {
// Strategy: use the reference information computed by the
// type checker to find the declaration. First type-check this
// package to find the declaration, then type check the
@@ -234,11 +234,11 @@

// Find the selected object (declaration or reference).
// For struct{T}, we choose the field (Def) over the type (Use).
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
- cur, _ := pgf.Cursor.FindByPos(pos, pos) // can't fail
+ cur, _ := pgf.Cursor.FindByPos(start, end) // can't fail

candidates, err := objectsAt(pkg.TypesInfo(), cur)
if err != nil {
diff --git a/gopls/internal/golang/rename.go b/gopls/internal/golang/rename.go
index c9ca2c5..640a49f 100644
--- a/gopls/internal/golang/rename.go
+++ b/gopls/internal/golang/rename.go
@@ -106,12 +106,12 @@
// The returned usererr is intended to be displayed to the user to explain why
// the prepare fails. Probably we could eliminate the redundancy in returning
// two errors, but for now this is done defensively.
-func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position) (_ *PrepareItem, usererr, err error) {
+func PrepareRename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, rng protocol.Range) (_ *PrepareItem, usererr, err error) {
ctx, done := event.Start(ctx, "golang.PrepareRename")
defer done()

// Is the cursor within the package name declaration?
- if pgf, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp); err != nil {
+ if pgf, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, rng); err != nil {
return nil, err, err
} else if inPackageName {
item, err := prepareRenamePackageName(ctx, snapshot, pgf)
@@ -131,15 +131,15 @@
if err != nil {
return nil, nil, err
}
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, nil, err
}
- cur, _ := pgf.Cursor.FindByPos(pos, pos) // can't fail
+ cur, _ := pgf.Cursor.FindByPos(start, end) // can't fail

// Check if we're in a 'func' keyword. If so, we hijack the renaming to
// change the function signature.
- if item, err := prepareRenameFuncSignature(pgf, pos, cur); err != nil {
+ if item, err := prepareRenameFuncSignature(pgf, start, end, cur); err != nil {
return nil, nil, err
} else if item != nil {
return item, nil, nil
@@ -151,7 +151,7 @@
// objectsAt will have returned an error in this case.
// TODO(adonovan): move this logic into objectsAt.
var ok bool
- cur, ok = docCommentPosToIdent(pgf, pos, cur)
+ cur, ok = docCommentPosToIdent(pgf, start, end, cur)
if !ok {
return nil, nil, err
}
@@ -170,16 +170,16 @@
if err := checkRenamable(obj, node); err != nil {
return nil, nil, err
}
- rng, err := pgf.NodeRange(node)
+ nodeRng, err := pgf.NodeRange(node)
if err != nil {
return nil, nil, err
}
if _, isImport := node.(*ast.ImportSpec); isImport {
// We're not really renaming the import path.
- rng.End = rng.Start
+ nodeRng.End = nodeRng.Start
}
return &PrepareItem{
- Range: rng,
+ Range: nodeRng,
Text: obj.Name(),
}, nil, nil
}
@@ -228,8 +228,8 @@
//
// The resulting text is the signature of the function, which may be edited to
// the new signature.
-func prepareRenameFuncSignature(pgf *parsego.File, pos token.Pos, cursor inspector.Cursor) (*PrepareItem, error) {
- fdecl := funcKeywordDecl(pos, cursor)
+func prepareRenameFuncSignature(pgf *parsego.File, start, end token.Pos, cursor inspector.Cursor) (*PrepareItem, error) {
+ fdecl := funcKeywordDecl(start, end, cursor)
if fdecl == nil {
return nil, nil
}
@@ -283,8 +283,8 @@

// renameFuncSignature computes and applies the effective change signature
// operation resulting from a 'renamed' (=rewritten) signature.
-func renameFuncSignature(ctx context.Context, pkg *cache.Package, pgf *parsego.File, pos token.Pos, snapshot *cache.Snapshot, cursor inspector.Cursor, f file.Handle, pp protocol.Position, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, error) {
- fdecl := funcKeywordDecl(pos, cursor)
+func renameFuncSignature(ctx context.Context, pkg *cache.Package, pgf *parsego.File, start, end token.Pos, snapshot *cache.Snapshot, cursor inspector.Cursor, f file.Handle, rng protocol.Range, newName string) (map[protocol.DocumentURI][]protocol.TextEdit, error) {
+ fdecl := funcKeywordDecl(start, end, cursor)
if fdecl == nil {
return nil, nil
}
@@ -343,11 +343,11 @@
newParams = append(newParams, info.idx)
}

- rng, err := pgf.PosRange(ftyp.Func, ftyp.Func)
+ funcRng, err := pgf.PosRange(ftyp.Func, ftyp.Func)
if err != nil {
return nil, err
}
- changes, err := ChangeSignature(ctx, snapshot, pkg, pgf, rng, newParams)
+ changes, err := ChangeSignature(ctx, snapshot, pkg, pgf, funcRng, newParams)
if err != nil {
return nil, err
}
@@ -360,13 +360,13 @@

// funcKeywordDecl returns the FuncDecl for which pos is in the 'func' keyword,
// if any.
-func funcKeywordDecl(pos token.Pos, cursor inspector.Cursor) *ast.FuncDecl {
+func funcKeywordDecl(start, end token.Pos, cursor inspector.Cursor) *ast.FuncDecl {
fdecl, _ := cursorutil.FirstEnclosing[*ast.FuncDecl](cursor)
if fdecl == nil {
return nil
}
ftyp := fdecl.Type
- if pos < ftyp.Func || pos > ftyp.Func+token.Pos(len("func")) { // tolerate renaming immediately after 'func'
+ if start < ftyp.Func || end > ftyp.Func+token.Pos(len("func")) { // tolerate renaming immediately after 'func'
return nil
}
return fdecl
@@ -410,7 +410,7 @@

// Rename returns a map of TextEdits for each file modified when renaming a
// given identifier within a package.
-func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, pp protocol.Position, newName string) ([]protocol.DocumentChange, error) {
+func Rename(ctx context.Context, snapshot *cache.Snapshot, f file.Handle, rng protocol.Range, newName string) ([]protocol.DocumentChange, error) {
ctx, done := event.Start(ctx, "golang.Rename")
defer done()

@@ -418,24 +418,24 @@
if err != nil {
return nil, err
}
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}

- cur, ok := pgf.Cursor.FindByPos(pos, pos)
+ cur, ok := pgf.Cursor.FindByPos(start, end)
if !ok {
return nil, fmt.Errorf("can't find cursor for selection")
}

- if edits, err := renameFuncSignature(ctx, pkg, pgf, pos, snapshot, cur, f, pp, newName); err != nil {
+ if edits, err := renameFuncSignature(ctx, pkg, pgf, start, end, snapshot, cur, f, rng, newName); err != nil {
return nil, err
} else if edits != nil {
return editsToDocChanges(ctx, snapshot, edits)
}

// Cursor within package name declaration?
- _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, pp)
+ _, inPackageName, err := parsePackageNameDecl(ctx, snapshot, f, rng)
if err != nil {
return nil, err
}
@@ -463,7 +463,7 @@
if !isValidIdentifier(newName) {
return nil, fmt.Errorf("invalid identifier to rename: %q", newName)
}
- editMap, err = renameOrdinary(ctx, snapshot, f.URI(), pp, newName)
+ editMap, err = renameOrdinary(ctx, snapshot, f.URI(), rng, newName)
if err != nil {
return nil, err
}
@@ -561,7 +561,7 @@
}

// renameOrdinary renames an ordinary (non-package) name throughout the workspace.
-func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, pp protocol.Position, newName string) (map[protocol.DocumentURI][]diff.Edit, error) {
+func renameOrdinary(ctx context.Context, snapshot *cache.Snapshot, uri protocol.DocumentURI, rng protocol.Range, newName string) (map[protocol.DocumentURI][]diff.Edit, error) {
// Type-check the referring package and locate the object(s).
//
// Unlike NarrowestPackageForFile, this operation prefers the
@@ -586,11 +586,11 @@
if err != nil {
return nil, err // "can't happen"
}
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
- cur, _ := pgf.Cursor.FindByPos(pos, pos) // cannot fail
+ cur, _ := pgf.Cursor.FindByPos(start, end) // cannot fail

targets, err := objectsAt(pkg.TypesInfo(), cur)
if err != nil {
@@ -598,7 +598,7 @@
// objectsAt will have returned an error in this case.
// TODO(adonovan): move this logic into objectsAt.
var ok bool
- cur, ok = docCommentPosToIdent(pgf, pos, cur)
+ cur, ok = docCommentPosToIdent(pgf, start, end, cur)
if !ok {
return nil, err
}
@@ -631,7 +631,7 @@
if err != nil {
return nil, err
}
- return renameOrdinary(ctx, snapshot, loc.URI, loc.Range.Start, newName)
+ return renameOrdinary(ctx, snapshot, loc.URI, loc.Range, newName)
}
}
}
@@ -1752,31 +1752,31 @@
// docCommentPosToIdent returns a cursor for the identifier whose doc
// comment contains pos, if any. The pos must be within an occurrence
// of the identifier's name, otherwise it returns zero.
-func docCommentPosToIdent(pgf *parsego.File, pos token.Pos, cur inspector.Cursor) (inspector.Cursor, bool) {
+func docCommentPosToIdent(pgf *parsego.File, start, end token.Pos, cur inspector.Cursor) (inspector.Cursor, bool) {
for curId := range cur.Preorder((*ast.Ident)(nil)) {
id := curId.Node().(*ast.Ident)
- if pos > id.Pos() {
+ if start > id.Pos() {
continue // Doc comments are not located after an ident.
}
doc := docComment(pgf, curId)
- if doc == nil || !(doc.Pos() <= pos && pos < doc.End()) {
+ if doc == nil || !(doc.Pos() <= start && end < doc.End()) {
continue
}

docRegexp := regexp.MustCompile(`\b` + id.Name + `\b`)
for _, comment := range doc.List {
- if isDirective(comment.Text) || !(comment.Pos() <= pos && pos < comment.End()) {
+ if isDirective(comment.Text) || !(comment.Pos() <= start && end < comment.End()) {
continue
}
- start := comment.Pos()
+ commentStart := comment.Pos()
text, err := pgf.NodeText(comment)
if err != nil {
return inspector.Cursor{}, false
}
for _, locs := range docRegexp.FindAllIndex(text, -1) {
- matchStart := start + token.Pos(locs[0])
- matchEnd := start + token.Pos(locs[1])
- if matchStart <= pos && pos <= matchEnd {
+ matchStart := commentStart + token.Pos(locs[0])
+ matchEnd := commentStart + token.Pos(locs[1])
+ if matchStart <= start && end <= matchEnd {
return curId, true
}
}
@@ -1814,15 +1814,15 @@
// whether the position ppos lies within it.
//
// Note: also used by references.
-func parsePackageNameDecl(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, ppos protocol.Position) (*parsego.File, bool, error) {
+func parsePackageNameDecl(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) (*parsego.File, bool, error) {
pgf, err := snapshot.ParseGo(ctx, fh, parsego.Header)
if err != nil {
return nil, false, err
}
// Careful: because we used parsego.Header,
// pgf.Pos(ppos) may be beyond EOF => (0, err).
- pos, _ := pgf.PositionPos(ppos)
- return pgf, astutil.NodeContainsPos(pgf.File.Name, pos), nil
+ start, end, _ := pgf.RangePos(rng)
+ return pgf, astutil.NodeContains(pgf.File.Name, astutil.RangeOf(start, end)), nil
}

// posEdit returns an edit to replace the (start, end) range of tf with 'new'.
diff --git a/gopls/internal/golang/signature_help.go b/gopls/internal/golang/signature_help.go
index 260bd0f..3c97112 100644
--- a/gopls/internal/golang/signature_help.go
+++ b/gopls/internal/golang/signature_help.go
@@ -23,7 +23,7 @@

// SignatureHelp returns information about the signature of the innermost
// function call enclosing the position, or nil if there is none.
-func SignatureHelp(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, params *protocol.SignatureHelpParams) (*protocol.SignatureInformation, error) {
+func SignatureHelp(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range, context *protocol.SignatureHelpContext) (*protocol.SignatureInformation, error) {
ctx, done := event.Start(ctx, "golang.SignatureHelp")
defer done()

@@ -33,13 +33,13 @@
if err != nil {
return nil, fmt.Errorf("getting file for SignatureHelp: %w", err)
}
- pos, err := pgf.PositionPos(params.Position)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}
// Find a call expression surrounding the query position.
var callExpr *ast.CallExpr
- path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
+ path, _ := astutil.PathEnclosingInterval(pgf.File, start, end)
if path == nil {
return nil, fmt.Errorf("cannot find node enclosing position")
}
@@ -66,7 +66,7 @@
}
case *ast.CallExpr:
// Beware: the ')' may be missing.
- if node.Lparen <= pos && pos <= node.Rparen {
+ if node.Lparen <= start && end <= node.Rparen {
callExpr = node
fnval = callExpr.Fun
break loop
@@ -81,7 +81,7 @@
// golang/go#43397: don't offer signature help when the user is typing
// in a string literal unless it was manually invoked or help is already active.
if node.Kind == token.STRING &&
- (params.Context == nil || (params.Context.TriggerKind != protocol.SigInvoked && !params.Context.IsRetrigger)) {
+ (context == nil || (context.TriggerKind != protocol.SigInvoked && !context.IsRetrigger)) {
return nil, nil
}
}
@@ -128,7 +128,7 @@
if err != nil {
return nil, err
}
- return signatureInformation(s, snapshot.Options(), pos, callExpr)
+ return signatureInformation(s, snapshot.Options(), start, end, callExpr)
}

mq := MetadataQualifierForFile(snapshot, pgf.File, pkg.Metadata())
@@ -153,10 +153,10 @@
return nil, err
}
s.name = name
- return signatureInformation(s, snapshot.Options(), pos, callExpr)
+ return signatureInformation(s, snapshot.Options(), start, end, callExpr)
}

-func signatureInformation(sig *signature, options *settings.Options, pos token.Pos, call *ast.CallExpr) (*protocol.SignatureInformation, error) {
+func signatureInformation(sig *signature, options *settings.Options, start, end token.Pos, call *ast.CallExpr) (*protocol.SignatureInformation, error) {
paramInfo := make([]protocol.ParameterInformation, 0, len(sig.params))
for _, p := range sig.params {
paramInfo = append(paramInfo, protocol.ParameterInformation{Label: p})
@@ -165,13 +165,13 @@
Label: sig.name + sig.Format(),
Documentation: stringToSigInfoDocumentation(sig.doc, options),
Parameters: paramInfo,
- ActiveParameter: activeParameter(sig, pos, call),
+ ActiveParameter: activeParameter(sig, start, end, call),
}, nil
}

// activeParameter returns a pointer to a variable containing
// the index of the active parameter (if known), or nil otherwise.
-func activeParameter(sig *signature, pos token.Pos, call *ast.CallExpr) *uint32 {
+func activeParameter(sig *signature, start, end token.Pos, call *ast.CallExpr) *uint32 {
if call == nil {
return nil
}
@@ -180,13 +180,13 @@
return nil
}
// Check if the position is even in the range of the arguments.
- if !(call.Lparen < pos && pos <= call.Rparen) {
+ if !(call.Lparen < start && end <= call.Rparen) {
return nil
}

var activeParam uint32
for _, arg := range call.Args {
- if pos <= arg.End() {
+ if end <= arg.End() {
break
}
// Don't advance the active parameter for the last parameter of a variadic function.
diff --git a/gopls/internal/golang/type_hierarchy.go b/gopls/internal/golang/type_hierarchy.go
index b1448ad..f2e72b7 100644
--- a/gopls/internal/golang/type_hierarchy.go
+++ b/gopls/internal/golang/type_hierarchy.go
@@ -33,18 +33,18 @@
// - fix pkg=command-line-arguments problem with query initiated at "error" in builtins.go

// PrepareTypeHierarchy returns the TypeHierarchyItems for the types at the selected position.
-func PrepareTypeHierarchy(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp protocol.Position) ([]protocol.TypeHierarchyItem, error) {
+func PrepareTypeHierarchy(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.TypeHierarchyItem, error) {
pkg, pgf, err := NarrowestPackageForFile(ctx, snapshot, fh.URI())
if err != nil {
return nil, err
}
- pos, err := pgf.PositionPos(pp)
+ start, end, err := pgf.RangePos(rng)
if err != nil {
return nil, err
}

// For now, we require that the selection be a type name.
- cur, ok := pgf.Cursor.FindByPos(pos, pos)
+ cur, ok := pgf.Cursor.FindByPos(start, end)
if !ok {
return nil, fmt.Errorf("no enclosing syntax") // can't happen
}
diff --git a/gopls/internal/mcp/references.go b/gopls/internal/mcp/references.go
index a95c1f7..d49bce1 100644
--- a/gopls/internal/mcp/references.go
+++ b/gopls/internal/mcp/references.go
@@ -28,7 +28,7 @@
}
defer release()
pos := params.Location.Range.Start
- refs, err := golang.References(ctx, snapshot, fh, pos, true)
+ refs, err := golang.References(ctx, snapshot, fh, protocol.Range{Start: pos, End: pos}, true)
if err != nil {
return nil, nil, err
}
diff --git a/gopls/internal/mcp/rename_symbol.go b/gopls/internal/mcp/rename_symbol.go
index d94021c..0534e29 100644
--- a/gopls/internal/mcp/rename_symbol.go
+++ b/gopls/internal/mcp/rename_symbol.go
@@ -38,7 +38,7 @@
if err != nil {
return nil, nil, err
}
- changes, err := golang.Rename(ctx, snapshot, fh, loc.Range.Start, params.NewName)
+ changes, err := golang.Rename(ctx, snapshot, fh, loc.Range, params.NewName)
if err != nil {
return nil, nil, err
}
diff --git a/gopls/internal/mcp/symbol_references.go b/gopls/internal/mcp/symbol_references.go
index d38cb70..e9966e0 100644
--- a/gopls/internal/mcp/symbol_references.go
+++ b/gopls/internal/mcp/symbol_references.go
@@ -49,7 +49,7 @@
if err != nil {
return nil, nil, err
}
- refs, err := golang.References(ctx, snapshot, declFH, loc.Range.Start, true)
+ refs, err := golang.References(ctx, snapshot, declFH, loc.Range, true)
if err != nil {
return nil, nil, err
}
diff --git a/gopls/internal/mod/hover.go b/gopls/internal/mod/hover.go
index 26cd5e2..d93b373 100644
--- a/gopls/internal/mod/hover.go
+++ b/gopls/internal/mod/hover.go
@@ -25,7 +25,7 @@
"golang.org/x/tools/internal/event"
)

-func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) {
+func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) (*protocol.Hover, error) {
// We only provide hover information for the view's go.mod files.
if !slices.Contains(snapshot.View().ModFiles(), fh.URI()) {
return nil, nil
@@ -39,22 +39,22 @@
if err != nil {
return nil, fmt.Errorf("getting modfile handle: %w", err)
}
- offset, err := pm.Mapper.PositionOffset(position)
+ startOffset, endOffset, err := pm.Mapper.RangeOffsets(rng)
if err != nil {
return nil, fmt.Errorf("computing cursor position: %w", err)
}

// If the cursor position is on a module statement
- if hover, ok := hoverOnModuleStatement(ctx, pm, offset, snapshot, fh); ok {
+ if hover, ok := hoverOnModuleStatement(ctx, pm, startOffset, endOffset, snapshot, fh); ok {
return hover, nil
}
- return hoverOnRequireStatement(ctx, pm, offset, snapshot, fh)
+ return hoverOnRequireStatement(ctx, pm, startOffset, endOffset, snapshot, fh)
}

-func hoverOnRequireStatement(ctx context.Context, pm *cache.ParsedModule, offset int, snapshot *cache.Snapshot, fh file.Handle) (*protocol.Hover, error) {
+func hoverOnRequireStatement(ctx context.Context, pm *cache.ParsedModule, startOffset, endOffset int, snapshot *cache.Snapshot, fh file.Handle) (*protocol.Hover, error) {
// Confirm that the cursor is at the position of a require statement.
var req *modfile.Require
- var startOffset, endOffset int
+ var depStartOffset, depEndOffset int
for _, r := range pm.File.Require {
dep := []byte(r.Mod.Path)
s, e := r.Syntax.Start.Byte, r.Syntax.End.Byte
@@ -64,8 +64,8 @@
}
// Shift the start position to the location of the
// dependency within the require statement.
- startOffset, endOffset = s+i, e
- if startOffset <= offset && offset <= endOffset {
+ depStartOffset, depEndOffset = s+i, e
+ if depStartOffset <= startOffset && endOffset <= depEndOffset {
req = r
break
}
@@ -122,12 +122,12 @@
}, nil
}

-func hoverOnModuleStatement(ctx context.Context, pm *cache.ParsedModule, offset int, snapshot *cache.Snapshot, fh file.Handle) (*protocol.Hover, bool) {
+func hoverOnModuleStatement(ctx context.Context, pm *cache.ParsedModule, startOffset, endOffset int, snapshot *cache.Snapshot, fh file.Handle) (*protocol.Hover, bool) {
module := pm.File.Module
if module == nil {
return nil, false // no module stmt
}
- if offset < module.Syntax.Start.Byte || offset > module.Syntax.End.Byte {
+ if endOffset < module.Syntax.Start.Byte || startOffset > module.Syntax.End.Byte {
return nil, false // cursor not in module stmt
}

diff --git a/gopls/internal/protocol/generate/output.go b/gopls/internal/protocol/generate/output.go
index 3f2206b..963aed8 100644
--- a/gopls/internal/protocol/generate/output.go
+++ b/gopls/internal/protocol/generate/output.go
@@ -119,14 +119,23 @@
out.WriteString("\t\t\treturn nil, true, fmt.Errorf(\"%%w: %%s\", jsonrpc2.ErrParse, err)\n\t\t}\n")
p = ", &params"

- // If the parameter extends the TextDocumentPositionParam, verify the
- // position is within the provided range.
+ // Ensure consistency between Range and Position. If the client provides
+ // only a Position, synthesize a zero-width Range at that location.
+ //
+ // Crucially, we do not clear the Position field. Since this request is
+ // forwarded, the downstream receiver (gopls) will re-validate that the
+ // Position lies within the Range.
if extends(nm, "TextDocumentPositionParams") {
- out.WriteString(` if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ out.WriteString(` if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %%v is outside the provided range %%v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
`)
-
}

}
@@ -314,6 +323,11 @@
json = fmt.Sprintf(" `json:\"%s,omitempty\"`", p.Name)
}
generateDoc(out, p.Documentation)
+ if docs := appendTypePropDocComments[name]; docs != nil {
+ if doc, ok := docs[p.Name]; ok {
+ out.WriteString(doc)
+ }
+ }
if star {
fmt.Fprintf(out, "\t%s *%s %s\n", goName(p.Name), tp, json)
} else {
diff --git a/gopls/internal/protocol/generate/tables.go b/gopls/internal/protocol/generate/tables.go
index e8b965a..1aa12d0 100644
--- a/gopls/internal/protocol/generate/tables.go
+++ b/gopls/internal/protocol/generate/tables.go
@@ -302,6 +302,14 @@
// properties (e.g. edits, commands) it's waiting for.`,
}

+// prependMethodDocComments specifies doc comments that will be prepend to
+// an LSP type's properties existing doc comments.
+var appendTypePropDocComments = map[string]map[string]string{
+ "TextDocumentPositionParams": {"position": ` //
+ // Deprecated: gopls should use [TextDocumentPositionParams.Range] instead.
+`},
+}
+
// appendTypeProp specifies block of code (typically properties with doc comment)
// that will be append to a struct.
var appendTypeProp = map[string]string{
diff --git a/gopls/internal/protocol/mapper.go b/gopls/internal/protocol/mapper.go
index 6a92a2c..c7977cf 100644
--- a/gopls/internal/protocol/mapper.go
+++ b/gopls/internal/protocol/mapper.go
@@ -362,6 +362,7 @@
func LocationTextDocumentPositionParams(loc Location) TextDocumentPositionParams {
return TextDocumentPositionParams{
TextDocument: TextDocumentIdentifier{URI: loc.URI},
- Position: loc.Range.Start,
+ Range: loc.Range,
+ Position: loc.Range.Start, // not used
}
}
diff --git a/gopls/internal/protocol/protocol.go b/gopls/internal/protocol/protocol.go
index d92e4c5..d809bb4 100644
--- a/gopls/internal/protocol/protocol.go
+++ b/gopls/internal/protocol/protocol.go
@@ -10,6 +10,7 @@
"encoding/json"
"fmt"
"io"
+ "log"

"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/jsonrpc2"
@@ -62,6 +63,10 @@
}

func (c clientConn) Call(ctx context.Context, method string, params any, result any) error {
+ if method == "textDocument/completion" {
+ log.Printf("params 1 = %v", params)
+ }
+
id, err := c.conn.Call(ctx, method, params, result)
if ctx.Err() != nil {
cancelCall(ctx, c, id)
diff --git a/gopls/internal/protocol/tsprotocol.go b/gopls/internal/protocol/tsprotocol.go
index 10aa2a4..644d3c4 100644
--- a/gopls/internal/protocol/tsprotocol.go
+++ b/gopls/internal/protocol/tsprotocol.go
@@ -5624,6 +5624,8 @@
// The text document.
TextDocument TextDocumentIdentifier `json:"textDocument"`
// The position inside the text document.
+ //
+ // Deprecated: gopls should use [TextDocumentPositionParams.Range] instead.
Position Position `json:"position"`
// Range is an optional field representing the user's text selection in the document.
// If provided, the Position must be contained within this range.
diff --git a/gopls/internal/protocol/tsserver.go b/gopls/internal/protocol/tsserver.go
index cbf6147..241abaf 100644
--- a/gopls/internal/protocol/tsserver.go
+++ b/gopls/internal/protocol/tsserver.go
@@ -387,9 +387,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Completion(ctx, &params)
if err != nil {
return nil, true, err
@@ -401,9 +407,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Declaration(ctx, &params)
if err != nil {
return nil, true, err
@@ -415,9 +427,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Definition(ctx, &params)
if err != nil {
return nil, true, err
@@ -483,9 +501,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.DocumentHighlight(ctx, &params)
if err != nil {
return nil, true, err
@@ -541,9 +565,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Hover(ctx, &params)
if err != nil {
return nil, true, err
@@ -555,9 +585,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Implementation(ctx, &params)
if err != nil {
return nil, true, err
@@ -580,9 +616,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.InlineCompletion(ctx, &params)
if err != nil {
return nil, true, err
@@ -605,9 +647,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.LinkedEditingRange(ctx, &params)
if err != nil {
return nil, true, err
@@ -619,9 +667,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Moniker(ctx, &params)
if err != nil {
return nil, true, err
@@ -644,9 +698,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.PrepareCallHierarchy(ctx, &params)
if err != nil {
return nil, true, err
@@ -658,9 +718,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.PrepareRename(ctx, &params)
if err != nil {
return nil, true, err
@@ -672,9 +738,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.PrepareTypeHierarchy(ctx, &params)
if err != nil {
return nil, true, err
@@ -708,9 +780,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.References(ctx, &params)
if err != nil {
return nil, true, err
@@ -722,9 +800,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.Rename(ctx, &params)
if err != nil {
return nil, true, err
@@ -780,9 +864,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.SignatureHelp(ctx, &params)
if err != nil {
return nil, true, err
@@ -794,9 +884,15 @@
if err := UnmarshalJSON(raw, &params); err != nil {
return nil, true, fmt.Errorf("%w: %s", jsonrpc2.ErrParse, err)
}
- if !params.Range.Empty() && !params.Range.Contains(params.Position) {
+ if params.Range != (Range{}) && !params.Range.Contains(params.Position) {
return nil, true, fmt.Errorf("position %v is outside the provided range %v.", params.Position, params.Range)
}
+ if params.Range == (Range{}) {
+ params.Range = Range{
+ Start: params.Position,
+ End: params.Position,
+ }
+ }
resp, err := server.TypeDefinition(ctx, &params)
if err != nil {
return nil, true, err
diff --git a/gopls/internal/server/call_hierarchy.go b/gopls/internal/server/call_hierarchy.go
index dc8cd4c..ee521ce 100644
--- a/gopls/internal/server/call_hierarchy.go
+++ b/gopls/internal/server/call_hierarchy.go
@@ -24,7 +24,7 @@
defer release()
switch snapshot.FileKind(fh) {
case file.Go:
- return golang.PrepareCallHierarchy(ctx, snapshot, fh, params.Position)
+ return golang.PrepareCallHierarchy(ctx, snapshot, fh, params.Range)
}
return nil, nil // empty result
}
@@ -40,7 +40,7 @@
defer release()
switch snapshot.FileKind(fh) {
case file.Go:
- return golang.IncomingCalls(ctx, snapshot, fh, params.Item.Range.Start)
+ return golang.IncomingCalls(ctx, snapshot, fh, params.Item.Range)
}
return nil, nil // empty result
}
diff --git a/gopls/internal/server/completion.go b/gopls/internal/server/completion.go
index 8ff7921..53d8527 100644
--- a/gopls/internal/server/completion.go
+++ b/gopls/internal/server/completion.go
@@ -36,29 +36,34 @@
}
defer release()

+ if params.Range.Start != params.Range.End {
+ return nil, fmt.Errorf("textDocument/completion request only applicable for position")
+ }
+ pos := params.Range.Start
+
var candidates []completion.CompletionItem
var surrounding *completion.Selection
switch snapshot.FileKind(fh) {
case file.Go:
- candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context)
+ candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, pos, params.Context)
case file.Mod:
candidates, surrounding = nil, nil
case file.Work:
- cl, err := work.Completion(ctx, snapshot, fh, params.Position)
+ cl, err := work.Completion(ctx, snapshot, fh, pos)
if err != nil {
break
}
return cl, nil
case file.Tmpl:
var cl *protocol.CompletionList
- cl, err = template.Completion(ctx, snapshot, fh, params.Position, params.Context)
+ cl, err = template.Completion(ctx, snapshot, fh, pos, params.Context)
if err != nil {
break // use common error handling, candidates==nil
}
return cl, nil
}
if err != nil {
- event.Error(ctx, "no completions found", err, label.Position.Of(params.Position))
+ event.Error(ctx, "no completions found", err, label.Position.Of(pos))
}
if candidates == nil || surrounding == nil {
complEmpty.Inc()
diff --git a/gopls/internal/server/definition.go b/gopls/internal/server/definition.go
index 964fa40..bc02325 100644
--- a/gopls/internal/server/definition.go
+++ b/gopls/internal/server/definition.go
@@ -35,11 +35,11 @@
defer release()
switch kind := snapshot.FileKind(fh); kind {
case file.Tmpl:
- return template.Definition(snapshot, fh, params.Position)
+ return template.Definition(snapshot, fh, params.Range)
case file.Go:
- return golang.Definition(ctx, snapshot, fh, params.Position)
+ return golang.Definition(ctx, snapshot, fh, params.Range)
case file.Asm:
- return goasm.Definition(ctx, snapshot, fh, params.Position)
+ return goasm.Definition(ctx, snapshot, fh, params.Range)
default:
return nil, fmt.Errorf("can't find definitions for file type %s", kind)
}
@@ -55,22 +55,10 @@
return nil, err
}

- var rng protocol.Range
- if params.Range == (protocol.Range{}) {
- // No selection range was provided.
- // Default to an empty range at the position.
- rng = protocol.Range{
- Start: params.Position,
- End: params.Position,
- }
- } else {
- rng = params.Range
- }
-
defer release()
switch kind := snapshot.FileKind(fh); kind {
case file.Go:
- return golang.TypeDefinition(ctx, snapshot, fh, rng)
+ return golang.TypeDefinition(ctx, snapshot, fh, params.Range)
default:
return nil, fmt.Errorf("can't find type definitions for file type %s", kind)
}
diff --git a/gopls/internal/server/highlight.go b/gopls/internal/server/highlight.go
index 6ff73d8..83b2660 100644
--- a/gopls/internal/server/highlight.go
+++ b/gopls/internal/server/highlight.go
@@ -27,9 +27,9 @@

switch snapshot.FileKind(fh) {
case file.Tmpl:
- return template.Highlight(ctx, snapshot, fh, params.Position)
+ return template.Highlight(ctx, snapshot, fh, params.Range)
case file.Go:
- rngs, err := golang.Highlight(ctx, snapshot, fh, params.Position)
+ rngs, err := golang.Highlight(ctx, snapshot, fh, params.Range)
if err != nil {
event.Error(ctx, "no highlight", err)
}
diff --git a/gopls/internal/server/hover.go b/gopls/internal/server/hover.go
index 0df8afb..d0c2c83 100644
--- a/gopls/internal/server/hover.go
+++ b/gopls/internal/server/hover.go
@@ -34,21 +34,9 @@
}
defer release()

- var rng protocol.Range
- if params.Range == (protocol.Range{}) {
- // No selection range was provided.
- // Default to an empty range at the position.
- rng = protocol.Range{
- Start: params.Position,
- End: params.Position,
- }
- } else {
- rng = params.Range
- }
-
switch snapshot.FileKind(fh) {
case file.Mod:
- return mod.Hover(ctx, snapshot, fh, params.Position)
+ return mod.Hover(ctx, snapshot, fh, params.Range)
case file.Go:
var pkgURL func(path golang.PackagePath, fragment string) protocol.URI
if snapshot.Options().LinksInHover == settings.LinksInHover_Gopls {
@@ -61,11 +49,11 @@
}
}
}
- return golang.Hover(ctx, snapshot, fh, rng, pkgURL)
+ return golang.Hover(ctx, snapshot, fh, params.Range, pkgURL)
case file.Tmpl:
- return template.Hover(ctx, snapshot, fh, params.Position)
+ return template.Hover(ctx, snapshot, fh, params.Range)
case file.Work:
- return work.Hover(ctx, snapshot, fh, params.Position)
+ return work.Hover(ctx, snapshot, fh, params.Range)
}
return nil, nil // empty result
}
diff --git a/gopls/internal/server/implementation.go b/gopls/internal/server/implementation.go
index c4e59c9..37a132b 100644
--- a/gopls/internal/server/implementation.go
+++ b/gopls/internal/server/implementation.go
@@ -32,5 +32,5 @@
if snapshot.FileKind(fh) != file.Go {
return nil, nil // empty result
}
- return golang.Implementation(ctx, snapshot, fh, params.Position)
+ return golang.Implementation(ctx, snapshot, fh, params.Range)
}
diff --git a/gopls/internal/server/references.go b/gopls/internal/server/references.go
index 2916f27..f419390 100644
--- a/gopls/internal/server/references.go
+++ b/gopls/internal/server/references.go
@@ -34,7 +34,7 @@
case file.Tmpl:
return template.References(ctx, snapshot, fh, params)
case file.Go:
- return golang.References(ctx, snapshot, fh, params.Position, params.Context.IncludeDeclaration)
+ return golang.References(ctx, snapshot, fh, params.Range, params.Context.IncludeDeclaration)
}
return nil, nil // empty result
}
diff --git a/gopls/internal/server/rename.go b/gopls/internal/server/rename.go
index c7abc4f..e12e915 100644
--- a/gopls/internal/server/rename.go
+++ b/gopls/internal/server/rename.go
@@ -30,7 +30,7 @@
return nil, fmt.Errorf("cannot rename in file of type %s", kind)
}

- changes, err := golang.Rename(ctx, snapshot, fh, params.Position, params.NewName)
+ changes, err := golang.Rename(ctx, snapshot, fh, params.Range, params.NewName)
if err != nil {
return nil, err
}
@@ -59,7 +59,7 @@

// Do not return errors here, as it adds clutter.
// Returning a nil result means there is not a valid rename.
- item, usererr, err := golang.PrepareRename(ctx, snapshot, fh, params.Position)
+ item, usererr, err := golang.PrepareRename(ctx, snapshot, fh, params.Range)
if err != nil {
// Return usererr here rather than err, to avoid cluttering the UI with
// internal error details.
diff --git a/gopls/internal/server/signature_help.go b/gopls/internal/server/signature_help.go
index c8b6cb6..f89115b 100644
--- a/gopls/internal/server/signature_help.go
+++ b/gopls/internal/server/signature_help.go
@@ -28,14 +28,14 @@
return nil, nil // empty result
}

- info, err := golang.SignatureHelp(ctx, snapshot, fh, params)
+ info, err := golang.SignatureHelp(ctx, snapshot, fh, params.Range, params.Context)
if err != nil {
// TODO(rfindley): is this correct? Apparently, returning an error from
// signatureHelp is distracting in some editors, though I haven't confirmed
// that recently.
//
// It's unclear whether we still need to avoid returning this error result.
- event.Error(ctx, "signature help failed", err, label.Position.Of(params.Position))
+ event.Error(ctx, "signature help failed", err, label.Position.Of(params.Range.Start), label.Position.Of(params.Range.End))
return nil, nil
}
if info == nil {
diff --git a/gopls/internal/server/type_hierarchy.go b/gopls/internal/server/type_hierarchy.go
index 037c8c5..61bced8 100644
--- a/gopls/internal/server/type_hierarchy.go
+++ b/gopls/internal/server/type_hierarchy.go
@@ -25,7 +25,7 @@
defer release()
switch snapshot.FileKind(fh) {
case file.Go:
- return golang.PrepareTypeHierarchy(ctx, snapshot, fh, params.Position)
+ return golang.PrepareTypeHierarchy(ctx, snapshot, fh, params.Range)
}
return nil, fmt.Errorf("unsupported file type: %v", fh)
}
diff --git a/gopls/internal/telemetry/telemetry_test.go b/gopls/internal/telemetry/telemetry_test.go
index 63a809c..2bbab7d 100644
--- a/gopls/internal/telemetry/telemetry_test.go
+++ b/gopls/internal/telemetry/telemetry_test.go
@@ -263,7 +263,7 @@
).Run(t, files, func(_ *testing.T, env *Env) {
env.OpenFile("a.go")
before := totalLatencySamples(t, "completion", false)
- loc := env.RegexpSearch("a.go", "x")
+ loc := env.RegexpSearch("a.go", "()x")
for i := 0; i < 10; i++ {
env.Completion(loc)
}
diff --git a/gopls/internal/template/highlight.go b/gopls/internal/template/highlight.go
index 8a8244d..2e83c8b 100644
--- a/gopls/internal/template/highlight.go
+++ b/gopls/internal/template/highlight.go
@@ -14,20 +14,20 @@
"golang.org/x/tools/gopls/internal/protocol"
)

-func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, loc protocol.Position) ([]protocol.DocumentHighlight, error) {
+func Highlight(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.DocumentHighlight, error) {
buf, err := fh.Content()
if err != nil {
return nil, err
}
p := parseBuffer(fh.URI(), buf)
- pos, err := p.mapper.PositionOffset(loc)
+ start, end, err := p.mapper.RangeOffsets(rng)
if err != nil {
return nil, err
}

if p.parseErr == nil {
for _, s := range p.symbols {
- if s.start <= pos && pos < s.start+s.len {
+ if s.start <= start && end < s.start+s.len {
return markSymbols(p, s)
}
}
@@ -36,8 +36,8 @@
// these tokens exist whether or not there was a parse error
// (symbols require a successful parse)
for _, tok := range p.tokens {
- if tok.start <= pos && pos < tok.end {
- wordAt := wordAt(p.buf, pos)
+ if tok.start <= start && end < tok.end {
+ wordAt := wordAt(p.buf, start)
if len(wordAt) > 0 {
return markWordInToken(p, wordAt)
}
diff --git a/gopls/internal/template/implementations.go b/gopls/internal/template/implementations.go
index f0e5552..2e6bf72 100644
--- a/gopls/internal/template/implementations.go
+++ b/gopls/internal/template/implementations.go
@@ -97,8 +97,8 @@
// does not understand scoping (if any) in templates. This code is
// for definitions, type definitions, and implementations.
// Results only for variables and templates.
-func Definition(snapshot *cache.Snapshot, fh file.Handle, loc protocol.Position) ([]protocol.Location, error) {
- x, _, err := symAtPosition(fh, loc)
+func Definition(snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) ([]protocol.Location, error) {
+ x, _, err := symAtRange(fh, rng)
if err != nil {
return nil, err
}
@@ -121,8 +121,8 @@
return ans, nil
}

-func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) {
- sym, p, err := symAtPosition(fh, position)
+func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) (*protocol.Hover, error) {
+ sym, p, err := symAtRange(fh, rng)
if err != nil {
return nil, err
}
@@ -151,13 +151,13 @@
value = fmt.Sprintf("oops, sym=%#v", sym)
}

- rng, err := p.mapper.OffsetRange(sym.offsets())
+ symRng, err := p.mapper.OffsetRange(sym.offsets())
if err != nil {
return nil, err
}

return &protocol.Hover{
- Range: rng,
+ Range: symRng,
Contents: protocol.MarkupContent{
Kind: protocol.Markdown,
Value: value,
@@ -166,7 +166,7 @@
}

func References(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, params *protocol.ReferenceParams) ([]protocol.Location, error) {
- sym, _, err := symAtPosition(fh, params.Position)
+ sym, _, err := symAtRange(fh, params.Range)
if err != nil {
return nil, err
}
@@ -233,19 +233,19 @@

// TODO: still need to do rename, etc

-func symAtPosition(fh file.Handle, posn protocol.Position) (*symbol, *parsed, error) {
+func symAtRange(fh file.Handle, rng protocol.Range) (*symbol, *parsed, error) {
buf, err := fh.Content()
if err != nil {
return nil, nil, err
}
p := parseBuffer(fh.URI(), buf)
- offset, err := p.mapper.PositionOffset(posn)
+ start, end, err := p.mapper.RangeOffsets(rng)
if err != nil {
return nil, nil, err
}
var syms []symbol
for _, s := range p.symbols {
- if s.start <= offset && offset < s.start+s.len {
+ if s.start <= start && end < s.start+s.len {
syms = append(syms, s)
}
}
diff --git a/gopls/internal/test/integration/completion/completion_test.go b/gopls/internal/test/integration/completion/completion_test.go
index 68bae9d..072a3a2 100644
--- a/gopls/internal/test/integration/completion/completion_test.go
+++ b/gopls/internal/test/integration/completion/completion_test.go
@@ -1300,8 +1300,8 @@
env.OpenFile("a.go")

tests := map[string][]string{
- `DoubleWrap\[()\]\(\)`: {"InterfaceA", "TypeA", "InterfaceB", "TypeB", "TypeC"},
- `DoubleWrap\[InterfaceA, (_)\]\(\)`: {"InterfaceB", "TypeB", "TypeX", "InterfaceA", "TypeA"},
+ `DoubleWrap\[()\]\(\)`: {"InterfaceA", "TypeA", "InterfaceB", "TypeB", "TypeC"},
+ `DoubleWrap\[InterfaceA, ()\_\]\(\)`: {"InterfaceB", "TypeB", "TypeX", "InterfaceA", "TypeA"},
}

for re, wantLabels := range tests {
diff --git a/gopls/internal/work/completion.go b/gopls/internal/work/completion.go
index 870450b..cbe8bc9 100644
--- a/gopls/internal/work/completion.go
+++ b/gopls/internal/work/completion.go
@@ -35,7 +35,7 @@
}

// Find the use statement the user is in.
- use, pathStart, _ := usePath(pw, cursor)
+ use, pathStart, _ := usePath(pw, cursor, cursor)
if use == nil {
return &protocol.CompletionList{}, nil
}
diff --git a/gopls/internal/work/hover.go b/gopls/internal/work/hover.go
index c59c147..5fd7650 100644
--- a/gopls/internal/work/hover.go
+++ b/gopls/internal/work/hover.go
@@ -16,7 +16,7 @@
"golang.org/x/tools/internal/event"
)

-func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, position protocol.Position) (*protocol.Hover, error) {
+func Hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, rng protocol.Range) (*protocol.Hover, error) {
// We only provide hover information for the view's go.work file.
if fh.URI() != snapshot.View().GoWork() {
return nil, nil
@@ -30,14 +30,14 @@
if err != nil {
return nil, fmt.Errorf("getting go.work file handle: %w", err)
}
- offset, err := pw.Mapper.PositionOffset(position)
+ startOffset, endOffset, err := pw.Mapper.RangeOffsets(rng)
if err != nil {
return nil, fmt.Errorf("computing cursor offset: %w", err)
}

// Confirm that the cursor is inside a use statement, and then find
// the position of the use statement's directory path.
- use, pathStart, pathEnd := usePath(pw, offset)
+ use, pathStart, pathEnd := usePath(pw, startOffset, endOffset)

// The cursor position is not on a use statement.
if use == nil {
@@ -59,7 +59,7 @@
mod := pm.File.Module.Mod

// Get the range to highlight for the hover.
- rng, err := pw.Mapper.OffsetRange(pathStart, pathEnd)
+ pathRng, err := pw.Mapper.OffsetRange(pathStart, pathEnd)
if err != nil {
return nil, err
}
@@ -69,11 +69,11 @@
Kind: options.PreferredContentFormat,
Value: mod.Path,
},
- Range: rng,
+ Range: pathRng,
}, nil
}

-func usePath(pw *cache.ParsedWorkFile, offset int) (use *modfile.Use, pathStart, pathEnd int) {
+func usePath(pw *cache.ParsedWorkFile, startOffset, endOffset int) (use *modfile.Use, pathStart, pathEnd int) {
for _, u := range pw.File.Use {
path := []byte(u.Path)
s, e := u.Syntax.Start.Byte, u.Syntax.End.Byte
@@ -85,7 +85,7 @@
// Shift the start position to the location of the
// module directory within the use statement.
pathStart, pathEnd = s+i, s+i+len(path)
- if pathStart <= offset && offset <= pathEnd {
+ if pathStart <= startOffset && endOffset <= pathEnd {
return u, pathStart, pathEnd
}
}

Change information

Files:
  • M gopls/internal/cmd/capabilities_test.go
  • M gopls/internal/goasm/definition.go
  • M gopls/internal/golang/call_hierarchy.go
  • M gopls/internal/golang/comment.go
  • M gopls/internal/golang/definition.go
  • M gopls/internal/golang/embeddirective.go
  • M gopls/internal/golang/highlight.go
  • M gopls/internal/golang/implementation.go
  • M gopls/internal/golang/inline_all.go
  • M gopls/internal/golang/linkname.go
  • M gopls/internal/golang/references.go
  • M gopls/internal/golang/rename.go
  • M gopls/internal/golang/signature_help.go
  • M gopls/internal/golang/type_hierarchy.go
  • M gopls/internal/mcp/references.go
  • M gopls/internal/mcp/rename_symbol.go
  • M gopls/internal/mcp/symbol_references.go
  • M gopls/internal/mod/hover.go
  • M gopls/internal/protocol/generate/output.go
  • M gopls/internal/protocol/generate/tables.go
  • M gopls/internal/protocol/mapper.go
  • M gopls/internal/protocol/protocol.go
  • M gopls/internal/protocol/tsprotocol.go
  • M gopls/internal/protocol/tsserver.go
  • M gopls/internal/server/call_hierarchy.go
  • M gopls/internal/server/completion.go
  • M gopls/internal/server/definition.go
  • M gopls/internal/server/highlight.go
  • M gopls/internal/server/hover.go
  • M gopls/internal/server/implementation.go
  • M gopls/internal/server/references.go
  • M gopls/internal/server/rename.go
  • M gopls/internal/server/signature_help.go
  • M gopls/internal/server/type_hierarchy.go
  • M gopls/internal/telemetry/telemetry_test.go
  • M gopls/internal/template/highlight.go
  • M gopls/internal/template/implementations.go
  • M gopls/internal/test/integration/completion/completion_test.go
  • M gopls/internal/work/completion.go
  • M gopls/internal/work/hover.go
Change size: L
Delta: 40 files changed, 337 insertions(+), 223 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I758f105271f5da6fa10a996ab9339938f60e2eae
Gerrit-Change-Number: 732160
Gerrit-PatchSet: 1
Gerrit-Owner: Hongxiang Jiang <hxj...@golang.org>
unsatisfied_requirement
satisfied_requirement
open
diffy

Hongxiang Jiang (Gerrit)

unread,
Dec 23, 2025, 5:04:08 AM (yesterday) Dec 23
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Hongxiang Jiang abandoned this change

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: abandon
unsatisfied_requirement
satisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages