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.
12 // Unified represents a set of edits as a unified diff.
14 // From is the name of the original file.
16 // To is the name of the modified file.
18 // Hunks is the set of edit hunks needed to transform the file content.
22 // Hunk represents a contiguous set of line edits to apply.
24 // The line in the original source where the hunk starts.
26 // The line in the original source where the hunk finishes.
28 // The set of line based edits to apply.
32 // Line represents a single line operation to apply as part of a Hunk.
34 // Kind is the type of line this represents, deletion, insertion or copy.
36 // Content is the content of this line.
37 // For deletion it is the line being removed, for all others it is the line
38 // to put in the output.
42 // OpKind is used to denote the type of operation a line represents.
46 // Delete is the operation kind for a line that is present in the input
47 // but not in the output.
49 // Insert is the operation kind for a line that is new in the output.
51 // Equal is the operation kind for a line that is the same in the input and
52 // output, often used to provide context around edited lines.
56 // String returns a human readable representation of an OpKind. It is not
57 // intended for machine processing.
58 func (k OpKind) String() string {
67 panic("unknown operation kind")
76 // ToUnified takes a file contents and a sequence of edits, and calculates
77 // a unified diff that represents those edits.
78 func ToUnified(from, to string, content string, edits []TextEdit) Unified {
86 c, edits, partial := prepareEdits(content, edits)
88 edits = lineEdits(content, c, edits)
90 lines := splitLines(content)
94 for _, edit := range edits {
95 start := edit.Span.Start().Line() - 1
96 end := edit.Span.End().Line() - 1
98 case h != nil && start == last:
100 case h != nil && start <= last+gap:
101 //within range of previous lines, add the joiners
102 addEqualLines(h, lines, last, start)
104 //need to start a new hunk
106 // add the edge to the previous hunk
107 addEqualLines(h, lines, last, last+edge)
108 u.Hunks = append(u.Hunks, h)
110 toLine += start - last
115 // add the edge to the new hunk
116 delta := addEqualLines(h, lines, start-edge, start)
121 for i := start; i < end; i++ {
122 h.Lines = append(h.Lines, Line{Kind: Delete, Content: lines[i]})
125 if edit.NewText != "" {
126 for _, line := range splitLines(edit.NewText) {
127 h.Lines = append(h.Lines, Line{Kind: Insert, Content: line})
133 // add the edge to the final hunk
134 addEqualLines(h, lines, last, last+edge)
135 u.Hunks = append(u.Hunks, h)
140 func splitLines(text string) []string {
141 lines := strings.SplitAfter(text, "\n")
142 if lines[len(lines)-1] == "" {
143 lines = lines[:len(lines)-1]
148 func addEqualLines(h *Hunk, lines []string, start, end int) int {
150 for i := start; i < end; i++ {
157 h.Lines = append(h.Lines, Line{Kind: Equal, Content: lines[i]})
163 // Format converts a unified diff to the standard textual form for that diff.
164 // The output of this function can be passed to tools like patch.
165 func (u Unified) Format(f fmt.State, r rune) {
166 if len(u.Hunks) == 0 {
169 fmt.Fprintf(f, "--- %s\n", u.From)
170 fmt.Fprintf(f, "+++ %s\n", u.To)
171 for _, hunk := range u.Hunks {
172 fromCount, toCount := 0, 0
173 for _, l := range hunk.Lines {
186 fmt.Fprintf(f, " -%d,%d", hunk.FromLine, fromCount)
188 fmt.Fprintf(f, " -%d", hunk.FromLine)
191 fmt.Fprintf(f, " +%d,%d", hunk.ToLine, toCount)
193 fmt.Fprintf(f, " +%d", hunk.ToLine)
195 fmt.Fprint(f, " @@\n")
196 for _, l := range hunk.Lines {
199 fmt.Fprintf(f, "-%s", l.Content)
201 fmt.Fprintf(f, "+%s", l.Content)
203 fmt.Fprintf(f, " %s", l.Content)
205 if !strings.HasSuffix(l.Content, "\n") {
206 fmt.Fprintf(f, "\n\\ No newline at end of file\n")