1
0
mirror of https://github.com/golang/go synced 2024-09-30 20:28:32 -06:00
go/internal/lsp/format.go
Rebecca Stambler 9650c66da3 internal/lsp: add support for publishing diagnostics
Any time a file is changed, we compute diagnostics for its package and
return them to the client. No caching is implemented yet, so we parse
and type-check the package each time.

Change-Id: I7fb2f1d8975e7ce092938d903599188cc2132512
Reviewed-on: https://go-review.googlesource.com/c/143497
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
2018-10-22 21:17:51 +00:00

84 lines
2.3 KiB
Go

package lsp
import (
"bytes"
"fmt"
"go/format"
"golang.org/x/tools/internal/lsp/protocol"
)
// format formats a document with a given range.
func (s *server) format(uri protocol.DocumentURI, rng *protocol.Range) ([]protocol.TextEdit, error) {
data, err := s.readActiveFile(uri)
if err != nil {
return nil, err
}
if rng != nil {
start, err := positionToOffset(data, int(rng.Start.Line), int(rng.Start.Character))
if err != nil {
return nil, err
}
end, err := positionToOffset(data, int(rng.End.Line), int(rng.End.Character))
if err != nil {
return nil, err
}
data = data[start:end]
// format.Source will fail if the substring is not a balanced expression tree.
// TODO(rstambler): parse the file and use astutil.PathEnclosingInterval to
// find the largest ast.Node n contained within start:end, and format the
// region n.Pos-n.End instead.
}
// format.Source changes slightly from one release to another, so the version
// of Go used to build the LSP server will determine how it formats code.
// This should be acceptable for all users, who likely be prompted to rebuild
// the LSP server on each Go release.
fmted, err := format.Source([]byte(data))
if err != nil {
return nil, err
}
if rng == nil {
// Get the ending line and column numbers for the original file.
line := bytes.Count(data, []byte("\n"))
col := len(data) - bytes.LastIndex(data, []byte("\n")) - 1
if col < 0 {
col = 0
}
rng = &protocol.Range{
Start: protocol.Position{
Line: 0,
Character: 0,
},
End: protocol.Position{
Line: float64(line),
Character: float64(col),
},
}
}
// TODO(rstambler): Compute text edits instead of replacing whole file.
return []protocol.TextEdit{
{
Range: *rng,
NewText: string(fmted),
},
}, nil
}
// positionToOffset converts a 0-based line and column number in a file
// to a byte offset value.
func positionToOffset(contents []byte, line, col int) (int, error) {
start := 0
for i := 0; i < int(line); i++ {
if start >= len(contents) {
return 0, fmt.Errorf("file contains %v lines, not %v lines", i, line)
}
index := bytes.IndexByte(contents[start:], '\n')
if index == -1 {
return 0, fmt.Errorf("file contains %v lines, not %v lines", i, line)
}
start += index + 1
}
offset := start + int(col)
return offset, nil
}