private static TextSpan GetSpanIncludingPrecedingWhitespaceInLine(SourceText sourceText, int start, int end) { var line = sourceText.Lines.GetLineFromPosition(start); var precedingLineText = sourceText.GetSubTextString(TextSpan.FromBounds(line.Start, start)); var precedingWhitespaceLength = precedingLineText.GetTrailingWhitespace().Length; return(TextSpan.FromBounds(start - precedingWhitespaceLength, end)); }
private void FormatCodeBlockStart(FormattingContext context, SourceText changedText, RazorDirectiveBodySyntax directiveBody, SyntaxNode innerCodeBlock, List <TextEdit> edits) { var sourceText = context.SourceText; var originalBodySpan = TextSpan.FromBounds(directiveBody.Position, directiveBody.EndPosition); var originalBodyRange = originalBodySpan.AsRange(sourceText); if (context.Range.Start.Line > originalBodyRange.Start.Line) { return; } // First line is within the selected range. Let's try and format the start. TrackChangeInSpan(sourceText, originalBodySpan, changedText, out var changedBodySpan, out _); var changedBodyRange = changedBodySpan.AsRange(changedText); // First, make sure the first line is indented correctly. var firstLine = changedText.Lines[(int)changedBodyRange.Start.Line]; var desiredIndentationLevel = context.Indentations[firstLine.LineNumber].IndentationLevel; var desiredIndentation = context.GetIndentationLevelString(desiredIndentationLevel); var firstNonWhitespaceOffset = firstLine.GetFirstNonWhitespaceOffset(); if (firstNonWhitespaceOffset.HasValue) { var edit = new TextEdit() { Range = new Range( new Position(firstLine.LineNumber, 0), new Position(firstLine.LineNumber, firstNonWhitespaceOffset.Value)), NewText = desiredIndentation }; edits.Add(edit); } // We should also move any code that comes after '{' down to its own line. var originalInnerCodeBlockSpan = TextSpan.FromBounds(innerCodeBlock.Position, innerCodeBlock.EndPosition); TrackChangeInSpan(sourceText, originalInnerCodeBlockSpan, changedText, out var changedInnerCodeBlockSpan, out _); var innerCodeBlockRange = changedInnerCodeBlockSpan.AsRange(changedText); var innerCodeBlockLine = changedText.Lines[(int)innerCodeBlockRange.Start.Line]; var textAfterBlockStart = innerCodeBlockLine.ToString().Substring(innerCodeBlock.Position - innerCodeBlockLine.Start); var isBlockStartOnSeparateLine = string.IsNullOrWhiteSpace(textAfterBlockStart); var innerCodeBlockIndentationLevel = desiredIndentationLevel + 1; var desiredInnerCodeBlockIndentation = context.GetIndentationLevelString(innerCodeBlockIndentationLevel); var whitespaceAfterBlockStart = textAfterBlockStart.GetLeadingWhitespace(); if (!isBlockStartOnSeparateLine) { // If the first line contains code, add a newline at the beginning and indent it. var edit = new TextEdit() { Range = new Range( new Position(innerCodeBlockLine.LineNumber, innerCodeBlock.Position - innerCodeBlockLine.Start), new Position(innerCodeBlockLine.LineNumber, innerCodeBlock.Position + whitespaceAfterBlockStart.Length - innerCodeBlockLine.Start)), NewText = Environment.NewLine + desiredInnerCodeBlockIndentation }; edits.Add(edit); } else { // // The code inside the code block directive is on its own line. Ideally the C# formatter would have already taken care of it. // Except, the first line of the code block is not indented because of how our SourceMappings work. // E.g, // @code { // ... // } // Our source mapping for this code block only ranges between the { and }, exclusive. // If the C# formatter provides any edits that start from before the {, we won't be able to map it back and we will ignore it. // Unfortunately because of this, we partially lose some edits which would have indented the first line of the code block correctly. // So let's manually indent the first line here. // var innerCodeBlockText = changedText.GetSubTextString(changedInnerCodeBlockSpan); if (!string.IsNullOrWhiteSpace(innerCodeBlockText)) { var codeStart = innerCodeBlockText.GetFirstNonWhitespaceOffset() + changedInnerCodeBlockSpan.Start; if (codeStart.HasValue && codeStart != changedInnerCodeBlockSpan.End) { // If we got here, it means this is a non-empty code block. We can safely indent the first line. var codeStartLine = changedText.Lines.GetLineFromPosition(codeStart.Value); var existingCodeStartIndentation = codeStartLine.GetFirstNonWhitespaceOffset() ?? 0; var edit = new TextEdit() { Range = new Range( new Position(codeStartLine.LineNumber, 0), new Position(codeStartLine.LineNumber, existingCodeStartIndentation)), NewText = desiredInnerCodeBlockIndentation }; edits.Add(edit); } } } }