// Internal for testing only internal static bool TryGetMinimalCSharpRange(RazorCodeDocument codeDocument, Range razorRange, [NotNullWhen(true)] out Range?csharpRange) { SourceSpan?minGeneratedSpan = null; SourceSpan?maxGeneratedSpan = null; var sourceText = codeDocument.GetSourceText(); var textSpan = razorRange.AsTextSpan(sourceText); var csharpDoc = codeDocument.GetCSharpDocument(); // We want to find the min and max C# source mapping that corresponds with our Razor range. foreach (var mapping in csharpDoc.SourceMappings) { var mappedTextSpan = mapping.OriginalSpan.AsTextSpan(); if (textSpan.OverlapsWith(mappedTextSpan)) { if (minGeneratedSpan is null || mapping.GeneratedSpan.AbsoluteIndex < minGeneratedSpan.Value.AbsoluteIndex) { minGeneratedSpan = mapping.GeneratedSpan; } var mappingEndIndex = mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length; if (maxGeneratedSpan is null || mappingEndIndex > maxGeneratedSpan.Value.AbsoluteIndex + maxGeneratedSpan.Value.Length) { maxGeneratedSpan = mapping.GeneratedSpan; } } } // Create a new projected range based on our calculated min/max source spans. if (minGeneratedSpan is not null && maxGeneratedSpan is not null) { var csharpSourceText = codeDocument.GetCSharpSourceText(); var startRange = minGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText); var endRange = maxGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText); csharpRange = new Range { Start = startRange.Start, End = endRange.End }; Debug.Assert(csharpRange.Start <= csharpRange.End, "Range.Start should not be larger than Range.End"); return(true); } csharpRange = null; return(false); }
private static void CleanupSourceMappingEnd(FormattingContext context, Range sourceMappingRange, List <TextChange> changes) { // // We look through every source mapping that intersects with the affected range and // bring the content after the last line to its own line and adjust its indentation, // // E.g, // // @{ // if (true) // { <div></div> // } // } // // becomes, // // @{ // if (true) // { // </div></div> // } // } // var text = context.SourceText; var sourceMappingSpan = sourceMappingRange.AsTextSpan(text); var mappingEndLineIndex = sourceMappingRange.End.Line; if (!context.Indentations[mappingEndLineIndex].StartsInCSharpContext) { // For corner cases like (Position marked with |), // It is already in a separate line. It doesn't need cleaning up. // @{ // if (true} // { // |<div></div> // } // } // return; } var endSpan = TextSpan.FromBounds(sourceMappingSpan.End, sourceMappingSpan.End); if (!ShouldFormat(context, endSpan, allowImplicitStatements: false)) { // We don't want to run cleanup on this range. return; } var contentStartOffset = text.Lines[mappingEndLineIndex].GetFirstNonWhitespaceOffset(sourceMappingRange.End.Character); if (contentStartOffset == null) { // There is no content after the end of this source mapping. No need to clean up. return; } var spanToReplace = new TextSpan(sourceMappingSpan.End, 0); if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel)) { // Can't find the correct indentation for this content. Leave it alone. return; } // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example. var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel); // After the below change the above example should look like, // @{ // if (true) // { // <div></div> // } // } var change = new TextChange(spanToReplace, replacement); changes.Add(change); }
private static void CleanupSourceMappingStart(FormattingContext context, Range sourceMappingRange, List <TextChange> changes) { // // We look through every source mapping that intersects with the affected range and // bring the first line to its own line and adjust its indentation, // // E.g, // // @{ public int x = 0; // } // // becomes, // // @{ // public int x = 0; // } // var text = context.SourceText; var sourceMappingSpan = sourceMappingRange.AsTextSpan(text); if (!ShouldFormat(context, sourceMappingSpan, allowImplicitStatements: false)) { // We don't want to run cleanup on this range. return; } if (sourceMappingRange.Start.Character == 0) { // It already starts on a fresh new line which doesn't need cleanup. // E.g, (The mapping starts at | in the below case) // @{ // @: Some html // | var x = 123; // } // return; } // @{ // if (true) // { // <div></div>| // // |} // } // We want to return the length of the range marked by |...| // var whitespaceLength = text.GetFirstNonWhitespaceOffset(sourceMappingSpan, out var newLineCount); if (whitespaceLength == null) { // There was no content after the start of this mapping. Meaning it already is clean. // E.g, // @{| // ... // } return; } var spanToReplace = new TextSpan(sourceMappingSpan.Start, whitespaceLength.Value); if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel)) { // Can't find the correct indentation for this content. Leave it alone. return; } // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example. // Make sure to preserve the same number of blank lines as the original string had var replacement = PrependLines(context.GetIndentationLevelString(contentIndentLevel), context.NewLineString, Math.Max(newLineCount, 1)); // After the below change the above example should look like, // @{ // if (true) // { // <div></div> // } // } var change = new TextChange(spanToReplace, replacement); changes.Add(change); }
private void CleanupSourceMappingStart(FormattingContext context, Range sourceMappingRange, List <TextChange> changes) { // // We look through every source mapping that intersects with the affected range and // bring the first line to its own line and adjust its indentation, // // E.g, // // @{ public int x = 0; // } // // becomes, // // @{ // public int x = 0; // } // if (!ShouldFormat(context, sourceMappingRange.Start, allowImplicitStatements: false)) { // We don't want to run cleanup on this range. return; } // @{ // if (true) // { // <div></div>| // // |} // } // We want to return the length of the range marked by |...| // var text = context.SourceText; var sourceMappingSpan = sourceMappingRange.AsTextSpan(text); var whitespaceLength = text.GetFirstNonWhitespaceOffset(sourceMappingSpan); if (whitespaceLength == null) { // There was no content here. Skip. return; } var spanToReplace = new TextSpan(sourceMappingSpan.Start, whitespaceLength.Value); if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel)) { // Can't find the correct indentation for this content. Leave it alone. return; } // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example. var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel); // After the below change the above example should look like, // @{ // if (true) // { // <div></div> // } // } var change = new TextChange(spanToReplace, replacement); changes.Add(change); }