-
Notifications
You must be signed in to change notification settings - Fork 4k
/
TextChangeRange.cs
142 lines (121 loc) · 4.41 KB
/
TextChangeRange.cs
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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Text
{
/// <summary>
/// Represents the change to a span of text.
/// </summary>
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
public readonly struct TextChangeRange : IEquatable<TextChangeRange>
{
/// <summary>
/// The span of text before the edit which is being changed
/// </summary>
public TextSpan Span { get; }
/// <summary>
/// Width of the span after the edit. A 0 here would represent a delete
/// </summary>
public int NewLength { get; }
internal int NewEnd => Span.Start + NewLength;
/// <summary>
/// Initializes a new instance of <see cref="TextChangeRange"/>.
/// </summary>
/// <param name="span"></param>
/// <param name="newLength"></param>
public TextChangeRange(TextSpan span, int newLength)
: this()
{
if (newLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(newLength));
}
this.Span = span;
this.NewLength = newLength;
}
/// <summary>
/// Compares current instance of <see cref="TextChangeRange"/> to another.
/// </summary>
public bool Equals(TextChangeRange other)
{
return
other.Span == this.Span &&
other.NewLength == this.NewLength;
}
/// <summary>
/// Compares current instance of <see cref="TextChangeRange"/> to another.
/// </summary>
public override bool Equals(object? obj)
{
return obj is TextChangeRange range && Equals(range);
}
/// <summary>
/// Provides hash code for current instance of <see cref="TextChangeRange"/>.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return Hash.Combine(this.NewLength, this.Span.GetHashCode());
}
/// <summary>
/// Determines if two instances of <see cref="TextChangeRange"/> are same.
/// </summary>
public static bool operator ==(TextChangeRange left, TextChangeRange right)
{
return left.Equals(right);
}
/// <summary>
/// Determines if two instances of <see cref="TextChangeRange"/> are different.
/// </summary>
public static bool operator !=(TextChangeRange left, TextChangeRange right)
{
return !(left == right);
}
/// <summary>
/// An empty set of changes.
/// </summary>
public static IReadOnlyList<TextChangeRange> NoChanges => SpecializedCollections.EmptyReadOnlyList<TextChangeRange>();
/// <summary>
/// Collapse a set of <see cref="TextChangeRange"/>s into a single encompassing range. If
/// the set of ranges provided is empty, an empty range is returned.
/// </summary>
public static TextChangeRange Collapse(IEnumerable<TextChangeRange> changes)
{
var diff = 0;
var start = int.MaxValue;
var end = 0;
foreach (var change in changes)
{
diff += change.NewLength - change.Span.Length;
if (change.Span.Start < start)
{
start = change.Span.Start;
}
if (change.Span.End > end)
{
end = change.Span.End;
}
}
if (start > end)
{
// there were no changes.
return default(TextChangeRange);
}
var combined = TextSpan.FromBounds(start, end);
var newLen = combined.Length + diff;
return new TextChangeRange(combined, newLen);
}
private string GetDebuggerDisplay()
{
return $"new TextChangeRange(new TextSpan({Span.Start}, {Span.Length}), {NewLength})";
}
public override string ToString()
{
return $"TextChangeRange(Span={Span}, NewLength={NewLength})";
}
}
}