private static bool IsInsideInterpolation(CSharp.CSharpSyntaxNode oldTree, int start) { var token = oldTree.FindToken(start, findInsideTrivia: false); for (var parent = token.Parent; // for each parent parent != null; parent = parent.Parent) { if (parent.Kind() == SyntaxKind.InterpolatedStringExpression) { return true; } } return false; }
/// <summary> /// Affected range of a change is the range within which nodes can be affected by a change /// and cannot be reused. Because of lookahead effective range of a change is larger than /// the change itself. /// </summary> private static TextChangeRange ExtendToAffectedRange( CSharp.CSharpSyntaxNode oldTree, TextChangeRange changeRange) { // we will increase affected range of the change by the number of lookahead tokens // original code in Blender seem to imply the lookahead at the end of a node is 1 token // max. TODO: 1 token lookahead seems a bit too optimistic. Increase if needed. const int maxLookahead = 1; // check if change is not after the end. TODO: there should be an assert somwhere about // changes starting at least at the End of old tree var lastCharIndex = oldTree.FullWidth - 1; // Move the start of the change range so that it is contained within oldTree. var start = Math.Max(Math.Min(changeRange.Span.Start, lastCharIndex), 0); // the first iteration aligns us with the change start. subsequent iteration move us to // the left by maxLookahead tokens. We only need to do this as long as we're not at the // start of the tree. Also, the tokens we get back may be zero width. In that case we // need to keep on looking backward. for (var i = 0; start > 0 && i <= maxLookahead;) { var token = oldTree.FindToken(start, findInsideTrivia: false); Debug.Assert(token.Kind() != SyntaxKind.None, "how could we not get a real token back?"); start = Math.Max(0, token.Position - 1); // Only increment i if we got a non-zero width token. Otherwise, we want to just do // this again having moved back one space. if (token.FullWidth > 0) { i++; } } if (IsInsideInterpolation(oldTree, start)) { // If the changed range starts inside an interpolated string, we // move the start of the change range to the beginning of the line so that any // interpolated string literal in the changed range will be scanned in its entirety. var column = oldTree.SyntaxTree.GetLineSpan(new TextSpan(start, 0)).Span.Start.Character; start = Math.Max(start - column, 0); } var finalSpan = TextSpan.FromBounds(start, changeRange.Span.End); var finalLength = changeRange.NewLength + (changeRange.Span.Start - start); return new TextChangeRange(finalSpan, finalLength); }