181 lines
6.1 KiB
C#
181 lines
6.1 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using Microsoft.AspNet.Razor.Parser;
|
|
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
|
|
|
|
namespace Microsoft.AspNet.Razor.CodeGenerators
|
|
{
|
|
public class CSharpPaddingBuilder
|
|
{
|
|
private static readonly char[] _newLineChars = { '\r', '\n' };
|
|
|
|
private readonly RazorEngineHost _host;
|
|
|
|
public CSharpPaddingBuilder(RazorEngineHost host)
|
|
{
|
|
_host = host;
|
|
}
|
|
|
|
// Special case for statement padding to account for brace positioning in the editor.
|
|
public string BuildStatementPadding(Span target)
|
|
{
|
|
if (target == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(target));
|
|
}
|
|
|
|
var padding = CalculatePadding(target, generatedStart: 0);
|
|
|
|
// We treat statement padding specially so for brace positioning, so that in the following example:
|
|
// @if (foo > 0)
|
|
// {
|
|
// }
|
|
//
|
|
// the braces shows up under the @ rather than under the if.
|
|
if (_host.DesignTimeMode &&
|
|
padding > 0 &&
|
|
target.Previous.Kind == SpanKind.Transition && // target.Previous is guaranteed to not be null if you have padding.
|
|
string.Equals(target.Previous.Content, SyntaxConstants.TransitionString, StringComparison.Ordinal))
|
|
{
|
|
padding--;
|
|
}
|
|
|
|
var generatedCode = BuildPaddingInternal(padding);
|
|
|
|
return generatedCode;
|
|
}
|
|
|
|
public string BuildExpressionPadding(Span target)
|
|
{
|
|
return BuildExpressionPadding(target, generatedStart: 0);
|
|
}
|
|
|
|
public string BuildExpressionPadding(Span target, int generatedStart)
|
|
{
|
|
if (target == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(target));
|
|
}
|
|
|
|
var padding = CalculatePadding(target, generatedStart);
|
|
|
|
return BuildPaddingInternal(padding);
|
|
}
|
|
|
|
internal int CalculatePadding(Span target, int generatedStart)
|
|
{
|
|
if (target == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(target));
|
|
}
|
|
|
|
int padding;
|
|
|
|
padding = CollectSpacesAndTabs(target, _host.TabSize) - generatedStart;
|
|
|
|
// if we add generated text that is longer than the padding we wanted to insert we have no recourse and we have to skip padding
|
|
// example:
|
|
// Razor code at column zero: @somecode()
|
|
// Generated code will be:
|
|
// In design time: __o = somecode();
|
|
// In Run time: Write(somecode());
|
|
//
|
|
// In both cases the padding would have been 1 space to remote the space the @ symbol takes, which will be smaller than the 6
|
|
// chars the hidden generated code takes.
|
|
if (padding < 0)
|
|
{
|
|
padding = 0;
|
|
}
|
|
|
|
return padding;
|
|
}
|
|
|
|
private string BuildPaddingInternal(int padding)
|
|
{
|
|
if (_host.DesignTimeMode && _host.IsIndentingWithTabs)
|
|
{
|
|
var spaces = padding % _host.TabSize;
|
|
var tabs = padding / _host.TabSize;
|
|
|
|
return new string('\t', tabs) + new string(' ', spaces);
|
|
}
|
|
else
|
|
{
|
|
return new string(' ', padding);
|
|
}
|
|
}
|
|
|
|
private static int CollectSpacesAndTabs(Span target, int tabSize)
|
|
{
|
|
var firstSpanInLine = target;
|
|
|
|
string currentContent = null;
|
|
|
|
while (firstSpanInLine.Previous != null)
|
|
{
|
|
// When scanning previous spans we need to be break down the spans with spaces. The parser combines
|
|
// whitespace into existing spans so you'll see tabs, newlines etc. within spans. We only care about
|
|
// the \t in existing spans.
|
|
var previousContent = firstSpanInLine.Previous.Content ?? string.Empty;
|
|
|
|
var lastNewLineIndex = previousContent.LastIndexOfAny(_newLineChars);
|
|
|
|
if (lastNewLineIndex < 0)
|
|
{
|
|
firstSpanInLine = firstSpanInLine.Previous;
|
|
}
|
|
else
|
|
{
|
|
if (lastNewLineIndex != previousContent.Length - 1)
|
|
{
|
|
firstSpanInLine = firstSpanInLine.Previous;
|
|
currentContent = previousContent.Substring(lastNewLineIndex + 1);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We need to walk from the beginning of the line, because space + tab(tabSize) = tabSize columns, but tab(tabSize) + space = tabSize+1 columns.
|
|
var currentSpanInLine = firstSpanInLine;
|
|
|
|
if (currentContent == null)
|
|
{
|
|
currentContent = currentSpanInLine.Content;
|
|
}
|
|
|
|
var padding = 0;
|
|
while (currentSpanInLine != target)
|
|
{
|
|
if (currentContent != null)
|
|
{
|
|
for (int i = 0; i < currentContent.Length; i++)
|
|
{
|
|
if (currentContent[i] == '\t')
|
|
{
|
|
// Example:
|
|
// <space><space><tab><tab>:
|
|
// iter 1) 1
|
|
// iter 2) 2
|
|
// iter 3) 4 = 2 + (4 - 2)
|
|
// iter 4) 8 = 4 + (4 - 0)
|
|
padding = padding + (tabSize - (padding % tabSize));
|
|
}
|
|
else
|
|
{
|
|
padding++;
|
|
}
|
|
}
|
|
}
|
|
|
|
currentSpanInLine = currentSpanInLine.Next;
|
|
currentContent = currentSpanInLine.Content;
|
|
}
|
|
|
|
return padding;
|
|
}
|
|
}
|
|
}
|