1 // Copyright 2020 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
12 "golang.org/x/tools/internal/lsp/protocol"
15 // Pos represents a position in a text buffer. Both Line and Column are
21 // Range corresponds to protocol.Range, but uses the editor friend Pos
22 // instead of UTF-16 oriented protocol.Position
28 func (p Pos) ToProtocolPosition() protocol.Position {
29 return protocol.Position{
30 Line: float64(p.Line),
31 Character: float64(p.Column),
35 func fromProtocolPosition(pos protocol.Position) Pos {
38 Column: int(pos.Character),
42 // Edit represents a single (contiguous) buffer edit.
48 // Location is the editor friendly equivalent of protocol.Location
49 type Location struct {
54 // SymbolInformation is an editor friendly version of
55 // protocol.SymbolInformation, with location information transformed to byte
56 // offsets. Field names correspond to the protocol type.
57 type SymbolInformation struct {
59 Kind protocol.SymbolKind
63 // NewEdit creates an edit replacing all content between
64 // (startLine, startColumn) and (endLine, endColumn) with text.
65 func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
67 Start: Pos{Line: startLine, Column: startColumn},
68 End: Pos{Line: endLine, Column: endColumn},
73 func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
74 return protocol.TextDocumentContentChangeEvent{
75 Range: &protocol.Range{
76 Start: e.Start.ToProtocolPosition(),
77 End: e.End.ToProtocolPosition(),
83 func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
85 Start: fromProtocolPosition(textEdit.Range.Start),
86 End: fromProtocolPosition(textEdit.Range.End),
87 Text: textEdit.NewText,
91 // inText reports whether p is a valid position in the text buffer.
92 func inText(p Pos, content []string) bool {
93 if p.Line < 0 || p.Line >= len(content) {
96 // Note the strict right bound: the column indexes character _separators_,
98 if p.Column < 0 || p.Column > len([]rune(content[p.Line])) {
104 // editContent implements a simplistic, inefficient algorithm for applying text
105 // edits to our buffer representation. It returns an error if the edit is
106 // invalid for the current content.
107 func editContent(content []string, edits []Edit) ([]string, error) {
108 newEdits := make([]Edit, len(edits))
109 copy(newEdits, edits)
110 sort.Slice(newEdits, func(i, j int) bool {
111 if newEdits[i].Start.Line < newEdits[j].Start.Line {
114 if newEdits[i].Start.Line > newEdits[j].Start.Line {
117 return newEdits[i].Start.Column < newEdits[j].Start.Column
121 for _, edit := range newEdits {
122 if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
123 return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
125 if !inText(edit.Start, content) {
126 return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
128 if !inText(edit.End, content) {
129 return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
137 advance := func(toLine, toColumn int) {
138 for ; line < toLine; line++ {
139 b.WriteString(string([]rune(content[line])[column:]) + "\n")
142 b.WriteString(string([]rune(content[line])[column:toColumn]))
145 for _, edit := range newEdits {
146 advance(edit.Start.Line, edit.Start.Column)
147 b.WriteString(edit.Text)
149 column = edit.End.Column
151 advance(len(content)-1, len([]rune(content[len(content)-1])))
152 return strings.Split(b.String(), "\n"), nil