File: edit.go

package info (click to toggle)
golang-golang-x-tools 1%3A0.5.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-backports
  • size: 16,592 kB
  • sloc: javascript: 2,011; asm: 1,635; sh: 192; yacc: 155; makefile: 52; ansic: 8
file content (157 lines) | stat: -rw-r--r-- 3,980 bytes parent folder | download
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
}