public virtual bool OwnsChange(Span target, TextChange change) { int end = target.Start.AbsoluteIndex + target.Length; int changeOldEnd = change.OldPosition + change.OldLength; return change.OldPosition >= target.Start.AbsoluteIndex && (changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharacters.None)); }
internal static void WriteDebugTree(string sourceFile, Block document, PartialParseResult result, TextChange change, RazorEditorParser parser, bool treeStructureChanged) { if (!OutputDebuggingEnabled) { return; } RunTask(() => { string outputFileName = Normalize(sourceFile) + "_tree"; string outputPath = Path.Combine(Path.GetDirectoryName(sourceFile), outputFileName); var treeBuilder = new StringBuilder(); WriteTree(document, treeBuilder); treeBuilder.AppendLine(); treeBuilder.AppendFormat(CultureInfo.CurrentCulture, "Last Change: {0}", change); treeBuilder.AppendLine(); treeBuilder.AppendFormat(CultureInfo.CurrentCulture, "Normalized To: {0}", change.Normalize()); treeBuilder.AppendLine(); treeBuilder.AppendFormat(CultureInfo.CurrentCulture, "Partial Parse Result: {0}", result); treeBuilder.AppendLine(); if (result.HasFlag(PartialParseResult.Rejected)) { treeBuilder.AppendFormat(CultureInfo.CurrentCulture, "Tree Structure Changed: {0}", treeStructureChanged); treeBuilder.AppendLine(); } if (result.HasFlag(PartialParseResult.AutoCompleteBlock)) { treeBuilder.AppendFormat(CultureInfo.CurrentCulture, "Auto Complete Insert String: \"{0}\"", parser.GetAutoCompleteString()); treeBuilder.AppendLine(); } File.WriteAllText(outputPath, treeBuilder.ToString()); }); }
public void TestIsDelete() { // Arrange ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object; ITextBuffer newBuffer = new Mock<ITextBuffer>().Object; TextChange change = new TextChange(0, 1, oldBuffer, 0, newBuffer); // Assert Assert.True(change.IsDelete); }
public void TestDeleteCreatesTheRightSizeChange() { // Arrange ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object; ITextBuffer newBuffer = new Mock<ITextBuffer>().Object; TextChange change = new TextChange(0, 1, oldBuffer, 0, newBuffer); // Assert Assert.Equal(0, change.NewText.Length); Assert.Equal(1, change.OldText.Length); }
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange) { if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, normalizedChange)) || IsAtEndOfFirstLine(target, normalizedChange)) && normalizedChange.IsInsert && ParserHelpers.IsNewLine(normalizedChange.NewText) && AutoCompleteString != null) { return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock; } return PartialParseResult.Rejected; }
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange) { if (AcceptedCharacters == AcceptedCharacters.Any) { return PartialParseResult.Rejected; } // In some editors intellisense insertions are handled as "dotless commits". If an intellisense selection is confirmed // via something like '.' a dotless commit will append a '.' and then insert the remaining intellisense selection prior // to the appended '.'. This 'if' statement attempts to accept the intermediate steps of a dotless commit via // intellisense. It will accept two cases: // 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)) { return HandleDotlessCommitInsertion(target); } if (IsAcceptableReplace(target, normalizedChange)) { return HandleReplacement(target, normalizedChange); } var changeRelativePosition = normalizedChange.OldPosition - target.Start.AbsoluteIndex; // Get the edit context char? lastChar = null; if (changeRelativePosition > 0 && target.Content.Length > 0) { lastChar = target.Content[changeRelativePosition - 1]; } // Don't support 0->1 length edits if (lastChar == null) { return PartialParseResult.Rejected; } // Accepts cases when insertions are made at the end of a span or '.' is inserted within a span. if (IsAcceptableInsertion(target, normalizedChange)) { // Handle the insertion return HandleInsertion(target, lastChar.Value, normalizedChange); } if (IsAcceptableDeletion(target, normalizedChange)) { return HandleDeletion(target, lastChar.Value, normalizedChange); } return PartialParseResult.Rejected; }
public void ConstructorInitializesProperties() { // Act ITextBuffer oldBuffer = new Mock<ITextBuffer>().Object; ITextBuffer newBuffer = new Mock<ITextBuffer>().Object; TextChange change = new TextChange(42, 24, oldBuffer, 1337, newBuffer); // Assert Assert.Equal(42, change.OldPosition); Assert.Equal(24, change.OldLength); Assert.Equal(1337, change.NewLength); Assert.Same(newBuffer, change.NewBuffer); Assert.Same(oldBuffer, change.OldBuffer); }
public virtual EditResult ApplyChange(Span target, TextChange change, bool force) { PartialParseResult result = PartialParseResult.Accepted; TextChange normalized = change.Normalize(); if (!force) { result = CanAcceptChange(target, normalized); } // If the change is accepted then apply the change if (result.HasFlag(PartialParseResult.Accepted)) { return new EditResult(result, UpdateSpan(target, normalized)); } return new EditResult(result, new SpanBuilder(target)); }
protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange) { string newContent = normalizedChange.ApplyChange(target); SpanBuilder newSpan = new SpanBuilder(target); newSpan.ClearSymbols(); foreach (ISymbol sym in Tokenizer(newContent)) { sym.OffsetStart(target.Start); newSpan.Accept(sym); } if (target.Next != null) { SourceLocation newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent); target.Next.ChangeStart(newEnd); } return newSpan; }
public void LocateOwnerReturnsNullIfNoSpanReturnsTrueForOwnsSpan() { // Arrange var factory = SpanFactory.CreateCsHtml(); var block = new MarkupBlock( factory.Markup("Foo "), new StatementBlock( factory.CodeTransition(), factory.Code("bar").AsStatement()), factory.Markup(" Baz")); var change = new TextChange(128, 1, new StringTextBuffer("Foo @bar Baz"), 1, new StringTextBuffer("Foo @bor Baz")); // Act var actual = block.LocateOwner(change); // Assert Assert.Null(actual); }
// Accepts character insertions at the end of spans. AKA: '@foo' -> '@fooo' or '@foo' -> '@foo ' etc. private static bool IsAcceptableEndInsertion(Span target, TextChange change) { Debug.Assert(change.IsInsert); return IsAtEndOfSpan(target, change) || 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) { return change.IsInsert && (IsAcceptableEndInsertion(target, change) || IsAcceptableInnerInsertion(target, change)); }
protected internal static bool IsAtEndOfSpan(Span target, TextChange change) { return (change.OldPosition + change.OldLength) == (target.Start.AbsoluteIndex + target.Length); }
private PartialParseResult HandleInsertionAfterDot(Span target, TextChange change) { // If the insertion is a full identifier or another dot, accept it if (ParserHelpers.IsIdentifier(change.NewText) || change.NewText == ".") { return TryAcceptChange(target, change); } return PartialParseResult.Rejected; }
private PartialParseResult HandleInsertion(Span target, char previousChar, TextChange change) { // What are we inserting after? if (previousChar == '.') { return HandleInsertionAfterDot(target, change); } else if (ParserHelpers.IsIdentifierPart(previousChar) || previousChar == ')' || previousChar == ']') { return HandleInsertionAfterIdPart(target, change); } else { return PartialParseResult.Rejected; } }
private PartialParseResult HandleReplacement(Span target, TextChange 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 result = PartialParseResult.Rejected; if (EndsWithDot(oldText) && EndsWithDot(change.NewText)) { result = PartialParseResult.Accepted; if (!AcceptTrailingDot) { result |= PartialParseResult.Provisional; } } return result; }
protected virtual PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange) { return PartialParseResult.Rejected; }
public virtual EditResult ApplyChange(Span target, TextChange change) { return ApplyChange(target, change, force: false); }
/// <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); }
private static bool IsAcceptableInnerInsertion(Span target, TextChange change) { Debug.Assert(change.IsInsert); // Ensure that we're actually inserting in the middle of a span and not at the end. // This case will fail if the IsAcceptableEndInsertion does not capture an end insertion correctly. Debug.Assert(!IsAtEndOfSpan(target, change)); return change.NewPosition > 0 && change.NewText == "."; }
private static bool RemainingIsWhitespace(Span target, TextChange change) { var offset = (change.OldPosition - target.Start.AbsoluteIndex) + change.OldLength; return String.IsNullOrWhiteSpace(target.Content.Substring(offset)); }
// A dotless commit is the process of inserting a '.' with an intellisense selection. private static bool IsDotlessCommitInsertion(Span target, TextChange change) { return IsNewDotlessCommitInsertion(target, change) || IsSecondaryDotlessCommitInsertion(target, change); }
private PartialParseResult HandleDeletion(Span target, char previousChar, TextChange change) { // What's left after deleting? if (previousChar == '.') { return TryAcceptChange(target, change, PartialParseResult.Accepted | PartialParseResult.Provisional); } else if (ParserHelpers.IsIdentifierPart(previousChar)) { return TryAcceptChange(target, change); } else { return PartialParseResult.Rejected; } }
// Completing 'DateTime' in intellisense with a '.' could result in: '@DateT' -> '@DateT.' -> '@DateTime.' which is accepted. private static bool IsNewDotlessCommitInsertion(Span target, TextChange change) { return !IsAtEndOfSpan(target, change) && change.NewPosition > 0 && change.NewLength > 0 && target.Content.Last() == '.' && ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false) && (change.OldLength == 0 || ParserHelpers.IsIdentifier(change.OldText, requireIdentifierStart: false)); }
private PartialParseResult HandleInsertionAfterIdPart(Span target, TextChange change) { // If the insertion is a full identifier part, accept it if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false)) { return TryAcceptChange(target, change); } else if (EndsWithDot(change.NewText)) { // Accept it, possibly provisionally var result = PartialParseResult.Accepted; if (!AcceptTrailingDot) { result |= PartialParseResult.Provisional; } return TryAcceptChange(target, change, result); } else { return PartialParseResult.Rejected; } }
private static bool IsAcceptableReplace(Span target, TextChange change) { return IsEndReplace(target, change) || (change.IsReplace && RemainingIsWhitespace(target, change)); }
private PartialParseResult TryAcceptChange(Span target, TextChange change, PartialParseResult acceptResult = PartialParseResult.Accepted) { var content = change.ApplyChange(target); if (StartsWithKeyword(content)) { return PartialParseResult.Rejected | PartialParseResult.SpanContextChanged; } return acceptResult; }
private static bool IsAcceptableDeletion(Span target, TextChange change) { return IsEndDeletion(target, change) || (change.IsDelete && RemainingIsWhitespace(target, change)); }
// 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) { // Do not need to worry about other punctuation, just looking for double '.' (after change) return change.NewLength == 1 && !String.IsNullOrEmpty(target.Content) && target.Content.Last() == '.' && change.NewText == "." && change.OldLength == 0; }
/// <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) { return change.IsReplace && IsAtEndOfSpan(target, change); }