Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201028153306-37f0764111ff / internal / lsp / diff / diff.go
1 // Copyright 2019 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
5 // Package diff supports a pluggable diff algorithm.
6 package diff
7
8 import (
9         "sort"
10         "strings"
11
12         "golang.org/x/tools/internal/span"
13 )
14
15 // TextEdit represents a change to a section of a document.
16 // The text within the specified span should be replaced by the supplied new text.
17 type TextEdit struct {
18         Span    span.Span
19         NewText string
20 }
21
22 // ComputeEdits is the type for a function that produces a set of edits that
23 // convert from the before content to the after content.
24 type ComputeEdits func(uri span.URI, before, after string) []TextEdit
25
26 // SortTextEdits attempts to order all edits by their starting points.
27 // The sort is stable so that edits with the same starting point will not
28 // be reordered.
29 func SortTextEdits(d []TextEdit) {
30         // Use a stable sort to maintain the order of edits inserted at the same position.
31         sort.SliceStable(d, func(i int, j int) bool {
32                 return span.Compare(d[i].Span, d[j].Span) < 0
33         })
34 }
35
36 // ApplyEdits applies the set of edits to the before and returns the resulting
37 // content.
38 // It may panic or produce garbage if the edits are not valid for the provided
39 // before content.
40 func ApplyEdits(before string, edits []TextEdit) string {
41         // Preconditions:
42         //   - all of the edits apply to before
43         //   - and all the spans for each TextEdit have the same URI
44         if len(edits) == 0 {
45                 return before
46         }
47         _, edits, _ = prepareEdits(before, edits)
48         after := strings.Builder{}
49         last := 0
50         for _, edit := range edits {
51                 start := edit.Span.Start().Offset()
52                 if start > last {
53                         after.WriteString(before[last:start])
54                         last = start
55                 }
56                 after.WriteString(edit.NewText)
57                 last = edit.Span.End().Offset()
58         }
59         if last < len(before) {
60                 after.WriteString(before[last:])
61         }
62         return after.String()
63 }
64
65 // LineEdits takes a set of edits and expands and merges them as necessary
66 // to ensure that there are only full line edits left when it is done.
67 func LineEdits(before string, edits []TextEdit) []TextEdit {
68         if len(edits) == 0 {
69                 return nil
70         }
71         c, edits, partial := prepareEdits(before, edits)
72         if partial {
73                 edits = lineEdits(before, c, edits)
74         }
75         return edits
76 }
77
78 // prepareEdits returns a sorted copy of the edits
79 func prepareEdits(before string, edits []TextEdit) (*span.TokenConverter, []TextEdit, bool) {
80         partial := false
81         c := span.NewContentConverter("", []byte(before))
82         copied := make([]TextEdit, len(edits))
83         for i, edit := range edits {
84                 edit.Span, _ = edit.Span.WithAll(c)
85                 copied[i] = edit
86                 partial = partial ||
87                         edit.Span.Start().Offset() >= len(before) ||
88                         edit.Span.Start().Column() > 1 || edit.Span.End().Column() > 1
89         }
90         SortTextEdits(copied)
91         return c, copied, partial
92 }
93
94 // lineEdits rewrites the edits to always be full line edits
95 func lineEdits(before string, c *span.TokenConverter, edits []TextEdit) []TextEdit {
96         adjusted := make([]TextEdit, 0, len(edits))
97         current := TextEdit{Span: span.Invalid}
98         for _, edit := range edits {
99                 if current.Span.IsValid() && edit.Span.Start().Line() <= current.Span.End().Line() {
100                         // overlaps with the current edit, need to combine
101                         // first get the gap from the previous edit
102                         gap := before[current.Span.End().Offset():edit.Span.Start().Offset()]
103                         // now add the text of this edit
104                         current.NewText += gap + edit.NewText
105                         // and then adjust the end position
106                         current.Span = span.New(current.Span.URI(), current.Span.Start(), edit.Span.End())
107                 } else {
108                         // does not overlap, add previous run (if there is one)
109                         adjusted = addEdit(before, adjusted, current)
110                         // and then remember this edit as the start of the next run
111                         current = edit
112                 }
113         }
114         // add the current pending run if there is one
115         return addEdit(before, adjusted, current)
116 }
117
118 func addEdit(before string, edits []TextEdit, edit TextEdit) []TextEdit {
119         if !edit.Span.IsValid() {
120                 return edits
121         }
122         // if edit is partial, expand it to full line now
123         start := edit.Span.Start()
124         end := edit.Span.End()
125         if start.Column() > 1 {
126                 // prepend the text and adjust to start of line
127                 delta := start.Column() - 1
128                 start = span.NewPoint(start.Line(), 1, start.Offset()-delta)
129                 edit.Span = span.New(edit.Span.URI(), start, end)
130                 edit.NewText = before[start.Offset():start.Offset()+delta] + edit.NewText
131         }
132         if start.Offset() >= len(before) && start.Line() > 1 && before[len(before)-1] != '\n' {
133                 // after end of file that does not end in eol, so join to last line of file
134                 // to do this we need to know where the start of the last line was
135                 eol := strings.LastIndex(before, "\n")
136                 if eol < 0 {
137                         // file is one non terminated line
138                         eol = 0
139                 }
140                 delta := len(before) - eol
141                 start = span.NewPoint(start.Line()-1, 1, start.Offset()-delta)
142                 edit.Span = span.New(edit.Span.URI(), start, end)
143                 edit.NewText = before[start.Offset():start.Offset()+delta] + edit.NewText
144         }
145         if end.Column() > 1 {
146                 remains := before[end.Offset():]
147                 eol := strings.IndexRune(remains, '\n')
148                 if eol < 0 {
149                         eol = len(remains)
150                 } else {
151                         eol++
152                 }
153                 end = span.NewPoint(end.Line()+1, 1, end.Offset()+eol)
154                 edit.Span = span.New(edit.Span.URI(), start, end)
155                 edit.NewText = edit.NewText + remains[:eol]
156         }
157         edits = append(edits, edit)
158         return edits
159 }