// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Diagnostics; using System.Globalization; using System.Text; using System.Web.Razor.Parser.SyntaxTree; using Microsoft.Internal.Web.Utils; namespace System.Web.Razor.Text { public struct TextChange { private string _newText; private string _oldText; /// /// Constructor for changes where the position hasn't moved (primarily for tests) /// internal TextChange(int position, int oldLength, ITextBuffer oldBuffer, int newLength, ITextBuffer newBuffer) : this(position, oldLength, oldBuffer, position, newLength, newBuffer) { } public TextChange(int oldPosition, int oldLength, ITextBuffer oldBuffer, int newPosition, int newLength, ITextBuffer newBuffer) : this() { if (oldPosition < 0) { throw new ArgumentOutOfRangeException("oldPosition", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "0")); } if (newPosition < 0) { throw new ArgumentOutOfRangeException("newPosition", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "0")); } if (oldLength < 0) { throw new ArgumentOutOfRangeException("oldLength", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "0")); } if (newLength < 0) { throw new ArgumentOutOfRangeException("newLength", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "0")); } if (oldBuffer == null) { throw new ArgumentNullException("oldBuffer"); } if (newBuffer == null) { throw new ArgumentNullException("newBuffer"); } OldPosition = oldPosition; NewPosition = newPosition; OldLength = oldLength; NewLength = newLength; NewBuffer = newBuffer; OldBuffer = oldBuffer; } public int OldPosition { get; private set; } public int NewPosition { get; private set; } public int OldLength { get; private set; } public int NewLength { get; private set; } public ITextBuffer NewBuffer { get; private set; } public ITextBuffer OldBuffer { get; private set; } public string OldText { get { if (_oldText == null && OldBuffer != null) { _oldText = GetText(OldBuffer, OldPosition, OldLength); } return _oldText; } } public string NewText { get { if (_newText == null) { _newText = GetText(NewBuffer, NewPosition, NewLength); } return _newText; } } public bool IsInsert { get { return OldLength == 0 && NewLength > 0; } } public bool IsDelete { get { return OldLength > 0 && NewLength == 0; } } public bool IsReplace { get { return OldLength > 0 && NewLength > 0; } } public override bool Equals(object obj) { if (!(obj is TextChange)) { return false; } TextChange change = (TextChange)obj; return (change.OldPosition == OldPosition) && (change.NewPosition == NewPosition) && (change.OldLength == OldLength) && (change.NewLength == NewLength) && OldBuffer.Equals(change.OldBuffer) && NewBuffer.Equals(change.NewBuffer); } public string ApplyChange(string content, int changeOffset) { int changeRelativePosition = OldPosition - changeOffset; Debug.Assert(changeRelativePosition >= 0); return content.Remove(changeRelativePosition, OldLength) .Insert(changeRelativePosition, NewText); } /// /// Applies the text change to the content of the span and returns the new content. /// This method doesn't update the span content. /// public string ApplyChange(Span span) { return ApplyChange(span.Content, span.Start.AbsoluteIndex); } public override int GetHashCode() { return OldPosition ^ NewPosition ^ OldLength ^ NewLength ^ NewBuffer.GetHashCode() ^ OldBuffer.GetHashCode(); } public override string ToString() { return String.Format(CultureInfo.CurrentCulture, "({0}:{1}) \"{3}\" -> ({0}:{2}) \"{4}\"", OldPosition, OldLength, NewLength, OldText, NewText); } /// /// Removes a common prefix from the edit to turn IntelliSense replacements into insertions where possible /// /// A normalized text change public TextChange Normalize() { if (OldBuffer != null && IsReplace && NewLength > OldLength && NewText.StartsWith(OldText, StringComparison.Ordinal) && NewPosition == OldPosition) { // Normalize the change into an insertion of the uncommon suffix (i.e. strip out the common prefix) return new TextChange(oldPosition: OldPosition + OldLength, oldLength: 0, oldBuffer: OldBuffer, newPosition: OldPosition + OldLength, newLength: NewLength - OldLength, newBuffer: NewBuffer); } return this; } private string GetText(ITextBuffer buffer, int position, int length) { int oldPosition = buffer.Position; try { buffer.Position = position; // Optimization for the common case of one char inserts if (NewLength == 1) { return ((char)buffer.Read()).ToString(); } else { var builder = new StringBuilder(); for (int i = 0; i < length; i++) { char c = (char)buffer.Read(); builder.Append(c); if (Char.IsHighSurrogate(c)) { builder.Append((char)buffer.Read()); } } return builder.ToString(); } } finally { buffer.Position = oldPosition; } } public static bool operator ==(TextChange left, TextChange right) { return left.Equals(right); } public static bool operator !=(TextChange left, TextChange right) { return !left.Equals(right); } } }