aboutsummaryrefslogtreecommitdiff
path: root/gopls/internal/lsp/selection_range.go
blob: 5b3fd31e9f19e6a6ede3c716d41cb26cab857233 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Copyright 2022 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 lsp

import (
	"context"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/gopls/internal/lsp/protocol"
	"golang.org/x/tools/gopls/internal/lsp/source"
	"golang.org/x/tools/internal/event"
)

// selectionRange defines the textDocument/selectionRange feature,
// which, given a list of positions within a file,
// reports a linked list of enclosing syntactic blocks, innermost first.
//
// See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_selectionRange.
//
// This feature can be used by a client to implement "expand selection" in a
// language-aware fashion. Multiple input positions are supported to allow
// for multiple cursors, and the entire path up to the whole document is
// returned for each cursor to avoid multiple round-trips when the user is
// likely to issue this command multiple times in quick succession.
func (s *Server) selectionRange(ctx context.Context, params *protocol.SelectionRangeParams) ([]protocol.SelectionRange, error) {
	ctx, done := event.Start(ctx, "lsp.Server.documentSymbol")
	defer done()

	snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
	defer release()
	if !ok {
		return nil, err
	}

	pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
	if err != nil {
		return nil, err
	}

	result := make([]protocol.SelectionRange, len(params.Positions))
	for i, protocolPos := range params.Positions {
		pos, err := pgf.PositionPos(protocolPos)
		if err != nil {
			return nil, err
		}

		path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)

		tail := &result[i] // tail of the Parent linked list, built head first

		for j, node := range path {
			rng, err := pgf.NodeRange(node)
			if err != nil {
				return nil, err
			}

			// Add node to tail.
			if j > 0 {
				tail.Parent = &protocol.SelectionRange{}
				tail = tail.Parent
			}
			tail.Range = rng
		}
	}

	return result, nil
}