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.
4
5package fake
6
7import (
8	"fmt"
9	"strings"
10
11	"golang.org/x/tools/internal/lsp/protocol"
12)
13
14// Pos represents a 0-indexed position in a text buffer.
15type Pos struct {
16	Line, Column int
17}
18
19func (p Pos) toProtocolPosition() protocol.Position {
20	return protocol.Position{
21		Line:      float64(p.Line),
22		Character: float64(p.Column),
23	}
24}
25
26func fromProtocolPosition(pos protocol.Position) Pos {
27	return Pos{
28		Line:   int(pos.Line),
29		Column: int(pos.Character),
30	}
31}
32
33// Edit represents a single (contiguous) buffer edit.
34type Edit struct {
35	Start, End Pos
36	Text       string
37}
38
39// NewEdit creates an edit replacing all content between
40// (startLine, startColumn) and (endLine, endColumn) with text.
41func NewEdit(startLine, startColumn, endLine, endColumn int, text string) Edit {
42	return Edit{
43		Start: Pos{Line: startLine, Column: startColumn},
44		End:   Pos{Line: endLine, Column: endColumn},
45		Text:  text,
46	}
47}
48
49func (e Edit) toProtocolChangeEvent() protocol.TextDocumentContentChangeEvent {
50	return protocol.TextDocumentContentChangeEvent{
51		Range: &protocol.Range{
52			Start: e.Start.toProtocolPosition(),
53			End:   e.End.toProtocolPosition(),
54		},
55		Text: e.Text,
56	}
57}
58
59func fromProtocolTextEdit(textEdit protocol.TextEdit) Edit {
60	return Edit{
61		Start: fromProtocolPosition(textEdit.Range.Start),
62		End:   fromProtocolPosition(textEdit.Range.End),
63		Text:  textEdit.NewText,
64	}
65}
66
67// inText reports whether p is a valid position in the text buffer.
68func inText(p Pos, content []string) bool {
69	if p.Line < 0 || p.Line >= len(content) {
70		return false
71	}
72	// Note the strict right bound: the column indexes character _separators_,
73	// not characters.
74	if p.Column < 0 || p.Column > len(content[p.Line]) {
75		return false
76	}
77	return true
78}
79
80// editContent implements a simplistic, inefficient algorithm for applying text
81// edits to our buffer representation. It returns an error if the edit is
82// invalid for the current content.
83func editContent(content []string, edit Edit) ([]string, error) {
84	if edit.End.Line < edit.Start.Line || (edit.End.Line == edit.Start.Line && edit.End.Column < edit.Start.Column) {
85		return nil, fmt.Errorf("invalid edit: end %v before start %v", edit.End, edit.Start)
86	}
87	if !inText(edit.Start, content) {
88		return nil, fmt.Errorf("start position %v is out of bounds", edit.Start)
89	}
90	if !inText(edit.End, content) {
91		return nil, fmt.Errorf("end position %v is out of bounds", edit.End)
92	}
93	// Splice the edit text in between the first and last lines of the edit.
94	prefix := string([]rune(content[edit.Start.Line])[:edit.Start.Column])
95	suffix := string([]rune(content[edit.End.Line])[edit.End.Column:])
96	newLines := strings.Split(prefix+edit.Text+suffix, "\n")
97	newContent := append(content[:edit.Start.Line], newLines...)
98	return append(newContent, content[edit.End.Line+1:]...), nil
99}
100