Introduce SourceChange in place of TextChange
This is the first step on the journey to replacing RazorEditorParser. We can't just get rid of this because VS is using it today.
This commit is contained in:
parent
e6c8ea8341
commit
e15d1be616
|
|
@ -31,11 +31,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
public string AutoCompleteString { get; set; }
|
||||
|
||||
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
|
||||
protected override PartialParseResult CanAcceptChange(Span target, SourceChange change)
|
||||
{
|
||||
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, normalizedChange)) || IsAtEndOfFirstLine(target, normalizedChange)) &&
|
||||
normalizedChange.IsInsert &&
|
||||
ParserHelpers.IsNewLine(normalizedChange.NewText) &&
|
||||
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, change)) || IsAtEndOfFirstLine(target, change)) &&
|
||||
change.IsInsert &&
|
||||
ParserHelpers.IsNewLine(change.NewText) &&
|
||||
AutoCompleteString != null)
|
||||
{
|
||||
return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
_main.Cancel();
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public void QueueChange(TextChange change)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
_main.QueueChange(change);
|
||||
}
|
||||
|
|
@ -67,37 +69,43 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
internal static bool TreesAreDifferent(RazorSyntaxTree leftTree, RazorSyntaxTree rightTree, IEnumerable<TextChange> changes)
|
||||
{
|
||||
return TreesAreDifferent(leftTree, rightTree, changes, CancellationToken.None);
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
internal static bool TreesAreDifferent(RazorSyntaxTree leftTree, RazorSyntaxTree rightTree, IEnumerable<TextChange> changes, CancellationToken cancelToken)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
return TreesAreDifferent(leftTree.Root, rightTree.Root, changes, cancelToken);
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<TextChange> changes)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
return TreesAreDifferent(leftTree, rightTree, changes, CancellationToken.None);
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<TextChange> changes, CancellationToken cancelToken)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
// Apply all the pending changes to the original tree
|
||||
// PERF: If this becomes a bottleneck, we can probably do it the other way around,
|
||||
// i.e. visit the tree and find applicable changes for each node.
|
||||
foreach (TextChange change in changes)
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
foreach (var change in changes)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
cancelToken.ThrowIfCancellationRequested();
|
||||
var changeOwner = leftTree.LocateOwner(change);
|
||||
|
||||
var sourceChange = change.AsSourceChange();
|
||||
var changeOwner = leftTree.LocateOwner(sourceChange);
|
||||
|
||||
// Apply the change to the tree
|
||||
if (changeOwner == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var result = changeOwner.EditHandler.ApplyChange(changeOwner, change, force: true);
|
||||
|
||||
var result = changeOwner.EditHandler.ApplyChange(changeOwner, sourceChange, force: true);
|
||||
changeOwner.ReplaceWith(result.EditedSpan);
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +158,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
private string _fileName;
|
||||
private readonly object _stateLock = new object();
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
private IList<TextChange> _changes = new List<TextChange>();
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public MainThreadState(string fileName)
|
||||
{
|
||||
|
|
@ -189,7 +199,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return new DisposableAction(() => Monitor.Exit(_stateLock));
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public void QueueChange(TextChange change)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
EnsureOnThread();
|
||||
lock (_stateLock)
|
||||
|
|
@ -216,7 +228,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
_currentParcelCancelSource = new CancellationTokenSource();
|
||||
|
||||
var changes = _changes;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
_changes = new List<TextChange>();
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
return new WorkParcel(changes, _currentParcelCancelSource.Token);
|
||||
}
|
||||
}
|
||||
|
|
@ -274,7 +288,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
private RazorTemplateEngine _templateEngine;
|
||||
private string _fileName;
|
||||
private RazorSyntaxTree _currentSyntaxTree;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
private IList<TextChange> _previouslyDiscarded = new List<TextChange>();
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
public BackgroundThread(MainThreadState main, RazorTemplateEngine templateEngine, string fileName)
|
||||
{
|
||||
|
|
@ -321,7 +337,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
if (!linkedCancel.IsCancellationRequested)
|
||||
{
|
||||
// Collect ALL changes
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
List<TextChange> allChanges;
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
||||
if (_previouslyDiscarded != null)
|
||||
{
|
||||
|
|
@ -411,14 +429,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
private class WorkParcel
|
||||
{
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public WorkParcel(IList<TextChange> changes, CancellationToken cancelToken)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
Changes = changes;
|
||||
CancelToken = cancelToken;
|
||||
}
|
||||
|
||||
public CancellationToken CancelToken { get; private set; }
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public IList<TextChange> Changes { get; private set; }
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
|
|
|||
|
|
@ -102,9 +102,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return current as Span;
|
||||
}
|
||||
|
||||
public virtual Span LocateOwner(TextChange change) => LocateOwner(change, Children);
|
||||
public virtual Span LocateOwner(SourceChange change) => LocateOwner(change, Children);
|
||||
|
||||
protected static Span LocateOwner(TextChange change, IEnumerable<SyntaxTreeNode> elements)
|
||||
protected static Span LocateOwner(SourceChange change, IEnumerable<SyntaxTreeNode> elements)
|
||||
{
|
||||
// Ask each child recursively
|
||||
Span owner = null;
|
||||
|
|
@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
else
|
||||
{
|
||||
if (change.OldPosition < span.Start.AbsoluteIndex)
|
||||
if (change.Span.AbsoluteIndex < span.Start.AbsoluteIndex)
|
||||
{
|
||||
// Early escape for cases where changes overlap multiple spans
|
||||
// In those cases, the span will return false, and we don't want to search the whole tree
|
||||
|
|
@ -134,6 +134,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
return owner;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(
|
||||
|
|
|
|||
|
|
@ -20,9 +20,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
/// </summary>
|
||||
public RazorCodeDocument GeneratorResults { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The TextChange which triggered the re-parse
|
||||
/// </summary>
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
public TextChange SourceChange { get; set; }
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return hashCodeCombiner;
|
||||
}
|
||||
|
||||
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
|
||||
protected override PartialParseResult CanAcceptChange(Span target, SourceChange change)
|
||||
{
|
||||
if (AcceptedCharacters == AcceptedCharacters.Any)
|
||||
{
|
||||
|
|
@ -75,21 +75,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
// 1. '@foo.' -> '@foobaz.'.
|
||||
// 2. '@foobaz..' -> '@foobaz.bar.'. Includes Sub-cases '@foobaz()..' -> '@foobaz().bar.' etc.
|
||||
// The key distinction being the double '.' in the second case.
|
||||
if (IsDotlessCommitInsertion(target, normalizedChange))
|
||||
if (IsDotlessCommitInsertion(target, change))
|
||||
{
|
||||
return HandleDotlessCommitInsertion(target);
|
||||
}
|
||||
|
||||
if (IsAcceptableIdentifierReplacement(target, normalizedChange))
|
||||
if (IsAcceptableIdentifierReplacement(target, change))
|
||||
{
|
||||
return TryAcceptChange(target, normalizedChange);
|
||||
return TryAcceptChange(target, change);
|
||||
}
|
||||
|
||||
if (IsAcceptableReplace(target, normalizedChange))
|
||||
if (IsAcceptableReplace(target, change))
|
||||
{
|
||||
return HandleReplacement(target, normalizedChange);
|
||||
return HandleReplacement(target, change);
|
||||
}
|
||||
var changeRelativePosition = normalizedChange.OldPosition - target.Start.AbsoluteIndex;
|
||||
var changeRelativePosition = change.Span.AbsoluteIndex - target.Start.AbsoluteIndex;
|
||||
|
||||
// Get the edit context
|
||||
char? lastChar = null;
|
||||
|
|
@ -105,57 +105,57 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Accepts cases when insertions are made at the end of a span or '.' is inserted within a span.
|
||||
if (IsAcceptableInsertion(target, normalizedChange))
|
||||
if (IsAcceptableInsertion(target, change))
|
||||
{
|
||||
// Handle the insertion
|
||||
return HandleInsertion(target, lastChar.Value, normalizedChange);
|
||||
return HandleInsertion(target, lastChar.Value, change);
|
||||
}
|
||||
|
||||
if (IsAcceptableDeletion(target, normalizedChange))
|
||||
if (IsAcceptableDeletion(target, change))
|
||||
{
|
||||
return HandleDeletion(target, lastChar.Value, normalizedChange);
|
||||
return HandleDeletion(target, lastChar.Value, change);
|
||||
}
|
||||
|
||||
return PartialParseResult.Rejected;
|
||||
}
|
||||
|
||||
// A dotless commit is the process of inserting a '.' with an intellisense selection.
|
||||
private static bool IsDotlessCommitInsertion(Span target, TextChange change)
|
||||
private static bool IsDotlessCommitInsertion(Span target, SourceChange change)
|
||||
{
|
||||
return IsNewDotlessCommitInsertion(target, change) || IsSecondaryDotlessCommitInsertion(target, change);
|
||||
}
|
||||
|
||||
// Completing 'DateTime' in intellisense with a '.' could result in: '@DateT' -> '@DateT.' -> '@DateTime.' which is accepted.
|
||||
private static bool IsNewDotlessCommitInsertion(Span target, TextChange change)
|
||||
private static bool IsNewDotlessCommitInsertion(Span target, SourceChange change)
|
||||
{
|
||||
return !IsAtEndOfSpan(target, change) &&
|
||||
change.NewPosition > 0 &&
|
||||
change.NewLength > 0 &&
|
||||
change.Span.AbsoluteIndex > 0 &&
|
||||
change.NewText.Length > 0 &&
|
||||
target.Content.Last() == '.' &&
|
||||
ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false) &&
|
||||
(change.OldLength == 0 || ParserHelpers.IsIdentifier(change.OldText, requireIdentifierStart: false));
|
||||
(change.Span.Length == 0 || ParserHelpers.IsIdentifier(change.GetOriginalText(target), requireIdentifierStart: false));
|
||||
}
|
||||
|
||||
// Once a dotless commit has been performed you then have something like '@DateTime.'. This scenario is used to detect the
|
||||
// situation when you try to perform another dotless commit resulting in a textchange with '..'. Completing 'DateTime.Now'
|
||||
// in intellisense with a '.' could result in: '@DateTime.' -> '@DateTime..' -> '@DateTime.Now.' which is accepted.
|
||||
private static bool IsSecondaryDotlessCommitInsertion(Span target, TextChange change)
|
||||
private static bool IsSecondaryDotlessCommitInsertion(Span target, SourceChange change)
|
||||
{
|
||||
// Do not need to worry about other punctuation, just looking for double '.' (after change)
|
||||
return change.NewLength == 1 &&
|
||||
return change.NewText.Length == 1 &&
|
||||
change.NewText == "." &&
|
||||
!string.IsNullOrEmpty(target.Content) &&
|
||||
target.Content.Last() == '.' &&
|
||||
change.NewText == "." &&
|
||||
change.OldLength == 0;
|
||||
change.Span.Length == 0;
|
||||
}
|
||||
|
||||
private static bool IsAcceptableReplace(Span target, TextChange change)
|
||||
private static bool IsAcceptableReplace(Span target, SourceChange change)
|
||||
{
|
||||
return IsEndReplace(target, change) ||
|
||||
(change.IsReplace && RemainingIsWhitespace(target, change));
|
||||
}
|
||||
|
||||
private bool IsAcceptableIdentifierReplacement(Span target, TextChange change)
|
||||
private bool IsAcceptableIdentifierReplacement(Span target, SourceChange change)
|
||||
{
|
||||
if (!change.IsReplace)
|
||||
{
|
||||
|
|
@ -174,15 +174,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
var symbolStartIndex = symbol.Start.AbsoluteIndex;
|
||||
var symbolEndIndex = symbolStartIndex + symbol.Content.Length;
|
||||
|
||||
// We're looking for the first symbol that contains the TextChange.
|
||||
if (symbolEndIndex > change.OldPosition)
|
||||
// We're looking for the first symbol that contains the SourceChange.
|
||||
if (symbolEndIndex > change.Span.AbsoluteIndex)
|
||||
{
|
||||
if (symbolEndIndex >= change.OldPosition + change.OldLength && symbol.Type == CSharpSymbolType.Identifier)
|
||||
if (symbolEndIndex >= change.Span.AbsoluteIndex + change.Span.Length && symbol.Type == CSharpSymbolType.Identifier)
|
||||
{
|
||||
// The symbol we're changing happens to be an identifier. Need to check if its transformed state is also one.
|
||||
// We do this transformation logic to capture the case that the new text change happens to not be an identifier;
|
||||
// i.e. "5". Alone, it's numeric, within an identifier it's classified as identifier.
|
||||
var transformedContent = change.ApplyChange(symbol.Content, symbolStartIndex);
|
||||
var transformedContent = change.GetEditedContent(symbol.Content, change.Span.AbsoluteIndex - symbolStartIndex);
|
||||
var newSymbols = Tokenizer(transformedContent);
|
||||
|
||||
if (newSymbols.Count() != 1)
|
||||
|
|
@ -208,14 +208,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool IsAcceptableDeletion(Span target, TextChange change)
|
||||
private static bool IsAcceptableDeletion(Span target, SourceChange change)
|
||||
{
|
||||
return IsEndDeletion(target, change) ||
|
||||
(change.IsDelete && RemainingIsWhitespace(target, change));
|
||||
}
|
||||
|
||||
// Acceptable insertions can occur at the end of a span or when a '.' is inserted within a span.
|
||||
private static bool IsAcceptableInsertion(Span target, TextChange change)
|
||||
private static bool IsAcceptableInsertion(Span target, SourceChange change)
|
||||
{
|
||||
return change.IsInsert &&
|
||||
(IsAcceptableEndInsertion(target, change) ||
|
||||
|
|
@ -223,7 +223,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Accepts character insertions at the end of spans. AKA: '@foo' -> '@fooo' or '@foo' -> '@foo ' etc.
|
||||
private static bool IsAcceptableEndInsertion(Span target, TextChange change)
|
||||
private static bool IsAcceptableEndInsertion(Span target, SourceChange change)
|
||||
{
|
||||
Debug.Assert(change.IsInsert);
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
|
||||
// Accepts '.' insertions in the middle of spans. Ex: '@foo.baz.bar' -> '@foo..baz.bar'
|
||||
// This is meant to allow intellisense when editing a span.
|
||||
private static bool IsAcceptableInnerInsertion(Span target, TextChange change)
|
||||
private static bool IsAcceptableInnerInsertion(Span target, SourceChange change)
|
||||
{
|
||||
Debug.Assert(change.IsInsert);
|
||||
|
||||
|
|
@ -241,13 +241,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
// This case will fail if the IsAcceptableEndInsertion does not capture an end insertion correctly.
|
||||
Debug.Assert(!IsAtEndOfSpan(target, change));
|
||||
|
||||
return change.NewPosition > 0 &&
|
||||
return change.Span.AbsoluteIndex > 0 &&
|
||||
change.NewText == ".";
|
||||
}
|
||||
|
||||
private static bool RemainingIsWhitespace(Span target, TextChange change)
|
||||
private static bool RemainingIsWhitespace(Span target, SourceChange change)
|
||||
{
|
||||
var offset = (change.OldPosition - target.Start.AbsoluteIndex) + change.OldLength;
|
||||
var offset = (change.Span.AbsoluteIndex - target.Start.AbsoluteIndex) + change.Span.Length;
|
||||
return string.IsNullOrWhiteSpace(target.Content.Substring(offset));
|
||||
}
|
||||
|
||||
|
|
@ -261,14 +261,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return result;
|
||||
}
|
||||
|
||||
private PartialParseResult HandleReplacement(Span target, TextChange change)
|
||||
private PartialParseResult HandleReplacement(Span target, SourceChange change)
|
||||
{
|
||||
// Special Case for IntelliSense commits.
|
||||
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
|
||||
// 1. Insert "." at the end of this span
|
||||
// 2. Replace the "Date." at the end of the span with "DateTime."
|
||||
// We need partial parsing to accept case #2.
|
||||
var oldText = GetOldText(target, change);
|
||||
var oldText = change.GetOriginalText(target);
|
||||
|
||||
var result = PartialParseResult.Rejected;
|
||||
if (EndsWithDot(oldText) && EndsWithDot(change.NewText))
|
||||
|
|
@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return result;
|
||||
}
|
||||
|
||||
private PartialParseResult HandleDeletion(Span target, char previousChar, TextChange change)
|
||||
private PartialParseResult HandleDeletion(Span target, char previousChar, SourceChange change)
|
||||
{
|
||||
// What's left after deleting?
|
||||
if (previousChar == '.')
|
||||
|
|
@ -299,7 +299,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
private PartialParseResult HandleInsertion(Span target, char previousChar, TextChange change)
|
||||
private PartialParseResult HandleInsertion(Span target, char previousChar, SourceChange change)
|
||||
{
|
||||
// What are we inserting after?
|
||||
if (previousChar == '.')
|
||||
|
|
@ -316,7 +316,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
private PartialParseResult HandleInsertionAfterIdPart(Span target, TextChange change)
|
||||
private PartialParseResult HandleInsertionAfterIdPart(Span target, SourceChange change)
|
||||
{
|
||||
// If the insertion is a full identifier part, accept it
|
||||
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
|
||||
|
|
@ -346,7 +346,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
|
||||
}
|
||||
|
||||
private PartialParseResult HandleInsertionAfterDot(Span target, TextChange change)
|
||||
private PartialParseResult HandleInsertionAfterDot(Span target, SourceChange change)
|
||||
{
|
||||
// If the insertion is a full identifier or another dot, accept it
|
||||
if (ParserHelpers.IsIdentifier(change.NewText) || change.NewText == ".")
|
||||
|
|
@ -356,9 +356,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return PartialParseResult.Rejected;
|
||||
}
|
||||
|
||||
private PartialParseResult TryAcceptChange(Span target, TextChange change, PartialParseResult acceptResult = PartialParseResult.Accepted)
|
||||
private PartialParseResult TryAcceptChange(Span target, SourceChange change, PartialParseResult acceptResult = PartialParseResult.Accepted)
|
||||
{
|
||||
var content = change.ApplyChange(target.Content, target.Start.AbsoluteIndex);
|
||||
var content = change.GetEditedContent(target);
|
||||
if (StartsWithKeyword(content))
|
||||
{
|
||||
return PartialParseResult.Rejected | PartialParseResult.SpanContextChanged;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
#pragma warning disable CS0618 // TextChange is Obsolete
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -118,6 +118,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return null;
|
||||
}
|
||||
|
||||
// Type or member is obsolete
|
||||
/// <summary>
|
||||
/// Determines if a change will cause a structural change to the document and if not, applies it to the
|
||||
/// existing tree. If a structural change would occur, automatically starts a reparse.
|
||||
|
|
@ -128,7 +129,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
/// </remarks>
|
||||
/// <param name="change">The change to apply to the parse tree.</param>
|
||||
/// <returns>A <see cref="PartialParseResult"/> value indicating the result of the incremental parse.</returns>
|
||||
#pragma warning disable CS0612
|
||||
public virtual PartialParseResult CheckForStructureChanges(TextChange change)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
var result = PartialParseResult.Rejected;
|
||||
|
||||
|
|
@ -171,14 +174,18 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
private PartialParseResult TryPartialParse(TextChange change)
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
{
|
||||
var sourceChange = change.AsSourceChange();
|
||||
|
||||
var result = PartialParseResult.Rejected;
|
||||
|
||||
// Try the last change owner
|
||||
if (_lastChangeOwner != null && _lastChangeOwner.EditHandler.OwnsChange(_lastChangeOwner, change))
|
||||
if (_lastChangeOwner != null && _lastChangeOwner.EditHandler.OwnsChange(_lastChangeOwner, sourceChange))
|
||||
{
|
||||
var editResult = _lastChangeOwner.EditHandler.ApplyChange(_lastChangeOwner, change);
|
||||
var editResult = _lastChangeOwner.EditHandler.ApplyChange(_lastChangeOwner, sourceChange);
|
||||
result = editResult.Result;
|
||||
if ((editResult.Result & PartialParseResult.Rejected) != PartialParseResult.Rejected)
|
||||
{
|
||||
|
|
@ -189,7 +196,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
|
||||
// Locate the span responsible for this change
|
||||
_lastChangeOwner = CurrentSyntaxTree.Root.LocateOwner(change);
|
||||
_lastChangeOwner = CurrentSyntaxTree.Root.LocateOwner(sourceChange);
|
||||
|
||||
if (LastResultProvisional)
|
||||
{
|
||||
|
|
@ -198,7 +205,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
else if (_lastChangeOwner != null)
|
||||
{
|
||||
var editResult = _lastChangeOwner.EditHandler.ApplyChange(_lastChangeOwner, change);
|
||||
var editResult = _lastChangeOwner.EditHandler.ApplyChange(_lastChangeOwner, sourceChange);
|
||||
result = editResult.Result;
|
||||
if ((editResult.Result & PartialParseResult.Rejected) != PartialParseResult.Rejected)
|
||||
{
|
||||
|
|
@ -258,3 +265,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
|
|
@ -30,49 +31,48 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return new SpanEditHandler(tokenizer);
|
||||
}
|
||||
|
||||
public virtual EditResult ApplyChange(Span target, TextChange change)
|
||||
public virtual EditResult ApplyChange(Span target, SourceChange change)
|
||||
{
|
||||
return ApplyChange(target, change, force: false);
|
||||
}
|
||||
|
||||
public virtual EditResult ApplyChange(Span target, TextChange change, bool force)
|
||||
public virtual EditResult ApplyChange(Span target, SourceChange change, bool force)
|
||||
{
|
||||
var result = PartialParseResult.Accepted;
|
||||
var normalized = change.Normalize();
|
||||
if (!force)
|
||||
{
|
||||
result = CanAcceptChange(target, normalized);
|
||||
result = CanAcceptChange(target, change);
|
||||
}
|
||||
|
||||
// If the change is accepted then apply the change
|
||||
if ((result & PartialParseResult.Accepted) == PartialParseResult.Accepted)
|
||||
{
|
||||
return new EditResult(result, UpdateSpan(target, normalized));
|
||||
return new EditResult(result, UpdateSpan(target, change));
|
||||
}
|
||||
return new EditResult(result, new SpanBuilder(target));
|
||||
}
|
||||
|
||||
public virtual bool OwnsChange(Span target, TextChange change)
|
||||
public virtual bool OwnsChange(Span target, SourceChange change)
|
||||
{
|
||||
var end = target.Start.AbsoluteIndex + target.Length;
|
||||
var changeOldEnd = change.OldPosition + change.OldLength;
|
||||
return change.OldPosition >= target.Start.AbsoluteIndex &&
|
||||
var changeOldEnd = change.Span.AbsoluteIndex + change.Span.Length;
|
||||
return change.Span.AbsoluteIndex >= target.Start.AbsoluteIndex &&
|
||||
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharacters.None));
|
||||
}
|
||||
|
||||
protected virtual PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
|
||||
protected virtual PartialParseResult CanAcceptChange(Span target, SourceChange change)
|
||||
{
|
||||
return PartialParseResult.Rejected;
|
||||
}
|
||||
|
||||
protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange)
|
||||
protected virtual SpanBuilder UpdateSpan(Span target, SourceChange change)
|
||||
{
|
||||
var newContent = normalizedChange.ApplyChange(target.Content, target.Start.AbsoluteIndex);
|
||||
var newContent = change.GetEditedContent(target);
|
||||
var newSpan = new SpanBuilder(target);
|
||||
newSpan.ClearSymbols();
|
||||
foreach (ISymbol sym in Tokenizer(newContent))
|
||||
foreach (var token in Tokenizer(newContent))
|
||||
{
|
||||
newSpan.Accept(sym);
|
||||
newSpan.Accept(token);
|
||||
}
|
||||
if (target.Next != null)
|
||||
{
|
||||
|
|
@ -82,16 +82,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return newSpan;
|
||||
}
|
||||
|
||||
protected internal static bool IsAtEndOfFirstLine(Span target, TextChange change)
|
||||
protected internal static bool IsAtEndOfFirstLine(Span target, SourceChange change)
|
||||
{
|
||||
var endOfFirstLine = target.Content.IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
|
||||
return (endOfFirstLine == -1 || (change.OldPosition - target.Start.AbsoluteIndex) <= endOfFirstLine);
|
||||
return (endOfFirstLine == -1 || (change.Span.AbsoluteIndex - target.Start.AbsoluteIndex) <= endOfFirstLine);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the specified change is an insertion of text at the end of this span.
|
||||
/// </summary>
|
||||
protected internal static bool IsEndDeletion(Span target, TextChange change)
|
||||
protected internal static bool IsEndDeletion(Span target, SourceChange change)
|
||||
{
|
||||
return change.IsDelete && IsAtEndOfSpan(target, change);
|
||||
}
|
||||
|
|
@ -99,25 +99,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
/// <summary>
|
||||
/// Returns true if the specified change is a replacement of text at the end of this span.
|
||||
/// </summary>
|
||||
protected internal static bool IsEndReplace(Span target, TextChange change)
|
||||
protected internal static bool IsEndReplace(Span target, SourceChange change)
|
||||
{
|
||||
return change.IsReplace && IsAtEndOfSpan(target, change);
|
||||
}
|
||||
|
||||
protected internal static bool IsAtEndOfSpan(Span target, TextChange change)
|
||||
protected internal static bool IsAtEndOfSpan(Span target, SourceChange change)
|
||||
{
|
||||
return (change.OldPosition + change.OldLength) == (target.Start.AbsoluteIndex + target.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the old text referenced by the change.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the content has already been updated by applying the change, this data will be _invalid_
|
||||
/// </remarks>
|
||||
protected internal static string GetOldText(Span target, TextChange change)
|
||||
{
|
||||
return target.Content.Substring(change.OldPosition - target.Start.AbsoluteIndex, change.OldLength);
|
||||
return (change.Span.AbsoluteIndex + change.Span.Length) == (target.Start.AbsoluteIndex + target.Length);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
|
||||
public override Span LocateOwner(TextChange change)
|
||||
public override Span LocateOwner(SourceChange change)
|
||||
{
|
||||
var oldPosition = change.OldPosition;
|
||||
var oldPosition = change.Span.AbsoluteIndex;
|
||||
if (oldPosition < Start.AbsoluteIndex)
|
||||
{
|
||||
// Change occurs prior to the TagHelper.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ using Microsoft.Extensions.Internal;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
[Obsolete] // Superseded by SourceChange
|
||||
public struct TextChange
|
||||
{
|
||||
private string _newText;
|
||||
|
|
@ -127,6 +128,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
get { return OldLength > 0 && NewLength > 0; }
|
||||
}
|
||||
|
||||
public SourceChange AsSourceChange()
|
||||
{
|
||||
var normalized = Normalize();
|
||||
return new SourceChange(
|
||||
new SourceSpan(
|
||||
filePath: null,
|
||||
absoluteIndex: normalized.OldPosition,
|
||||
lineIndex: -1,
|
||||
characterIndex: -1,
|
||||
length: normalized.OldLength),
|
||||
normalized.NewText);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is TextChange))
|
||||
|
|
@ -156,15 +170,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return hashCodeCombiner;
|
||||
}
|
||||
|
||||
public string ApplyChange(string content, int changeOffset)
|
||||
{
|
||||
var changeRelativePosition = OldPosition - changeOffset;
|
||||
|
||||
Debug.Assert(changeRelativePosition >= 0);
|
||||
return content.Remove(changeRelativePosition, OldLength)
|
||||
.Insert(changeRelativePosition, NewText);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, "({0}:{1}) \"{3}\" -> ({0}:{2}) \"{4}\"", OldPosition, OldLength, NewLength, OldText, NewText);
|
||||
|
|
|
|||
|
|
@ -374,6 +374,20 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
internal static string FormatInvalidTargetedTagNameNullOrWhitespace()
|
||||
=> GetString("InvalidTargetedTagNameNullOrWhitespace");
|
||||
|
||||
/// <summary>
|
||||
/// The node '{0}' is not the owner of change '{1}'.
|
||||
/// </summary>
|
||||
internal static string InvalidOperation_SpanIsNotChangeOwner
|
||||
{
|
||||
get => GetString("InvalidOperation_SpanIsNotChangeOwner");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The node '{0}' is not the owner of change '{1}'.
|
||||
/// </summary>
|
||||
internal static string FormatInvalidOperation_SpanIsNotChangeOwner(object p0, object p1)
|
||||
=> string.Format(CultureInfo.CurrentCulture, GetString("InvalidOperation_SpanIsNotChangeOwner"), p0, p1);
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -195,4 +195,7 @@
|
|||
<data name="InvalidTargetedTagNameNullOrWhitespace" xml:space="preserve">
|
||||
<value>Targeted tag name cannot be null or whitespace.</value>
|
||||
</data>
|
||||
<data name="InvalidOperation_SpanIsNotChangeOwner" xml:space="preserve">
|
||||
<value>The node '{0}' is not the owner of change '{1}'.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Microsoft.Extensions.Internal;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public sealed class SourceChange : IEquatable<SourceChange>
|
||||
{
|
||||
public SourceChange(int absoluteIndex, int length, string newText)
|
||||
{
|
||||
if (absoluteIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
|
||||
}
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
}
|
||||
|
||||
if (newText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newText));
|
||||
}
|
||||
|
||||
Span = new SourceSpan(absoluteIndex, length);
|
||||
NewText = newText;
|
||||
}
|
||||
|
||||
public SourceChange(SourceSpan span, string newText)
|
||||
{
|
||||
if (newText == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newText));
|
||||
}
|
||||
|
||||
Span = span;
|
||||
NewText = newText;
|
||||
}
|
||||
|
||||
public bool IsDelete => Span.Length > 0 && NewText.Length == 0;
|
||||
|
||||
public bool IsInsert => Span.Length == 0 && NewText.Length > 0;
|
||||
|
||||
public bool IsReplace => Span.Length > 0 && NewText.Length > 0;
|
||||
|
||||
public SourceSpan Span { get; }
|
||||
|
||||
public string NewText { get; }
|
||||
|
||||
internal string GetEditedContent(Span span)
|
||||
{
|
||||
if (span == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(span));
|
||||
}
|
||||
|
||||
var offset = GetOffset(span);
|
||||
return GetEditedContent(span.Content, offset);
|
||||
}
|
||||
|
||||
internal string GetEditedContent(string text, int offset)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
return text.Remove(offset, Span.Length).Insert(offset, NewText);
|
||||
}
|
||||
|
||||
internal int GetOffset(Span span)
|
||||
{
|
||||
if (span == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(span));
|
||||
}
|
||||
|
||||
var start = Span.AbsoluteIndex;
|
||||
var end = Span.AbsoluteIndex + Span.Length;
|
||||
|
||||
if (start < span.Start.AbsoluteIndex ||
|
||||
start > span.Start.AbsoluteIndex + span.Length ||
|
||||
end < span.Start.AbsoluteIndex ||
|
||||
end > span.Start.AbsoluteIndex + span.Length)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatInvalidOperation_SpanIsNotChangeOwner(span, this));
|
||||
}
|
||||
|
||||
return start - span.Start.AbsoluteIndex;
|
||||
}
|
||||
|
||||
internal string GetOriginalText(Span span)
|
||||
{
|
||||
if (span == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(span));
|
||||
}
|
||||
|
||||
if (span.Length == 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var offset = GetOffset(span);
|
||||
return span.Content.Substring(offset, Span.Length);
|
||||
}
|
||||
|
||||
public bool Equals(SourceChange other)
|
||||
{
|
||||
return
|
||||
other != null &&
|
||||
Span.Equals(other.Span) &&
|
||||
string.Equals(NewText, other.NewText, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as SourceChange);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCodeCombiner();
|
||||
hash.Add(Span);
|
||||
hash.Add(NewText, StringComparer.Ordinal);
|
||||
return hash;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Span.ToString() + " : " + NewText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,11 @@ namespace Microsoft.AspNetCore.Razor.Language
|
|||
{
|
||||
public struct SourceSpan : IEquatable<SourceSpan>
|
||||
{
|
||||
public SourceSpan(int absoluteIndex, int length)
|
||||
: this(null, absoluteIndex, -1, -1, length)
|
||||
{
|
||||
}
|
||||
|
||||
public SourceSpan(SourceLocation location, int contentLength)
|
||||
: this(location.FilePath, location.AbsoluteIndex, location.LineIndex, location.CharacterIndex, contentLength)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal static class TextChangeExtensions
|
||||
{
|
||||
public static SourceChange AsSourceChange(this TextChange textChange)
|
||||
{
|
||||
if (textChange == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(textChange));
|
||||
}
|
||||
|
||||
return new SourceChange(textChange.Span.AsSourceSpan(), textChange.NewText);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor
|
||||
{
|
||||
internal static class TextSpanExtensions
|
||||
{
|
||||
public static SourceSpan AsSourceSpan(this TextSpan textSpan)
|
||||
{
|
||||
return new SourceSpan(filePath: null, absoluteIndex: textSpan.Start, lineIndex: -1, characterIndex: -1, length: textSpan.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -171,8 +171,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
var trackingPoint = line.Snapshot.CreateTrackingPoint(line.End, PointTrackingMode.Negative);
|
||||
var previousLineEnd = trackingPoint.GetPosition(syntaxTreeSnapshot);
|
||||
|
||||
var razorBuffer = new ShimTextBufferAdapter(syntaxTreeSnapshot);
|
||||
var simulatedChange = new TextChange(previousLineEnd, 0, razorBuffer, previousLineEnd, 0, razorBuffer);
|
||||
var simulatedChange = new SourceChange(previousLineEnd, 0, string.Empty);
|
||||
var owningSpan = LocateOwner(syntaxTree.Root, simulatedChange);
|
||||
|
||||
int? desiredIndentation = null;
|
||||
|
|
@ -223,20 +222,20 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
return desiredIndentation;
|
||||
}
|
||||
|
||||
private Span LocateOwner(Block root, TextChange change)
|
||||
private Span LocateOwner(Block root, SourceChange change)
|
||||
{
|
||||
// Ask each child recursively
|
||||
Span owner = null;
|
||||
foreach (SyntaxTreeNode element in root.Children)
|
||||
{
|
||||
if (element.Start.AbsoluteIndex > change.OldPosition)
|
||||
if (element.Start.AbsoluteIndex > change.Span.AbsoluteIndex)
|
||||
{
|
||||
// too far
|
||||
break;
|
||||
}
|
||||
|
||||
int elementLen = element.Length;
|
||||
if (element.Start.AbsoluteIndex + elementLen < change.OldPosition)
|
||||
if (element.Start.AbsoluteIndex + elementLen < change.Span.AbsoluteIndex)
|
||||
{
|
||||
// not far enough
|
||||
continue;
|
||||
|
|
@ -246,7 +245,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
{
|
||||
Block block = element as Block;
|
||||
|
||||
if (element.Start.AbsoluteIndex + elementLen == change.OldPosition)
|
||||
if (element.Start.AbsoluteIndex + elementLen == change.Span.AbsoluteIndex)
|
||||
{
|
||||
Span lastDescendant = block.FindLastDescendentSpan();
|
||||
if ((lastDescendant == null) && (block is TagHelperBlock))
|
||||
|
|
@ -306,14 +305,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
{
|
||||
Block sourceStartTag = tagHelperNode.SourceStartTag;
|
||||
Block sourceEndTag = tagHelperNode.SourceEndTag;
|
||||
if ((sourceStartTag.Start.AbsoluteIndex <= change.OldPosition) &&
|
||||
(sourceStartTag.Start.AbsoluteIndex + sourceStartTag.Length >= change.OldPosition))
|
||||
if ((sourceStartTag.Start.AbsoluteIndex <= change.Span.AbsoluteIndex) &&
|
||||
(sourceStartTag.Start.AbsoluteIndex + sourceStartTag.Length >= change.Span.AbsoluteIndex))
|
||||
{
|
||||
// intersects the start tag
|
||||
return LocateOwner(sourceStartTag, change);
|
||||
}
|
||||
else if ((sourceEndTag.Start.AbsoluteIndex <= change.OldPosition) &&
|
||||
(sourceEndTag.Start.AbsoluteIndex + sourceEndTag.Length >= change.OldPosition))
|
||||
else if ((sourceEndTag.Start.AbsoluteIndex <= change.Span.AbsoluteIndex) &&
|
||||
(sourceEndTag.Start.AbsoluteIndex + sourceEndTag.Length >= change.Span.AbsoluteIndex))
|
||||
{
|
||||
// intersects the end tag
|
||||
return LocateOwner(sourceEndTag, change);
|
||||
|
|
@ -346,88 +345,5 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
|
|||
|
||||
return indentLevel;
|
||||
}
|
||||
|
||||
private class ShimTextBufferAdapter : ITextBuffer
|
||||
{
|
||||
public ITextSnapshot Snapshot { get; private set; }
|
||||
private int _position;
|
||||
private string _cachedText;
|
||||
private int _cachedPos;
|
||||
|
||||
public ShimTextBufferAdapter(ITextSnapshot snapshot)
|
||||
{
|
||||
Snapshot = snapshot;
|
||||
_cachedPos = -1;
|
||||
}
|
||||
|
||||
#region IRazorTextBuffer
|
||||
|
||||
int ITextBuffer.Length
|
||||
{
|
||||
get { return Length; }
|
||||
}
|
||||
|
||||
int ITextBuffer.Position
|
||||
{
|
||||
get { return _position; }
|
||||
set { _position = value; }
|
||||
}
|
||||
|
||||
int ITextBuffer.Read()
|
||||
{
|
||||
return Read();
|
||||
}
|
||||
|
||||
int ITextBuffer.Peek()
|
||||
{
|
||||
return Peek();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region private methods
|
||||
|
||||
private int Length
|
||||
{
|
||||
get { return Snapshot.Length; }
|
||||
}
|
||||
|
||||
private int Read()
|
||||
{
|
||||
if (_position >= Snapshot.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
int readVal = ReadChar();
|
||||
_position = _position + 1;
|
||||
|
||||
return readVal;
|
||||
}
|
||||
|
||||
private int Peek()
|
||||
{
|
||||
if (_position >= Snapshot.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ReadChar();
|
||||
}
|
||||
|
||||
private int ReadChar()
|
||||
{
|
||||
if ((_cachedPos < 0) || (_position < _cachedPos) || (_position >= _cachedPos + _cachedText.Length))
|
||||
{
|
||||
_cachedPos = _position;
|
||||
int cachedLen = Math.Min(1024, Snapshot.Length - _cachedPos);
|
||||
_cachedText = Snapshot.GetText(_cachedPos, cachedLen);
|
||||
}
|
||||
|
||||
return _cachedText[_position - _cachedPos];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Threading;
|
|||
using Microsoft.AspNetCore.Testing;
|
||||
using Xunit;
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
public class RazorEditorParserTest
|
||||
|
|
@ -1448,3 +1449,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||
{
|
||||
public class TextChangeTest
|
||||
|
|
@ -224,51 +225,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
Assert.Equal("bb", oldText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyChangeWithInsertedTextReturnsNewContentWithChangeApplied()
|
||||
{
|
||||
// Arrange
|
||||
var newBuffer = new StringTextBuffer("test");
|
||||
var oldBuffer = new StringTextBuffer("");
|
||||
var textChange = new TextChange(0, 0, oldBuffer, 3, newBuffer);
|
||||
|
||||
// Act
|
||||
var text = textChange.ApplyChange("abcd", 0);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("tesabcd", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyChangeWithRemovedTextReturnsNewContentWithChangeApplied()
|
||||
{
|
||||
// Arrange
|
||||
var newBuffer = new StringTextBuffer("abcdefg");
|
||||
var oldBuffer = new StringTextBuffer("");
|
||||
var textChange = new TextChange(1, 1, oldBuffer, 0, newBuffer);
|
||||
|
||||
// Act
|
||||
var text = textChange.ApplyChange("abcdefg", 1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("bcdefg", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ApplyChangeWithReplacedTextReturnsNewContentWithChangeApplied()
|
||||
{
|
||||
// Arrange
|
||||
var newBuffer = new StringTextBuffer("abcdefg");
|
||||
var oldBuffer = new StringTextBuffer("");
|
||||
var textChange = new TextChange(1, 1, oldBuffer, 2, newBuffer);
|
||||
|
||||
// Act
|
||||
var text = textChange.ApplyChange("abcdefg", 1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("bcbcdefg", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeFixesUpIntelliSenseStyleReplacements()
|
||||
{
|
||||
|
|
@ -315,3 +271,4 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language
|
||||
{
|
||||
public class SourceChangeTest
|
||||
{
|
||||
[Fact]
|
||||
public void SourceChange_ConstructorSetsDefaults_WhenNotProvided()
|
||||
{
|
||||
// Arrange & Act
|
||||
var change = new SourceChange(15, 7, "Hello");
|
||||
|
||||
// Assert
|
||||
Assert.Equal(15, change.Span.AbsoluteIndex);
|
||||
Assert.Equal(-1, change.Span.CharacterIndex);
|
||||
Assert.Null(change.Span.FilePath);
|
||||
Assert.Equal(7, change.Span.Length);
|
||||
Assert.Equal(-1, change.Span.LineIndex);
|
||||
Assert.Equal("Hello", change.NewText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsDelete_IsTrue_WhenOldLengthIsPositive_AndNewLengthIsZero()
|
||||
{
|
||||
// Arrange & Act
|
||||
var change = new SourceChange(3, 5, string.Empty);
|
||||
|
||||
// Assert
|
||||
Assert.True(change.IsDelete);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsInsert_IsTrue_WhenOldLengthIsZero_AndNewLengthIsPositive()
|
||||
{
|
||||
// Arrange & Act
|
||||
var change = new SourceChange(3, 0, "Hello");
|
||||
|
||||
// Assert
|
||||
Assert.True(change.IsInsert);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsReplace_IsTrue_WhenOldLengthIsPositive_AndNewLengthIsPositive()
|
||||
{
|
||||
// Arrange & Act
|
||||
var change = new SourceChange(3, 5, "Hello");
|
||||
|
||||
// Assert
|
||||
Assert.True(change.IsReplace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditedContent_ForDelete_ReturnsNewContent()
|
||||
{
|
||||
// Arrange
|
||||
var text = "Hello, World";
|
||||
|
||||
var change = new SourceChange(2, 2, string.Empty);
|
||||
|
||||
// Act
|
||||
var result = change.GetEditedContent(text, 1);
|
||||
|
||||
// Act
|
||||
Assert.Equal("Hlo, World", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditedContent_ForInsert_ReturnsNewContent()
|
||||
{
|
||||
// Arrange
|
||||
var text = "Hello, World";
|
||||
|
||||
var change = new SourceChange(2, 0, "heyo");
|
||||
|
||||
// Act
|
||||
var result = change.GetEditedContent(text, 1);
|
||||
|
||||
// Act
|
||||
Assert.Equal("Hheyoello, World", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditedContent_ForReplace_ReturnsNewContent()
|
||||
{
|
||||
// Arrange
|
||||
var text = "Hello, World";
|
||||
|
||||
var change = new SourceChange(2, 2, "heyo");
|
||||
|
||||
// Act
|
||||
var result = change.GetEditedContent(text, 1);
|
||||
|
||||
// Act
|
||||
Assert.Equal("Hheyolo, World", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEditedContent_Span_ReturnsNewContent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new SpanBuilder(new SourceLocation(0, 0, 0));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(0, 0, 0), "Hello, "));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(7, 0, 7), "World"));
|
||||
|
||||
var span = new Span(builder);
|
||||
|
||||
var change = new SourceChange(2, 2, "heyo");
|
||||
|
||||
// Act
|
||||
var result = change.GetEditedContent(span);
|
||||
|
||||
// Act
|
||||
Assert.Equal("Heheyoo, World", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOffSet_SpanIsOwner_ReturnsOffset()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new SpanBuilder(new SourceLocation(13, 0, 0));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));
|
||||
|
||||
var span = new Span(builder);
|
||||
|
||||
var change = new SourceChange(15, 2, "heyo");
|
||||
|
||||
// Act
|
||||
var result = change.GetOffset(span);
|
||||
|
||||
// Act
|
||||
Assert.Equal(2, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(12, 2)]
|
||||
[InlineData(12, 14)]
|
||||
[InlineData(13, 13)]
|
||||
[InlineData(13, 13)]
|
||||
[InlineData(20, 1)]
|
||||
[InlineData(21, 0)]
|
||||
public void GetOffSet_SpanIsNotOwnerOfChange_ThrowsException(int absoluteIndex, int length)
|
||||
{
|
||||
// Arrange
|
||||
var builder = new SpanBuilder(new SourceLocation(13, 0, 0));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));
|
||||
|
||||
var span = new Span(builder);
|
||||
|
||||
var change = new SourceChange(12, 2, "heyo");
|
||||
|
||||
var expected = $"The node '{span}' is not the owner of change '{change}'.";
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<InvalidOperationException>(() => { change.GetOffset(span); });
|
||||
Assert.Equal(expected, exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrigninalText_SpanIsOwner_ReturnsContent()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new SpanBuilder(new SourceLocation(13, 0, 0));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));
|
||||
|
||||
var span = new Span(builder);
|
||||
|
||||
var change = new SourceChange(15, 2, "heyo");
|
||||
|
||||
// Act
|
||||
var result = change.GetOriginalText(span);
|
||||
|
||||
// Act
|
||||
Assert.Equal("ll", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetOrigninalText_SpanIsOwner_ReturnsContent_ZeroLengthSpan()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new SpanBuilder(new SourceLocation(13, 0, 0));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
|
||||
builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));
|
||||
|
||||
var span = new Span(builder);
|
||||
|
||||
var change = new SourceChange(15, 0, "heyo");
|
||||
|
||||
// Act
|
||||
var result = change.GetOriginalText(span);
|
||||
|
||||
// Act
|
||||
Assert.Equal(string.Empty, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue