1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
|
// Copyright 2020 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 fake
import (
"fmt"
"strings"
"unicode/utf8"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/internal/diff"
)
// Pos represents a position in a text buffer.
// Both Line and Column are 0-indexed.
// Column counts runes.
type Pos struct {
Line, Column int
}
func (p Pos) String() string {
return fmt.Sprintf("%v:%v", p.Line, p.Column)
}
// Range corresponds to protocol.Range, but uses the editor friend Pos
// instead of UTF-16 oriented protocol.Position
type Range struct {
Start Pos
End Pos
}
func (p Pos) ToProtocolPosition() protocol.Position {
return protocol.Position{
Line: uint32(p.Line),
Character: uint32(p.Column),
}
}
func fromProtocolPosition(pos protocol.Position) Pos {
return Pos{
Line: int(pos.Line),
Column: int(pos.Character),
}
}
// Edit represents a single (contiguous) buffer edit.
type Edit struct {
Start, End Pos
Text string
}
// Location is the editor friendly equivalent of protocol.Location
type Location struct {
Path string
Range Range
}
// SymbolInformation is an editor friendly version of
// protocol.SymbolInformation, with location information transformed to byte
// offsets. Field names correspond to the protocol type.
type SymbolInformation struct {
Name string
Kind protocol.SymbolKind
Location Location
}
// NewEdit creates an edit replacing all content between
// (startLine, startColumn) and (endLine, endColumn) with text.
func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
return Edit{
Start: Pos{Line: startLine, Column: startColumn},
End: Pos{Line: endLine, Column: endColumn},
Text: text,
}
}
func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
return protocol.TextDocumentContentChangeEvent{
Range: &protocol.Range{
Start: e.Start.ToProtocolPosition(),
End: e.End.ToProtocolPosition(),
},
Text: e.Text,
}
}
func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
return Edit{
Start: fromProtocolPosition(textEdit.Range.Start),
End: fromProtocolPosition(textEdit.Range.End),
Text: textEdit.NewText,
}
}
// inText reports whether p is a valid position in the text buffer.
func inText(p Pos, content []string) bool {
if p.Line < 0 || p.Line >= len(content) {
return false
}
// Note the strict right bound: the column indexes character _separators_,
// not characters.
if p.Column < 0 || p.Column > len([]rune(content[p.Line])) {
return false
}
return true
}
// applyEdits applies the edits to a file with the specified lines,
// and returns a new slice containing the lines of the patched file.
// It is a wrapper around diff.Apply; see that function for preconditions.
func applyEdits(lines []string, edits []Edit) ([]string, error) {
src := strings.Join(lines, "\n")
// Build a table of byte offset of start of each line.
lineOffset := make([]int, len(lines)+1)
offset := 0
for i, line := range lines {
lineOffset[i] = offset
offset += len(line) + len("\n")
}
lineOffset[len(lines)] = offset // EOF
var badCol error
posToOffset := func(pos Pos) int {
offset := lineOffset[pos.Line]
// Convert pos.Column (runes) to a UTF-8 byte offset.
if pos.Line < len(lines) {
for i := 0; i < pos.Column; i++ {
r, sz := utf8.DecodeRuneInString(src[offset:])
if r == '\n' && badCol == nil {
badCol = fmt.Errorf("bad column")
}
offset += sz
}
}
return offset
}
// Convert fake.Edits to diff.Edits
diffEdits := make([]diff.Edit, len(edits))
for i, edit := range edits {
diffEdits[i] = diff.Edit{
Start: posToOffset(edit.Start),
End: posToOffset(edit.End),
New: edit.Text,
}
}
patched, err := diff.Apply(src, diffEdits)
if err != nil {
return nil, err
}
return strings.Split(patched, "\n"), badCol
}
|