--- /dev/null
+// 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"
+ "sort"
+ "strings"
+
+ "golang.org/x/tools/internal/lsp/protocol"
+)
+
+// Pos represents a position in a text buffer. Both Line and Column are
+// 0-indexed.
+type Pos struct {
+ Line, Column int
+}
+
+// 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: float64(p.Line),
+ Character: float64(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
+}
+
+// editContent implements a simplistic, inefficient algorithm for applying text
+// edits to our buffer representation. It returns an error if the edit is
+// invalid for the current content.
+func editContent(content []string, edits []Edit) ([]string, error) {
+ newEdits := make([]Edit, len(edits))
+ copy(newEdits, edits)
+ sort.Slice(newEdits, func(i, j int) bool {
+ if newEdits[i].Start.Line < newEdits[j].Start.Line {
+ return true
+ }
+ if newEdits[i].Start.Line > newEdits[j].Start.Line {
+ return false
+ }
+ return newEdits[i].Start.Column < newEdits[j].Start.Column
+ })
+
+ // Validate edits.
+ for _, edit := range newEdits {
+ if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
+ return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
+ }
+ if !inText(edit.Start, content) {
+ return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
+ }
+ if !inText(edit.End, content) {
+ return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
+ }
+ }
+
+ var (
+ b strings.Builder
+ line, column int
+ )
+ advance := func(toLine, toColumn int) {
+ for ; line < toLine; line++ {
+ b.WriteString(string([]rune(content[line])[column:]) + "\n")
+ column = 0
+ }
+ b.WriteString(string([]rune(content[line])[column:toColumn]))
+ column = toColumn
+ }
+ for _, edit := range newEdits {
+ advance(edit.Start.Line, edit.Start.Column)
+ b.WriteString(edit.Text)
+ line = edit.End.Line
+ column = edit.End.Column
+ }
+ advance(len(content)-1, len([]rune(content[len(content)-1])))
+ return strings.Split(b.String(), "\n"), nil
+}