40
40
using Microsoft . CodeAnalysis . Razor . DocumentMapping ;
41
41
using System . Reflection . Metadata . Ecma335 ;
42
42
using Microsoft . VisualStudio . Utilities ;
43
+ using System . Text . RegularExpressions ;
43
44
44
45
namespace Microsoft . AspNetCore . Razor . LanguageServer . CodeActions ;
45
46
@@ -92,6 +93,17 @@ internal sealed class ExtractToComponentCodeActionResolver(
92
93
return null ;
93
94
}
94
95
96
+ // For the purposes of determining the indentation of the extracted code, get the whitespace before the start of the selection.
97
+ var whitespaceReferenceOwner = codeDocument . GetSyntaxTree ( ) . Root . FindInnermostNode ( selectionAnalysis . ExtractStart , includeWhitespace : true ) . AssumeNotNull ( ) ;
98
+ var whitespaceReferenceNode = whitespaceReferenceOwner . FirstAncestorOrSelf < MarkupSyntaxNode > ( node => node is MarkupElementSyntax or MarkupTagHelperElementSyntax ) ;
99
+ var whitespace = string . Empty ;
100
+ if ( whitespaceReferenceNode . TryGetPreviousSibling ( out var startPreviousSibling ) && startPreviousSibling . ContainsOnlyWhitespace ( ) )
101
+ {
102
+ // Get the whitespace substring so we know how much to dedent the extracted code. Remove any carriage return and newline escape characters.
103
+ whitespace = startPreviousSibling . ToFullString ( ) ;
104
+ whitespace = whitespace . Replace ( "\r " , string . Empty ) . Replace ( "\n " , string . Empty ) ;
105
+ }
106
+
95
107
var start = codeDocument . Source . Text . Lines . GetLinePosition ( selectionAnalysis . ExtractStart ) ;
96
108
var end = codeDocument . Source . Text . Lines . GetLinePosition ( selectionAnalysis . ExtractEnd ) ;
97
109
var removeRange = new Range
@@ -118,7 +130,7 @@ internal sealed class ExtractToComponentCodeActionResolver(
118
130
} . Uri ;
119
131
120
132
var componentName = Path . GetFileNameWithoutExtension ( componentPath ) ;
121
- var newComponentResult = await GenerateNewComponentAsync ( selectionAnalysis , codeDocument , actionParams . Uri , documentContext , removeRange , cancellationToken ) . ConfigureAwait ( false ) ;
133
+ var newComponentResult = await GenerateNewComponentAsync ( selectionAnalysis , codeDocument , actionParams . Uri , documentContext , removeRange , newComponentUri , whitespace , cancellationToken ) . ConfigureAwait ( false ) ;
122
134
123
135
if ( newComponentResult is null )
124
136
{
@@ -498,6 +510,8 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS
498
510
Uri componentUri ,
499
511
DocumentContext documentContext ,
500
512
Range relevantRange ,
513
+ Uri newComponentUri ,
514
+ string whitespace ,
501
515
CancellationToken cancellationToken )
502
516
{
503
517
var contents = await documentContext . GetSourceTextAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
@@ -526,6 +540,18 @@ private static void AddUsingFromTagHelperInfo(TagHelperInfo tagHelperInfo, HashS
526
540
selectionAnalysis . ExtractEnd - selectionAnalysis . ExtractStart ) )
527
541
. Trim ( ) ;
528
542
543
+ // Go through each line of the extractedContents and remove the whitespace from the beginning of each line.
544
+ var extractedLines = extractedContents . Split ( '\n ' ) ;
545
+ for ( var i = 1 ; i < extractedLines . Length ; i ++ )
546
+ {
547
+ var line = extractedLines [ i ] ;
548
+ if ( line . StartsWith ( whitespace , StringComparison . Ordinal ) )
549
+ {
550
+ extractedLines [ i ] = line . Substring ( whitespace . Length ) ;
551
+ }
552
+ }
553
+
554
+ extractedContents = string . Join ( "\n " , extractedLines ) ;
529
555
newFileContentBuilder . Append ( extractedContents ) ;
530
556
531
557
// Get CSharpStatements within component
@@ -808,6 +834,8 @@ private static HashSet<FieldSymbolicInfo> GetFieldsInContext(FieldSymbolicInfo[]
808
834
return fieldsInContext ;
809
835
}
810
836
837
+ // By forwarded fields, I mean fields that are present in the extraction, but get directly added/copied to the extracted component's code block, instead of being passed as an attribute.
838
+ // If you have naming suggestions that make more sense, please let me know.
811
839
private static string GenerateForwardedConstantFields ( HashSet < FieldSymbolicInfo > relevantFields , string ? sourceDocumentFileName )
812
840
{
813
841
var builder = new StringBuilder ( ) ;
0 commit comments