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 }