private async Task <TextEdit[]> FormatOnServerAsync( FormattingContext context, Range projectedRange, CancellationToken cancellationToken) { var csharpSourceText = context.CodeDocument.GetCSharpSourceText(); var spanToFormat = projectedRange.AsTextSpan(csharpSourceText); var root = await context.CSharpWorkspaceDocument.GetSyntaxRootAsync(cancellationToken); var workspace = context.CSharpWorkspace; // Formatting options will already be set in the workspace. var changes = CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(root, spanToFormat, workspace); var edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray(); return(edits); }
private bool TryMapFromProjectedDocumentRangeInclusive(RazorCodeDocument codeDocument, Range projectedRange, out Range originalRange) { originalRange = default; var csharpDoc = codeDocument.GetCSharpDocument(); var csharpSourceText = SourceText.From(csharpDoc.GeneratedCode); var projectedRangeAsSpan = projectedRange.AsTextSpan(csharpSourceText); var range = projectedRange; var startIndex = projectedRangeAsSpan.Start; var startMappedDirectly = TryMapFromProjectedDocumentPosition(codeDocument, startIndex, out var hostDocumentStart, out _); var endIndex = projectedRangeAsSpan.End; var endMappedDirectly = TryMapFromProjectedDocumentPosition(codeDocument, endIndex, out var hostDocumentEnd, out _); if (startMappedDirectly && endMappedDirectly) { // We strictly mapped the start/end of the projected range. originalRange = new Range(hostDocumentStart, hostDocumentEnd); return(true); } List <SourceMapping> candidateMappings; if (startMappedDirectly) { // Start of projected range intersects with a mapping candidateMappings = csharpDoc.SourceMappings.Where(mapping => IntersectsWith(startIndex, mapping.GeneratedSpan)).ToList(); } else if (endMappedDirectly) { // End of projected range intersects with a mapping candidateMappings = csharpDoc.SourceMappings.Where(mapping => IntersectsWith(endIndex, mapping.GeneratedSpan)).ToList(); } else { // Our range does not intersect with any mapping; we should see if it overlaps generated locations candidateMappings = csharpDoc.SourceMappings.Where(mapping => Overlaps(projectedRangeAsSpan, mapping.GeneratedSpan)).ToList(); } if (candidateMappings.Count == 1) { // We're intersecting or overlapping a single mapping, lets choose that. var mapping = candidateMappings[0]; originalRange = ConvertMapping(codeDocument.Source, mapping); return(true); } else { // More then 1 or exactly 0 intersecting/overlapping mappings return(false); } bool Overlaps(TextSpan projectedRangeAsSpan, SourceSpan span) { var overlapStart = Math.Max(projectedRangeAsSpan.Start, span.AbsoluteIndex); var overlapEnd = Math.Min(projectedRangeAsSpan.End, span.AbsoluteIndex + span.Length); return(overlapStart < overlapEnd); } bool IntersectsWith(int position, SourceSpan span) { return(unchecked ((uint)(position - span.AbsoluteIndex) <= (uint)span.Length)); }
// // 'minCSharpIndentLevel' refers to the minimum level of how much the C# formatter would indent code. // @code/@functions blocks contain class members and so are typically indented by 2 levels. // @{} blocks are put inside method body which means they are typically indented by 3 levels. // private SourceText ApplyCSharpEdits(FormattingContext context, Range codeBlockRange, TextEdit[] edits, int minCSharpIndentLevel) { var originalText = context.SourceText; var originalCodeBlockSpan = codeBlockRange.AsTextSpan(originalText); // Sometimes the C# formatter edits outside the range we supply. Filter out those edits. var changes = edits.Select(e => e.AsTextChange(originalText)).Where(c => originalCodeBlockSpan.Contains(c.Span)).ToArray(); if (changes.Length == 0) { return(originalText); } // Apply the C# edits to the document. var changedText = originalText.WithChanges(changes); TrackChangeInSpan(originalText, originalCodeBlockSpan, changedText, out var changedCodeBlockSpan, out var changeEncompassingSpan); // We now have the changed document with C# edits. But it might be indented more/less than what we want depending on the context. // So, we want to bring each line to the right level of indentation based on where the block is in the document. // We also need to only do this for the lines that are part of the input range to respect range formatting. var desiredIndentationLevel = context.Indentations[(int)codeBlockRange.Start.Line].IndentationLevel + 1; var editsToApply = new List <TextChange>(); var inputSpan = context.Range.AsTextSpan(originalText); TrackChangeInSpan(originalText, inputSpan, changedText, out var changedInputSpan, out _); var changedInputRange = changedInputSpan.AsRange(changedText); for (var i = (int)changedInputRange.Start.Line; i <= changedInputRange.End.Line; i++) { var line = changedText.Lines[i]; if (line.Span.Length == 0) { // Empty line. C# formatter didn't remove it so we won't either. continue; } if (!changedCodeBlockSpan.Contains(line.Start)) { // Defensive check to make sure we're not handling lines that are not part of the current code block. continue; } var leadingWhitespace = line.GetLeadingWhitespace(); var minCSharpIndentLength = context.GetIndentationLevelString(minCSharpIndentLevel).Length; if (leadingWhitespace.Length < minCSharpIndentLength) { // For whatever reason, the C# formatter decided to not indent this. Leave it as is. continue; } else { // At this point we assume the C# formatter has relatively indented this line to the correct level. // All we want to do at this point is to indent/unindent this line based on the absolute indentation of the block // and the minimum C# indent level. We don't need to worry about the actual existing indentation here because it doesn't matter. var effectiveDesiredIndentationLevel = desiredIndentationLevel - minCSharpIndentLevel; var effectiveDesiredIndentation = context.GetIndentationLevelString(Math.Abs(effectiveDesiredIndentationLevel)); if (effectiveDesiredIndentationLevel < 0) { // This means that we need to unindent. var span = new TextSpan(line.Start, effectiveDesiredIndentation.Length); editsToApply.Add(new TextChange(span, string.Empty)); } else if (effectiveDesiredIndentationLevel > 0) { // This means that we need to indent. var span = new TextSpan(line.Start, 0); editsToApply.Add(new TextChange(span, effectiveDesiredIndentation)); } } } changedText = ApplyChanges(changedText, editsToApply); return(changedText); }