internal/lsp: add support for document highlight
Change-Id: I232dbb0b66d690e45079808fd0dbf026c4459400
Reviewed-on: https://go-review.googlesource.com/c/tools/+/169277
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/go/packages/packagestest/expect.go b/go/packages/packagestest/expect.go
index 925e417..3a1fa3f 100644
--- a/go/packages/packagestest/expect.go
+++ b/go/packages/packagestest/expect.go
@@ -52,6 +52,7 @@
// *regexp.Regexp : can only be supplied a regular expression literal
// token.Pos : has a file position calculated as described below.
// token.Position : has a file position calculated as described below.
+// expect.Range: has a start and end position as described below.
// interface{} : will be passed any value
//
// Position calculation
diff --git a/internal/lsp/highlight.go b/internal/lsp/highlight.go
new file mode 100644
index 0000000..403ac28
--- /dev/null
+++ b/internal/lsp/highlight.go
@@ -0,0 +1,24 @@
+// Copyright 2019 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 (
+ "golang.org/x/tools/internal/lsp/protocol"
+ "golang.org/x/tools/internal/span"
+)
+
+func toProtocolHighlight(m *protocol.ColumnMapper, spans []span.Span) []protocol.DocumentHighlight {
+ result := make([]protocol.DocumentHighlight, 0, len(spans))
+ kind := protocol.Text
+ for _, span := range spans {
+ r, err := m.Range(span)
+ if err != nil {
+ continue
+ }
+ h := protocol.DocumentHighlight{Kind: &kind, Range: r}
+ result = append(result, h)
+ }
+ return result
+}
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 3fe4bc9..ee44f9c 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -42,6 +42,7 @@
const expectedFormatCount = 4
const expectedDefinitionsCount = 16
const expectedTypeDefinitionsCount = 2
+ const expectedHighlightsCount = 2
files := packagestest.MustCopyFileTree(dir)
for fragment, operation := range files {
@@ -85,15 +86,17 @@
expectedFormat := make(formats)
expectedDefinitions := make(definitions)
expectedTypeDefinitions := make(definitions)
+ expectedHighlights := make(highlights)
// Collect any data that needs to be used by subsequent tests.
if err := exported.Expect(map[string]interface{}{
- "diag": expectedDiagnostics.collect,
- "item": completionItems.collect,
- "complete": expectedCompletions.collect,
- "format": expectedFormat.collect,
- "godef": expectedDefinitions.collect,
- "typdef": expectedTypeDefinitions.collect,
+ "diag": expectedDiagnostics.collect,
+ "item": completionItems.collect,
+ "complete": expectedCompletions.collect,
+ "format": expectedFormat.collect,
+ "godef": expectedDefinitions.collect,
+ "typdef": expectedTypeDefinitions.collect,
+ "highlight": expectedHighlights.collect,
}); err != nil {
t.Fatal(err)
}
@@ -155,6 +158,16 @@
}
expectedTypeDefinitions.test(t, s, true)
})
+
+ t.Run("Highlights", func(t *testing.T) {
+ t.Helper()
+ if goVersion111 { // TODO(rstambler): Remove this when we no longer support Go 1.10.
+ if len(expectedHighlights) != expectedHighlightsCount {
+ t.Errorf("got %v highlights expected %v", len(expectedHighlights), expectedHighlightsCount)
+ }
+ }
+ expectedHighlights.test(t, s)
+ })
}
type diagnostics map[span.URI][]protocol.Diagnostic
@@ -162,6 +175,7 @@
type completions map[token.Position][]token.Pos
type formats map[string]string
type definitions map[protocol.Location]protocol.Location
+type highlights map[string][]protocol.Location
func (d diagnostics) test(t *testing.T, v source.View) int {
count := 0
@@ -456,6 +470,39 @@
d[lSrc] = lTarget
}
+func (h highlights) collect(e *packagestest.Exported, fset *token.FileSet, name string, rng packagestest.Range) {
+ s, m := testLocation(e, fset, rng)
+ loc, err := m.Location(s)
+ if err != nil {
+ return
+ }
+
+ h[name] = append(h[name], loc)
+}
+
+func (h highlights) test(t *testing.T, s *server) {
+ for name, locations := range h {
+ params := &protocol.TextDocumentPositionParams{
+ TextDocument: protocol.TextDocumentIdentifier{
+ URI: locations[0].URI,
+ },
+ Position: locations[0].Range.Start,
+ }
+ highlights, err := s.DocumentHighlight(context.Background(), params)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(highlights) != len(locations) {
+ t.Fatalf("got %d highlights for %s, expected %d", len(highlights), name, len(locations))
+ }
+ for i := range highlights {
+ if highlights[i].Range != locations[i].Range {
+ t.Errorf("want %v, got %v\n", locations[i].Range, highlights[i].Range)
+ }
+ }
+ }
+}
+
func testLocation(e *packagestest.Exported, fset *token.FileSet, rng packagestest.Range) (span.Span, *protocol.ColumnMapper) {
spn, err := span.NewRange(fset, rng.Start, rng.End).Span()
if err != nil {
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 842ae35..5f6d2a7 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -127,6 +127,7 @@
DocumentRangeFormattingProvider: true,
DocumentSymbolProvider: true,
HoverProvider: true,
+ DocumentHighlightProvider: true,
SignatureHelpProvider: &protocol.SignatureHelpOptions{
TriggerCharacters: []string{"(", ","},
},
@@ -423,8 +424,24 @@
return nil, notImplemented("References")
}
-func (s *server) DocumentHighlight(context.Context, *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) {
- return nil, notImplemented("DocumentHighlight")
+func (s *server) DocumentHighlight(ctx context.Context, params *protocol.TextDocumentPositionParams) ([]protocol.DocumentHighlight, error) {
+ f, m, err := newColumnMap(ctx, s.view, span.URI(params.TextDocument.URI))
+ if err != nil {
+ return nil, err
+ }
+
+ spn, err := m.PointSpan(params.Position)
+ if err != nil {
+ return nil, err
+ }
+
+ rng, err := spn.Range(m.Converter)
+ if err != nil {
+ return nil, err
+ }
+
+ spans := source.Highlight(ctx, f, rng.Start)
+ return toProtocolHighlight(m, spans), nil
}
func (s *server) DocumentSymbol(ctx context.Context, params *protocol.DocumentSymbolParams) ([]protocol.DocumentSymbol, error) {
diff --git a/internal/lsp/source/highlight.go b/internal/lsp/source/highlight.go
new file mode 100644
index 0000000..2952d13
--- /dev/null
+++ b/internal/lsp/source/highlight.go
@@ -0,0 +1,42 @@
+// Copyright 2019 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 source
+
+import (
+ "context"
+ "go/ast"
+ "go/token"
+
+ "golang.org/x/tools/go/ast/astutil"
+ "golang.org/x/tools/internal/span"
+)
+
+func Highlight(ctx context.Context, f File, pos token.Pos) []span.Span {
+ fAST := f.GetAST(ctx)
+ fset := f.GetFileSet(ctx)
+ path, _ := astutil.PathEnclosingInterval(fAST, pos, pos)
+ if len(path) == 0 {
+ return nil
+ }
+
+ id, ok := path[0].(*ast.Ident)
+ if !ok {
+ return nil
+ }
+
+ var result []span.Span
+ if id.Obj != nil {
+ ast.Inspect(path[len(path)-1], func(n ast.Node) bool {
+ if n, ok := n.(*ast.Ident); ok && n.Obj == id.Obj {
+ s, err := nodeSpan(n, fset)
+ if err == nil {
+ result = append(result, s)
+ }
+ }
+ return true
+ })
+ }
+ return result
+}
diff --git a/internal/lsp/testdata/highlights/highlights.go b/internal/lsp/testdata/highlights/highlights.go
new file mode 100644
index 0000000..9314842
--- /dev/null
+++ b/internal/lsp/testdata/highlights/highlights.go
@@ -0,0 +1,15 @@
+package highlights
+
+import "fmt"
+
+type F struct{ bar int }
+
+var foo = F{bar: 52} //@highlight("foo", "foo")
+
+func Print() {
+ fmt.Println(foo) //@highlight("foo", "foo")
+}
+
+func (x *F) Inc() { //@highlight("x", "x")
+ x.bar++ //@highlight("x", "x")
+}