| 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 |
| } |