Skip to content

Commit b95d2e6

Browse files
committed
add missing file
1 parent 53f0a04 commit b95d2e6

1 file changed

Lines changed: 313 additions & 0 deletions

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using DiffPlex;
6+
using DiffPlex.Chunkers;
7+
using DiffPlex.Model;
8+
9+
namespace DiffPlex.Renderer
10+
{
11+
/// <summary>
12+
/// Renderer for generating unified diff (unidiff) format output from diff results
13+
/// </summary>
14+
public class UnidiffRenderer
15+
{
16+
private readonly IDiffer differ;
17+
private readonly int contextLines;
18+
19+
/// <summary>
20+
/// Gets the default singleton instance of the unidiff renderer.
21+
/// </summary>
22+
public static UnidiffRenderer Instance { get; } = new UnidiffRenderer();
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="UnidiffRenderer"/> class.
26+
/// </summary>
27+
/// <param name="differ">The differ to use. If null, uses the default Differ.</param>
28+
/// <param name="contextLines">Number of unchanged context lines to include around changes.</param>
29+
public UnidiffRenderer(IDiffer differ = null, int contextLines = 3)
30+
{
31+
this.differ = differ ?? Differ.Instance;
32+
this.contextLines = contextLines;
33+
}
34+
35+
/// <summary>
36+
/// Generates a unified diff format output from two texts.
37+
/// </summary>
38+
/// <param name="oldText">The old text to diff.</param>
39+
/// <param name="newText">The new text.</param>
40+
/// <param name="oldFileName">The old file name to show in the headers.</param>
41+
/// <param name="newFileName">The new file name to show in the headers.</param>
42+
/// <param name="ignoreWhitespace">Whether to ignore whitespace differences.</param>
43+
/// <param name="ignoreCase">Whether to ignore case differences.</param>
44+
/// <returns>A string containing the unified diff output.</returns>
45+
public string Generate(string oldText, string newText, string oldFileName = "a", string newFileName = "b", bool ignoreWhitespace = true, bool ignoreCase = false)
46+
{
47+
if (oldText == null) throw new ArgumentNullException(nameof(oldText));
48+
if (newText == null) throw new ArgumentNullException(nameof(newText));
49+
if (oldFileName == null) throw new ArgumentNullException(nameof(oldFileName));
50+
if (newFileName == null) throw new ArgumentNullException(nameof(newFileName));
51+
52+
var diffResult = differ.CreateDiffs(oldText, newText, ignoreWhitespace, ignoreCase, new LineChunker());
53+
return Generate(diffResult, oldFileName, newFileName);
54+
}
55+
56+
/// <summary>
57+
/// Generates a unified diff format output from a diff result.
58+
/// </summary>
59+
/// <param name="diffResult">The diff result to render.</param>
60+
/// <param name="oldFileName">The old file name to show in the headers.</param>
61+
/// <param name="newFileName">The new file name to show in the headers.</param>
62+
/// <returns>A string containing the unified diff output.</returns>
63+
public string Generate(DiffResult diffResult, string oldFileName = "a", string newFileName = "b")
64+
{
65+
if (diffResult == null) throw new ArgumentNullException(nameof(diffResult));
66+
if (oldFileName == null) throw new ArgumentNullException(nameof(oldFileName));
67+
if (newFileName == null) throw new ArgumentNullException(nameof(newFileName));
68+
69+
if (diffResult.DiffBlocks.Count == 0)
70+
{
71+
return string.Empty;
72+
}
73+
74+
var sb = new StringBuilder();
75+
76+
// Generate the unified diff header
77+
sb.AppendLine($"--- {oldFileName}");
78+
sb.AppendLine($"+++ {newFileName}");
79+
80+
// Group changes into hunks with context
81+
var hunks = CreateHunks(diffResult);
82+
83+
foreach (var hunk in hunks)
84+
{
85+
// Calculate line numbers for the hunk header
86+
int oldStart = hunk.OldStartLine;
87+
int oldCount = hunk.OldLength;
88+
int newStart = hunk.NewStartLine;
89+
int newCount = hunk.NewLength;
90+
91+
// Generate the hunk header
92+
sb.AppendLine($"@@ -{oldStart},{oldCount} +{newStart},{newCount} @@");
93+
94+
// Generate the hunk content
95+
foreach (var line in hunk.Lines)
96+
{
97+
switch (line.Type)
98+
{
99+
case LineType.Unchanged:
100+
sb.AppendLine($" {line.Text}");
101+
break;
102+
case LineType.Deleted:
103+
sb.AppendLine($"-{line.Text}");
104+
break;
105+
case LineType.Inserted:
106+
sb.AppendLine($"+{line.Text}");
107+
break;
108+
}
109+
}
110+
}
111+
112+
return sb.ToString();
113+
}
114+
115+
private List<DiffHunk> CreateHunks(DiffResult diffResult)
116+
{
117+
var hunks = new List<DiffHunk>();
118+
119+
if (diffResult.DiffBlocks.Count == 0) return hunks;
120+
121+
var oldPieces = diffResult.PiecesOld;
122+
var newPieces = diffResult.PiecesNew;
123+
124+
// First, organize the diff blocks into potential hunks separated by contextLines boundary
125+
List<List<DiffBlock>> hunkGroups = new List<List<DiffBlock>>();
126+
List<DiffBlock> currentGroup = new List<DiffBlock>();
127+
hunkGroups.Add(currentGroup);
128+
129+
DiffBlock previousBlock = null;
130+
foreach (var block in diffResult.DiffBlocks)
131+
{
132+
if (previousBlock != null)
133+
{
134+
// If the blocks are too far apart, start a new group
135+
// We want to create a new hunk if the distance between blocks is more than 2*contextLines
136+
if (block.DeleteStartA > (previousBlock.DeleteStartA + previousBlock.DeleteCountA + 2 * contextLines))
137+
{
138+
currentGroup = new List<DiffBlock>();
139+
hunkGroups.Add(currentGroup);
140+
}
141+
}
142+
143+
currentGroup.Add(block);
144+
previousBlock = block;
145+
}
146+
147+
// Now convert each group to a hunk
148+
foreach (var group in hunkGroups)
149+
{
150+
if (group.Count == 0) continue;
151+
152+
// Find the range of the entire group with context
153+
int firstBlockStartA = group[0].DeleteStartA;
154+
int lastBlockEndA = group[group.Count - 1].DeleteStartA + group[group.Count - 1].DeleteCountA;
155+
156+
int contextStartA = Math.Max(0, firstBlockStartA - contextLines);
157+
int contextEndA = Math.Min(oldPieces.Count, lastBlockEndA + contextLines);
158+
159+
// Calculate B file positions
160+
int firstBlockStartB = group[0].InsertStartB;
161+
int contextStartB = Math.Max(0, firstBlockStartB - contextLines);
162+
163+
// Create a new hunk
164+
DiffHunk hunk = new DiffHunk
165+
{
166+
OldStartLine = contextStartA + 1, // 1-based indexing
167+
NewStartLine = contextStartB + 1 // 1-based indexing
168+
};
169+
170+
// Add context lines before first change
171+
for (int i = contextStartA; i < firstBlockStartA; i++)
172+
{
173+
hunk.Lines.Add(new DiffLine
174+
{
175+
Type = LineType.Unchanged,
176+
Text = oldPieces[i],
177+
OldIndex = i,
178+
NewIndex = contextStartB + (i - contextStartA)
179+
});
180+
}
181+
182+
// Add all blocks and intermediate context
183+
int currentPosA = firstBlockStartA;
184+
int currentPosB = firstBlockStartB;
185+
186+
for (int blockIndex = 0; blockIndex < group.Count; blockIndex++)
187+
{
188+
var block = group[blockIndex];
189+
190+
// Add context between blocks if needed
191+
for (int i = currentPosA; i < block.DeleteStartA; i++)
192+
{
193+
int newIndex = currentPosB + (i - currentPosA);
194+
hunk.Lines.Add(new DiffLine
195+
{
196+
Type = LineType.Unchanged,
197+
Text = oldPieces[i],
198+
OldIndex = i,
199+
NewIndex = newIndex
200+
});
201+
}
202+
203+
// Update the current position in B
204+
if (currentPosA < block.DeleteStartA)
205+
{
206+
currentPosB += (block.DeleteStartA - currentPosA);
207+
}
208+
209+
// Add deleted lines
210+
for (int i = 0; i < block.DeleteCountA; i++)
211+
{
212+
hunk.Lines.Add(new DiffLine
213+
{
214+
Type = LineType.Deleted,
215+
Text = oldPieces[block.DeleteStartA + i],
216+
OldIndex = block.DeleteStartA + i,
217+
NewIndex = -1
218+
});
219+
}
220+
221+
// Add inserted lines
222+
for (int i = 0; i < block.InsertCountB; i++)
223+
{
224+
hunk.Lines.Add(new DiffLine
225+
{
226+
Type = LineType.Inserted,
227+
Text = newPieces[block.InsertStartB + i],
228+
OldIndex = -1,
229+
NewIndex = block.InsertStartB + i
230+
});
231+
}
232+
233+
currentPosA = block.DeleteStartA + block.DeleteCountA;
234+
currentPosB = block.InsertStartB + block.InsertCountB;
235+
}
236+
237+
// Add context after last block
238+
for (int i = currentPosA; i < contextEndA; i++)
239+
{
240+
int newIndex = currentPosB + (i - currentPosA);
241+
if (newIndex < newPieces.Count) // Ensure we don't go out of bounds
242+
{
243+
hunk.Lines.Add(new DiffLine
244+
{
245+
Type = LineType.Unchanged,
246+
Text = oldPieces[i],
247+
OldIndex = i,
248+
NewIndex = newIndex
249+
});
250+
}
251+
}
252+
253+
// Calculate final hunk lengths
254+
hunk.OldLength = hunk.Lines.Count(l => l.Type != LineType.Inserted);
255+
hunk.NewLength = hunk.Lines.Count(l => l.Type != LineType.Deleted);
256+
257+
hunks.Add(hunk);
258+
}
259+
260+
return hunks;
261+
}
262+
263+
/// <summary>
264+
/// Generate a unified diff format output directly from two texts.
265+
/// </summary>
266+
/// <param name="oldText">The old text to diff.</param>
267+
/// <param name="newText">The new text.</param>
268+
/// <param name="oldFileName">The old file name to show in the headers.</param>
269+
/// <param name="newFileName">The new file name to show in the headers.</param>
270+
/// <param name="ignoreWhitespace">Whether to ignore whitespace differences.</param>
271+
/// <param name="ignoreCase">Whether to ignore case differences.</param>
272+
/// <param name="contextLines">Number of unchanged context lines to include around changes.</param>
273+
/// <returns>A string containing the unified diff output.</returns>
274+
public static string GenerateUnidiff(
275+
string oldText,
276+
string newText,
277+
string oldFileName = "a",
278+
string newFileName = "b",
279+
bool ignoreWhitespace = true,
280+
bool ignoreCase = false,
281+
int contextLines = 3)
282+
{
283+
var renderer = new UnidiffRenderer(contextLines: contextLines);
284+
return renderer.Generate(oldText, newText, oldFileName, newFileName, ignoreWhitespace, ignoreCase);
285+
}
286+
287+
#region Helper Classes
288+
private enum LineType
289+
{
290+
Unchanged,
291+
Deleted,
292+
Inserted
293+
}
294+
295+
private class DiffLine
296+
{
297+
public LineType Type { get; set; }
298+
public string Text { get; set; }
299+
public int OldIndex { get; set; }
300+
public int NewIndex { get; set; }
301+
}
302+
303+
private class DiffHunk
304+
{
305+
public int OldStartLine { get; set; }
306+
public int OldLength { get; set; }
307+
public int NewStartLine { get; set; }
308+
public int NewLength { get; set; }
309+
public List<DiffLine> Lines { get; } = new List<DiffLine>();
310+
}
311+
#endregion
312+
}
313+
}

0 commit comments

Comments
 (0)