diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs index d55a490755..39e2e27d87 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpCodeWriter.cs @@ -25,6 +25,11 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp return (CSharpCodeWriter)base.Indent(size); } + public CSharpCodeWriter ResetIndent() + { + return (CSharpCodeWriter)base.ResetIndent(); + } + public CSharpCodeWriter SetIndent(int size) { return (CSharpCodeWriter)base.SetIndent(size); diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs index 891cbd5a73..5a9b30fede 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpLineMappingWriter.cs @@ -11,8 +11,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp private SourceLocation _generatedLocation; private int _startIndent; private int _generatedContentLength; + private bool _writePragmas; - public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength, string sourceFilename) + public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength) { _writer = writer; _documentMapping = new MappingLocation(documentLocation, contentLength); @@ -20,6 +21,14 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp _startIndent = _writer.CurrentIndent; _generatedContentLength = 0; _writer.ResetIndent(); + + _generatedLocation = _writer.GetCurrentSourceLocation(); + } + + public CSharpLineMappingWriter(CSharpCodeWriter writer, SourceLocation documentLocation, int contentLength, string sourceFilename) + : this(writer, documentLocation, contentLength) + { + _writePragmas = true; // TODO: Should this just be '\n'? if (_writer.LastWrite.Last() != '\n') @@ -49,9 +58,9 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { _generatedContentLength = _writer.ToString().Length - _generatedLocation.AbsoluteIndex; } - + var generatedLocation = new MappingLocation(_generatedLocation, _generatedContentLength); - if(_documentMapping.ContentLength == -1) + if (_documentMapping.ContentLength == -1) { _documentMapping.ContentLength = generatedLocation.ContentLength; } @@ -60,13 +69,24 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp documentLocation: _documentMapping, generatedLocation: new MappingLocation(_generatedLocation, _generatedContentLength)); - if (_writer.ToString().Last() != '\n') + if (_writePragmas) { - _writer.WriteLine(); - } + // Need to add an additional line at the end IF there wasn't one already written. + // This is needed to work with the C# editor's handling of #line ... + bool writeExtraLine = _writer.ToString().Last() != '\n'; - _writer.WriteLineDefaultDirective(); - _writer.WriteLineHiddenDirective(); + // Always write at least 1 empty line to potentially separate code from pragmas. + _writer.WriteLine(); + + // Check if the previous empty line wasn't enough to separate code from pragmas. + if (writeExtraLine) + { + _writer.WriteLine(); + } + + _writer.WriteLineDefaultDirective() + .WriteLineHiddenDirective(); + } // Reset indent back to when it was started _writer.SetIndent(_startIndent); diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs index fe01747084..cc7ea0bd19 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/CSharpPaddingBuilder.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp throw new ArgumentNullException("target"); } - int padding = CalculatePadding(target); + int padding = CalculatePadding(target, 0); // We treat statement padding specially so for brace positioning, so that in the following example: // @if (foo > 0) @@ -44,21 +44,38 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp return generatedCode; } - public string BuildExpressionPadding(Span target) + public string BuildExpressionPadding(Span target, int generatedStart = 0) { - int padding = CalculatePadding(target); + int padding = CalculatePadding(target, generatedStart); return BuildPaddingInternal(padding); } - internal int CalculatePadding(Span target) + internal int CalculatePadding(Span target, int generatedStart) { if (target == null) { throw new ArgumentNullException("target"); } - return CollectSpacesAndTabs(target, _host.TabSize); + 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) diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs index a29ac498b5..54ede121ed 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpCodeVisitor.cs @@ -2,6 +2,7 @@ using System.Globalization; using System.Linq; using Microsoft.AspNet.Razor.Parser.SyntaxTree; +using Microsoft.AspNet.Razor.Text; namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { @@ -115,53 +116,25 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp protected override void Visit(ExpressionBlockChunk chunk) { // TODO: Handle instrumentation - // TODO: Refactor - if (!Context.Host.DesignTimeMode && Context.ExpressionRenderingMode == ExpressionRenderingMode.InjectCode) + if (Context.Host.DesignTimeMode) { - Visit((ChunkBlock)chunk); + RenderDesignTimeExpressionBlockChunk(chunk); } else { - if (Context.Host.DesignTimeMode) - { - Writer.WriteStartAssignment("__o"); - } - else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput) - { - if (!String.IsNullOrEmpty(Context.TargetWriterName)) - { - Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName) - .Write(Context.TargetWriterName) - .WriteParameterSeparator(); - } - else - { - Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName); - } - } - - Visit((ChunkBlock)chunk); - - if (Context.Host.DesignTimeMode) - { - Writer.WriteLine(";"); - } - else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput) - { - Writer.WriteEndMethodInvocation(); - } + RenderRuntimeExpressionBlockChunk(chunk); } } protected override void Visit(ExpressionChunk chunk) { - CreateCodeMapping(chunk.Code, chunk); + CreateExpressionCodeMapping(chunk.Code, chunk); } protected override void Visit(StatementChunk chunk) { - CreateCodeMapping(chunk.Code, chunk); + CreateStatementCodeMapping(chunk.Code, chunk); Writer.WriteLine(); } @@ -308,16 +281,101 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp Writer.WriteEndMethodInvocation(); } - public void CreateCodeMapping(string code, Chunk chunk) + public void RenderDesignTimeExpressionBlockChunk(ExpressionBlockChunk chunk) + { + // TODO: Handle instrumentation + + int currentIndent = Writer.CurrentIndent; + string designTimeAssignment = "__o = "; + + // The first child should never be null, it should always be a transition span, that's what + // defines an expression block chunk. + var firstChild = (ExpressionChunk)chunk.Children.FirstOrDefault(); + + Writer.ResetIndent() + .WriteLineNumberDirective(1, "This is here only for document formatting.") + // We build the padding with an offset of the design time assignment statement. + .Write(_paddingBuilder.BuildExpressionPadding((Span)firstChild.Association, designTimeAssignment.Length)) + .Write(designTimeAssignment); + + // We map the first line of code but do not write the line pragmas associated with it. + CreateRawCodeMapping(firstChild.Code, firstChild.Association.Start); + + // This is a temporary block that is indentical to the current one with the exception of its children. + var subBlock = new ChunkBlock + { + Start = chunk.Start, + Association = chunk.Association, + Children = chunk.Children.Skip(1).ToList() + }; + + // Render all children (except for the first one) + Visit(subBlock); + + Writer.WriteLine(";") + .WriteLine() + .WriteLineDefaultDirective() + .WriteLineHiddenDirective() + .SetIndent(currentIndent); + } + + public void RenderRuntimeExpressionBlockChunk(ExpressionBlockChunk chunk) + { + // TODO: Handle instrumentation + + if (Context.ExpressionRenderingMode == ExpressionRenderingMode.InjectCode) + { + Visit((ChunkBlock)chunk); + } + else if (Context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput) + { + if (!String.IsNullOrEmpty(Context.TargetWriterName)) + { + Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteToMethodName) + .Write(Context.TargetWriterName) + .WriteParameterSeparator(); + } + else + { + Writer.WriteStartMethodInvocation(Context.Host.GeneratedClassContext.WriteMethodName); + } + + Visit((ChunkBlock)chunk); + + Writer.WriteEndMethodInvocation() + .WriteLine(); + } + } + + public void CreateExpressionCodeMapping(string code, Chunk chunk) + { + CreateCodeMapping(_paddingBuilder.BuildExpressionPadding((Span)chunk.Association), code, chunk); + } + + public void CreateStatementCodeMapping(string code, Chunk chunk) + { + CreateCodeMapping(_paddingBuilder.BuildStatementPadding((Span)chunk.Association), code, chunk); + } + + public void CreateCodeMapping(string padding, string code, Chunk chunk) { using (CSharpLineMappingWriter mappingWriter = Writer.BuildLineMapping(chunk.Start, code.Length, Context.SourceFile)) { - Writer.Write(_paddingBuilder.BuildExpressionPadding((Span)chunk.Association)); + Writer.Write(padding); mappingWriter.MarkLineMappingStart(); Writer.Write(code); mappingWriter.MarkLineMappingEnd(); } } + + // Raw CodeMapping's do not write out line pragmas, they just map code. + public void CreateRawCodeMapping(string code, SourceLocation documentLocation) + { + using (new CSharpLineMappingWriter(Writer, documentLocation, code.Length)) + { + Writer.Write(code); + } + } } } diff --git a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs index eb5b84df07..7fda51375e 100644 --- a/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs +++ b/src/Microsoft.AspNet.Razor/Generator/Compiler/CodeBuilder/CSharp/Visitors/CSharpTypeMemberVisitor.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Razor.Generator.Compiler.CSharp { if (!String.IsNullOrEmpty(chunk.Code)) { - _csharpCodeVisitor.CreateCodeMapping(chunk.Code, chunk); + _csharpCodeVisitor.CreateCodeMapping(String.Empty, chunk.Code, chunk); } } }