Пример #1
0
        protected static List <TextChange> CleanupDocument(FormattingContext context, Range range = null)
        {
            var text = context.SourceText;

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);
            var csharpDocument = context.CodeDocument.GetCSharpDocument();

            var changes = new List <TextChange>();

            foreach (var mapping in csharpDocument.SourceMappings)
            {
                var mappingSpan  = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                var mappingRange = mappingSpan.AsRange(text);
                if (!range.LineOverlapsWith(mappingRange))
                {
                    // We don't care about this range. It didn't change.
                    continue;
                }

                CleanupSourceMappingStart(context, mappingRange, changes);

                CleanupSourceMappingEnd(context, mappingRange, changes);
            }

            return(changes);
        }
Пример #2
0
        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));
        }
        protected override void CommandCallback(object sender, EventArgs e)
        {
            IWpfTextView textView = ServiceProvider.GetWpfTextView();

            if (textView == null)
            {
                return;
            }

            SnapshotPoint     caretPosition = textView.Caret.Position.BufferPosition;
            ITextSnapshotLine caretLine     = caretPosition.GetContainingLine();

            if (caretLine == null)
            {
                return;
            }

            Document document = caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges();

            if (document == null)
            {
                return;
            }

            (SyntaxNode syntaxRoot, SemanticModel semanticModel) = ThreadHelper.JoinableTaskFactory.Run(
                async() => (await document.GetSyntaxRootAsync(), await document.GetSemanticModelAsync()));

            if (syntaxRoot == null || semanticModel == null)
            {
                return;
            }

            TextSpan lineSpan   = TextSpan.FromBounds(caretLine.Start.Position, caretLine.End.Position);
            var      memberNode = syntaxRoot.FindNode(lineSpan) as MemberDeclarationSyntax;

            if (memberNode == null)
            {
                return;
            }

            PXContext context = new PXContext(semanticModel.Compilation);

            if (!context.IsPlatformReferenced)
            {
                return;
            }

            ISymbol memberSymbol = GetMemberSymbol(memberNode, semanticModel, caretPosition);

            if (!CheckMemberSymbol(memberSymbol, context))
            {
                return;
            }

            NavigateToHandlerOrDeclaration(document, textView, memberSymbol, memberNode, semanticModel, context);
        }
Пример #4
0
        private void FormatCodeBlockEnd(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.End.Line < originalBodyRange.End.Line)
            {
                return;
            }

            // Last line is within the selected range. Let's try and format the end.

            TrackChangeInSpan(sourceText, originalBodySpan, changedText, out var changedBodySpan, out _);
            var changedBodyRange = changedBodySpan.AsRange(changedText);

            var firstLine = changedText.Lines[(int)changedBodyRange.Start.Line];
            var desiredIndentationLevel = context.Indentations[firstLine.LineNumber].IndentationLevel;
            var desiredIndentation      = context.GetIndentationLevelString(desiredIndentationLevel);

            // we want to keep the close '}' on its own line. So bring it to the next line.
            var originalInnerCodeBlockSpan = TextSpan.FromBounds(innerCodeBlock.Position, innerCodeBlock.EndPosition);

            TrackChangeInSpan(sourceText, originalInnerCodeBlockSpan, changedText, out var changedInnerCodeBlockSpan, out _);
            var closeCurlyLocation       = changedInnerCodeBlockSpan.End;
            var closeCurlyLine           = changedText.Lines.GetLineFromPosition(closeCurlyLocation);
            var firstNonWhitespaceOffset = closeCurlyLine.GetFirstNonWhitespaceOffset() ?? 0;

            if (closeCurlyLine.Start + firstNonWhitespaceOffset != closeCurlyLocation)
            {
                // This means the '}' is on the same line as some C# code.
                // Bring it down to the next line and apply the desired indentation.
                var edit = new TextEdit()
                {
                    Range = new Range(
                        new Position(closeCurlyLine.LineNumber, closeCurlyLocation - closeCurlyLine.Start),
                        new Position(closeCurlyLine.LineNumber, closeCurlyLocation - closeCurlyLine.Start)),
                    NewText = Environment.NewLine + desiredIndentation
                };
                edits.Add(edit);
            }
            else if (firstNonWhitespaceOffset != desiredIndentation.Length)
            {
                // This means the '}' is on its own line but is not indented correctly. Correct it.
                var edit = new TextEdit()
                {
                    Range = new Range(
                        new Position(closeCurlyLine.LineNumber, 0),
                        new Position(closeCurlyLine.LineNumber, firstNonWhitespaceOffset)),
                    NewText = desiredIndentation
                };
                edits.Add(edit);
            }
        }
Пример #5
0
        private TextChange[] Diff(SourceText oldText, SourceText newText, TextSpan?spanToDiff = default)
        {
            // Once https://github.com/dotnet/roslyn/issues/41413 is fixed,
            // the following lines can be replaced with `newText.GetTextChanges(oldText)`.

            var spanToTrack = spanToDiff ?? TextSpan.FromBounds(0, oldText.Length);

            TrackChangeInSpan(oldText, spanToTrack, newText, out var changedSpanToTrack, out _);
            var change = new TextChange(spanToTrack, newText.GetSubText(changedSpanToTrack).ToString());

            return(new[] { change });
        }
        private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, CancellationToken cancellationToken)
        {
            var textBuffer = this.EditorAdaptersFactoryService.GetDataBuffer((IVsTextBuffer)textLayer);

            if (textBuffer == null)
            {
                return(VSConstants.E_UNEXPECTED);
            }

            var document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();

            if (document == null)
            {
                return(VSConstants.E_FAIL);
            }

            var documentSyntax    = ParsedDocument.CreateSynchronously(document, cancellationToken);
            var text              = documentSyntax.Text;
            var root              = documentSyntax.Root;
            var formattingOptions = textBuffer.GetSyntaxFormattingOptions(EditorOptionsService, document.Project.LanguageServices, explicitFormat: true);

            var ts           = selections.Single();
            var start        = text.Lines[ts.iStartLine].Start + ts.iStartIndex;
            var end          = text.Lines[ts.iEndLine].Start + ts.iEndIndex;
            var adjustedSpan = GetFormattingSpan(root, start, end);

            // Since we know we are on the UI thread, lets get the base indentation now, so that there is less
            // cleanup work to do later in Venus.
            var ruleFactory = Workspace.Services.GetService <IHostDependentFormattingRuleFactoryService>();
            var rules       = ruleFactory.CreateRule(documentSyntax, start).Concat(Formatter.GetDefaultFormattingRules(document.Project.Services));

            // use formatting that return text changes rather than tree rewrite which is more expensive
            var formatter       = document.GetRequiredLanguageService <ISyntaxFormattingService>();
            var originalChanges = formatter.GetFormattingResult(root, SpecializedCollections.SingletonEnumerable(adjustedSpan), formattingOptions, rules, cancellationToken)
                                  .GetTextChanges(cancellationToken);

            var originalSpan     = RoslynTextSpan.FromBounds(start, end);
            var formattedChanges = ruleFactory.FilterFormattedChanges(document.Id, originalSpan, originalChanges);

            if (formattedChanges.IsEmpty())
            {
                return(VSConstants.S_OK);
            }

            // create new formatted document
            var formattedDocument = document.WithText(text.WithChanges(formattedChanges));

            Workspace.ApplyDocumentChanges(formattedDocument, cancellationToken);

            return(VSConstants.S_OK);
        }
        private async Task <FunctionBreakpointNameFactory?> GetFunctionBreakpointNameFactoryAsync(CancellationToken cancellationToken)
        {
            ErrorHandler.ThrowOnFailure(textManager.GetActiveView(fMustHaveFocus: 1, pBuffer: null, out var view));
            var activeViewSelection = editorAdaptersFactoryService.GetWpfTextView(view).Selection;
            var document            = activeViewSelection.Start.Position.Snapshot.GetOpenDocumentInCurrentContextWithChanges();

            return(await FunctionBreakpointUtils.GetFunctionBreakpointNameFactoryAsync(
                       await document.GetSyntaxRootAsync(cancellationToken),
                       TextSpan.FromBounds(
                           activeViewSelection.Start.Position.Position,
                           activeViewSelection.End.Position.Position),
                       document.GetSemanticModelAsync,
                       cancellationToken));
        }
        private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, CancellationToken cancellationToken)
        {
            var textBuffer = this.EditorAdaptersFactoryService.GetDataBuffer((IVsTextBuffer)textLayer);

            if (textBuffer == null)
            {
                return(VSConstants.E_UNEXPECTED);
            }

            var document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();

            if (document == null)
            {
                return(VSConstants.E_FAIL);
            }

            var text = document.GetTextAsync(cancellationToken).WaitAndGetResult(cancellationToken);
            var root = document.GetSyntaxRootAsync(cancellationToken).WaitAndGetResult(cancellationToken);

            var ts           = selections.Single();
            int start        = text.Lines[ts.iStartLine].Start + ts.iStartIndex;
            int end          = text.Lines[ts.iEndLine].Start + ts.iEndIndex;
            var adjustedSpan = GetFormattingSpan(root, start, end);

            // Since we know we are on the UI thread, lets get the base indentation now, so that there is less
            // cleanup work to do later in Venus.
            var ruleFactory = this.Workspace.Services.GetService <IHostDependentFormattingRuleFactoryService>();
            var rules       = ruleFactory.CreateRule(document, start).Concat(Formatter.GetDefaultFormattingRules(document));

            // use formatting that return text changes rather than tree rewrite which is more expensive
            var originalChanges = Formatter.GetFormattedTextChanges(root, adjustedSpan, document.Project.Solution.Workspace, rules: rules, cancellationToken: cancellationToken);

            var originalSpan     = RoslynTextSpan.FromBounds(start, end);
            var formattedChanges = ruleFactory.FilterFormattedChanges(document, originalSpan, originalChanges);

            if (formattedChanges.IsEmpty())
            {
                return(VSConstants.S_OK);
            }

            // create new formatted document
            var formattedDocument = document.WithText(text.WithChanges(formattedChanges));

            formattedDocument.Project.Solution.Workspace.ApplyDocumentChanges(formattedDocument, cancellationToken);

            return(VSConstants.S_OK);
        }
Пример #9
0
 private TextSpan GetTextSpanFromCaret(SnapshotPoint caretPosition, ITextSnapshotLine caretLine)
 {
     if (caretLine.Length == 0)
     {
         return(TextSpan.FromBounds(caretLine.Start.Position, caretLine.End.Position));
     }
     else if (caretPosition.Position < caretLine.End.Position)
     {
         var nextPoint = caretPosition.Add(1);
         return(TextSpan.FromBounds(caretPosition.Position, nextPoint.Position));
     }
     else
     {
         var prevPoint = caretPosition.Add(-1);
         return(TextSpan.FromBounds(prevPoint.Position, caretPosition.Position));
     }
 }
        private static List <PropertyDeclarationSyntax> ExtractCSharpProperties(SourceText newText, IReadOnlyList <RazorDirectiveSyntax> codeBlocks)
        {
            var propertyList = new List <PropertyDeclarationSyntax>();

            for (var i = 0; i < codeBlocks.Count; i++)
            {
                var bodyRange    = ((RazorDirectiveBodySyntax)codeBlocks[i].Body).CSharpCode.Span;
                var bodyTextSpan = TextSpan.FromBounds(bodyRange.Start, bodyRange.End);
                var subText      = newText.GetSubText(bodyTextSpan);
                var parsedText   = CSharpSyntaxTree.ParseText(subText);
                var root         = parsedText.GetRoot();
                var childNodes   = root.ChildNodes();
                var properties   = childNodes.Where(node => node.Kind() == CodeAnalysis.CSharp.SyntaxKind.PropertyDeclaration).OfType <PropertyDeclarationSyntax>();
                propertyList.AddRange(properties);
            }

            return(propertyList);
        }
Пример #11
0
        private void TrackChangeInSpan(SourceText oldText, TextSpan originalSpan, SourceText newText, out TextSpan changedSpan, out TextSpan changeEncompassingSpan)
        {
            var affectedRange = newText.GetEncompassingTextChangeRange(oldText);

            // The span of text before the edit which is being changed
            changeEncompassingSpan = affectedRange.Span;

            if (!originalSpan.Contains(changeEncompassingSpan))
            {
                _logger.LogDebug($"The changed region {changeEncompassingSpan} was not a subset of the span {originalSpan} being tracked. This is unexpected.");
            }

            // We now know what was the range that changed and the length of that span after the change.
            // Let's now compute what the original span looks like after the change.
            // We know it still starts from the same location but could have grown or shrunk in length.
            // Compute the change in length and then update the original span.
            var changeInOriginalSpanLength = affectedRange.NewLength - changeEncompassingSpan.Length;

            changedSpan = TextSpan.FromBounds(originalSpan.Start, originalSpan.End + changeInOriginalSpanLength);
        }
        private static RoslynTextSpan GetFormattingSpan(SyntaxNode root, int start, int end)
        {
            // HACK: The formatting engine is inclusive in it's spans, so it won't insert
            // adjust the indentation if there is a token right at the spans we start at.
            // Instead, we make sure we include preceding indentation.
            var prevToken = root.FindToken(start).GetPreviousToken();

            if (prevToken != default)
            {
                start = prevToken.Span.Start;
            }

            var nextToken = root.FindTokenFromEnd(end).GetNextToken();

            if (nextToken != default)
            {
                end = nextToken.Span.End;
            }

            return(RoslynTextSpan.FromBounds(start, end));
        }
 /// <summary>
 /// It is unclear why, but we are sometimes asked to navigate to a <see cref="TextSpan"/>
 /// that is not inside the bounds of the associated <see cref="Document"/>. This method
 /// returns a span that is guaranteed to be inside the <see cref="Document"/> bounds. If
 /// the returned span is different from the given span, then the worst observable behavior
 /// is either no navigation or navigation to the end of the document.
 /// See https://github.com/dotnet/roslyn/issues/7660 for more details.
 /// </summary>
 private static TextSpan GetSpanWithinDocumentBounds(TextSpan span, int documentLength)
 {
     return TextSpan.FromBounds(GetPositionWithinDocumentBounds(span.Start, documentLength), GetPositionWithinDocumentBounds(span.End, documentLength));
 }
        protected async Task <List <TextChange> > AdjustIndentationAsync(FormattingContext context, CancellationToken cancellationToken, Range?range = null)
        {
            // In this method, the goal is to make final adjustments to the indentation of each line.
            // We will take into account the following,
            // 1. The indentation due to nested C# structures
            // 2. The indentation due to Razor and HTML constructs

            var text = context.SourceText;

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);

            // To help with figuring out the correct indentation, first we will need the indentation
            // that the C# formatter wants to apply in the following locations,
            // 1. The start and end of each of our source mappings
            // 2. The start of every line that starts in C# context

            // Due to perf concerns, we only want to invoke the real C# formatter once.
            // So, let's collect all the significant locations that we want to obtain the CSharpDesiredIndentations for.

            var significantLocations = new HashSet <int>();

            // First, collect all the locations at the beginning and end of each source mapping.
            var sourceMappingMap = new Dictionary <int, int>();

            foreach (var mapping in context.CodeDocument.GetCSharpDocument().SourceMappings)
            {
                var mappingSpan = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                if (!ShouldFormat(context, mappingSpan, allowImplicitStatements: true))
                {
                    // We don't care about this range as this can potentially lead to incorrect scopes.
                    continue;
                }

                var originalStartLocation  = mapping.OriginalSpan.AbsoluteIndex;
                var projectedStartLocation = mapping.GeneratedSpan.AbsoluteIndex;
                sourceMappingMap[originalStartLocation] = projectedStartLocation;
                significantLocations.Add(projectedStartLocation);

                var originalEndLocation  = mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length + 1;
                var projectedEndLocation = mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length + 1;
                sourceMappingMap[originalEndLocation] = projectedEndLocation;
                significantLocations.Add(projectedEndLocation);
            }

            // Next, collect all the line starts that start in C# context
            var indentations = context.GetIndentations();
            var lineStartMap = new Dictionary <int, int>();

            for (var i = range.Start.Line; i <= range.End.Line; i++)
            {
                if (indentations[i].EmptyOrWhitespaceLine)
                {
                    // We should remove whitespace on empty lines.
                    continue;
                }

                var line      = context.SourceText.Lines[i];
                var lineStart = line.GetFirstNonWhitespacePosition() ?? line.Start;

                var lineStartSpan = new TextSpan(lineStart, 0);
                if (!ShouldFormat(context, lineStartSpan, allowImplicitStatements: true))
                {
                    // We don't care about this range as this can potentially lead to incorrect scopes.
                    continue;
                }

                if (DocumentMappingService.TryMapToProjectedDocumentPosition(context.CodeDocument, lineStart, out _, out var projectedLineStart))
                {
                    lineStartMap[lineStart] = projectedLineStart;
                    significantLocations.Add(projectedLineStart);
                }
            }

            // Now, invoke the C# formatter to obtain the CSharpDesiredIndentation for all significant locations.
            var significantLocationIndentation = await CSharpFormatter.GetCSharpIndentationAsync(context, significantLocations, cancellationToken);

            // Build source mapping indentation scopes.
            var sourceMappingIndentations = new SortedDictionary <int, int>();

            foreach (var originalLocation in sourceMappingMap.Keys)
            {
                var significantLocation = sourceMappingMap[originalLocation];
                if (!significantLocationIndentation.TryGetValue(significantLocation, out var indentation))
                {
                    // C# formatter didn't return an indentation for this. Skip.
                    continue;
                }

                sourceMappingIndentations[originalLocation] = indentation;
            }

            var sourceMappingIndentationScopes = sourceMappingIndentations.Keys.ToArray();

            // Build lineStart indentation map.
            var lineStartIndentations = new Dictionary <int, int>();

            foreach (var originalLocation in lineStartMap.Keys)
            {
                var significantLocation = lineStartMap[originalLocation];
                if (!significantLocationIndentation.TryGetValue(significantLocation, out var indentation))
                {
                    // C# formatter didn't return an indentation for this. Skip.
                    continue;
                }

                lineStartIndentations[originalLocation] = indentation;
            }

            // Now, let's combine the C# desired indentation with the Razor and HTML indentation for each line.
            var newIndentations = new Dictionary <int, int>();

            for (var i = range.Start.Line; i <= range.End.Line; i++)
            {
                if (indentations[i].EmptyOrWhitespaceLine)
                {
                    // We should remove whitespace on empty lines.
                    newIndentations[i] = 0;
                    continue;
                }

                var minCSharpIndentation = context.GetIndentationOffsetForLevel(indentations[i].MinCSharpIndentLevel);
                var line          = context.SourceText.Lines[i];
                var lineStart     = line.GetFirstNonWhitespacePosition() ?? line.Start;
                var lineStartSpan = new TextSpan(lineStart, 0);
                if (!ShouldFormat(context, lineStartSpan, allowImplicitStatements: true))
                {
                    // We don't care about this line as it lies in an area we don't want to format.
                    continue;
                }

                if (!lineStartIndentations.TryGetValue(lineStart, out var csharpDesiredIndentation))
                {
                    // Couldn't remap. This is probably a non-C# location.
                    // Use SourceMapping indentations to locate the C# scope of this line.
                    // E.g,
                    //
                    // @if (true) {
                    //   <div>
                    //  |</div>
                    // }
                    //
                    // We can't find a direct mapping at |, but we can infer its base indentation from the
                    // indentation of the latest source mapping prior to this line.
                    // We use binary search to find that spot.

                    var index = Array.BinarySearch(sourceMappingIndentationScopes, lineStart);
                    if (index < 0)
                    {
                        // Couldn't find the exact value. Find the index of the element to the left of the searched value.
                        index = (~index) - 1;
                    }

                    if (index < 0)
                    {
                        // If we _still_ couldn't find the right indentation, then it probably means that the text is
                        // before the first source mapping location, so we can just place it in the minimum spot (realistically
                        // at index 0 in the razor file, but we use minCSharpIndentation because we're adjusting based on the
                        // generated file here)
                        csharpDesiredIndentation = minCSharpIndentation;
                    }
                    else
                    {
                        // index will now be set to the same value as the end of the closest source mapping.
                        var absoluteIndex = sourceMappingIndentationScopes[index];
                        csharpDesiredIndentation = sourceMappingIndentations[absoluteIndex];

                        // This means we didn't find an exact match and so we used the indentation of the end of a previous mapping.
                        // So let's use the MinCSharpIndentation of that same location if possible.
                        if (context.TryGetFormattingSpan(absoluteIndex, out var span))
                        {
                            minCSharpIndentation = context.GetIndentationOffsetForLevel(span.MinCSharpIndentLevel);
                        }
                    }
                }

                // Now let's use that information to figure out the effective C# indentation.
                // This should be based on context.
                // For instance, lines inside @code/@functions block should be reduced one level
                // and lines inside @{} should be reduced by two levels.

                if (csharpDesiredIndentation < minCSharpIndentation)
                {
                    // CSharp formatter doesn't want to indent this. Let's not touch it.
                    continue;
                }

                var effectiveCSharpDesiredIndentation = csharpDesiredIndentation - minCSharpIndentation;
                var razorDesiredIndentation           = context.GetIndentationOffsetForLevel(indentations[i].IndentationLevel);
                if (indentations[i].StartsInHtmlContext)
                {
                    // This is a non-C# line.
                    if (context.IsFormatOnType)
                    {
                        // HTML formatter doesn't run in the case of format on type.
                        // Let's stick with our syntax understanding of HTML to figure out the desired indentation.
                    }
                    else
                    {
                        // Given that the HTML formatter ran before this, we can assume
                        // HTML is already correctly formatted. So we can use the existing indentation as is.
                        // We need to make sure to use the indentation size, as this will get passed to
                        // GetIndentationString eventually.
                        razorDesiredIndentation = indentations[i].ExistingIndentationSize;
                    }
                }

                var effectiveDesiredIndentation = razorDesiredIndentation + effectiveCSharpDesiredIndentation;

                // This will now contain the indentation we ultimately want to apply to this line.
                newIndentations[i] = effectiveDesiredIndentation;
            }

            // Now that we have collected all the indentations for each line, let's convert them to text edits.
            var changes = new List <TextChange>();

            foreach (var item in newIndentations)
            {
                var line        = item.Key;
                var indentation = item.Value;
                Debug.Assert(indentation >= 0, "Negative indentation. This is unexpected.");

                var existingIndentationLength   = indentations[line].ExistingIndentation;
                var spanToReplace               = new TextSpan(context.SourceText.Lines[line].Start, existingIndentationLength);
                var effectiveDesiredIndentation = context.GetIndentationString(indentation);
                changes.Add(new TextChange(spanToReplace, effectiveDesiredIndentation));
            }

            return(changes);
        }
        protected static SourceText CleanupDocument(FormattingContext context, Range range = null)
        {
            //
            // We look through every source mapping that intersects with the affected range and
            // adjust the indentation of the first line,
            //
            // E.g,
            //
            // @{   public int x = 0;
            // }
            //
            // becomes,
            //
            // @{
            //    public int x  = 0;
            // }
            //
            var text = context.SourceText;

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);
            var csharpDocument = context.CodeDocument.GetCSharpDocument();

            var changes = new List <TextChange>();

            foreach (var mapping in csharpDocument.SourceMappings)
            {
                var mappingSpan  = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                var mappingRange = mappingSpan.AsRange(text);
                if (!range.LineOverlapsWith(mappingRange))
                {
                    // We don't care about this range. It didn't change.
                    continue;
                }

                var mappingStartLineIndex = mappingRange.Start.Line;
                if (context.Indentations[mappingStartLineIndex].StartsInCSharpContext)
                {
                    // Doesn't need cleaning up.
                    // For corner cases like (Range marked with |...|),
                    // @{
                    //     if (true} { <div></div>| }|
                    // }
                    // We want to leave it alone because tackling it here is really complicated.
                    continue;
                }

                if (!ShouldCleanup(context, mappingRange.Start))
                {
                    // We don't want to run cleanup on this range.
                    continue;
                }

                // @{
                //     if (true)
                //     {
                //         <div></div>|
                //
                //              |}
                // }
                // We want to return the length of the range marked by |...|
                //
                var whitespaceLength = text.GetFirstNonWhitespaceOffset(mappingSpan);
                if (whitespaceLength == null)
                {
                    // There was no content here. Skip.
                    continue;
                }

                var spanToReplace = new TextSpan(mappingSpan.Start, whitespaceLength.Value);
                if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel))
                {
                    // Can't find the correct indentation for this content. Leave it alone.
                    continue;
                }

                // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example.
                var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel);

                // After the below change the above example should look like,
                // @{
                //     if (true)
                //     {
                //         <div></div>
                //     }
                // }
                var change = new TextChange(spanToReplace, replacement);
                changes.Add(change);
            }

            var changedText = text.WithChanges(changes);

            return(changedText);
        }
Пример #16
0
        private List <TextChange> AdjustIndentation(FormattingContext context, CancellationToken cancellationToken, Range range = null)
        {
            // In this method, the goal is to make final adjustments to the indentation of each line.
            // We will take into account the following,
            // 1. The indentation due to nested C# structures
            // 2. The indentation due to Razor and HTML constructs

            var text = context.SourceText;

            range ??= TextSpan.FromBounds(0, text.Length).AsRange(text);

            // First, let's build an understanding of the desired C# indentation at the beginning and end of each source mapping.
            var sourceMappingIndentations = new SortedDictionary <int, int>();

            foreach (var mapping in context.CodeDocument.GetCSharpDocument().SourceMappings)
            {
                var mappingSpan  = new TextSpan(mapping.OriginalSpan.AbsoluteIndex, mapping.OriginalSpan.Length);
                var mappingRange = mappingSpan.AsRange(context.SourceText);
                if (!ShouldFormat(context, mappingRange.Start))
                {
                    // We don't care about this range as this can potentially lead to incorrect scopes.
                    continue;
                }

                var startIndentation = CSharpFormatter.GetCSharpIndentation(context, mapping.GeneratedSpan.AbsoluteIndex, cancellationToken);
                sourceMappingIndentations[mapping.OriginalSpan.AbsoluteIndex] = startIndentation;

                var endIndentation = CSharpFormatter.GetCSharpIndentation(context, mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length + 1, cancellationToken);
                sourceMappingIndentations[mapping.OriginalSpan.AbsoluteIndex + mapping.OriginalSpan.Length + 1] = endIndentation;
            }

            var sourceMappingIndentationScopes = sourceMappingIndentations.Keys.ToArray();

            // Now, let's combine the C# desired indentation with the Razor and HTML indentation for each line.
            var newIndentations = new Dictionary <int, int>();

            for (var i = range.Start.Line; i <= range.End.Line; i++)
            {
                var line = context.SourceText.Lines[i];
                if (line.Span.Length == 0)
                {
                    // We don't want to indent empty lines.
                    continue;
                }

                var lineStart = line.Start;
                int csharpDesiredIndentation;
                if (DocumentMappingService.TryMapToProjectedDocumentPosition(context.CodeDocument, lineStart, out _, out var projectedLineStart))
                {
                    // We were able to map this line to C# directly.
                    csharpDesiredIndentation = CSharpFormatter.GetCSharpIndentation(context, projectedLineStart, cancellationToken);
                }
                else
                {
                    // Couldn't remap. This is probably a non-C# location.
                    // Use SourceMapping indentations to locate the C# scope of this line.
                    // E.g,
                    //
                    // @if (true) {
                    //   <div>
                    //  |</div>
                    // }
                    //
                    // We can't find a direct mapping at |, but we can infer its base indentation from the
                    // indentation of the latest source mapping prior to this line.
                    // We use binary search to find that spot.

                    var index = Array.BinarySearch(sourceMappingIndentationScopes, lineStart);
                    if (index < 0)
                    {
                        // Couldn't find the exact value. Find the index of the element to the left of the searched value.
                        index = (~index) - 1;
                    }

                    // This will now be set to the same value as the end of the closest source mapping.
                    csharpDesiredIndentation = index < 0 ? 0 : sourceMappingIndentations[sourceMappingIndentationScopes[index]];
                }

                // Now let's use that information to figure out the effective C# indentation.
                // This should be based on context.
                // For instance, lines inside @code/@functions block should be reduced one level
                // and lines inside @{} should be reduced by two levels.

                var csharpDesiredIndentLevel = context.GetIndentationLevelForOffset(csharpDesiredIndentation);
                var minCSharpIndentLevel     = context.Indentations[i].MinCSharpIndentLevel;
                if (csharpDesiredIndentLevel < minCSharpIndentLevel)
                {
                    // CSharp formatter doesn't want to indent this. Let's not touch it.
                    continue;
                }

                var effectiveCSharpDesiredIndentationLevel = csharpDesiredIndentLevel - minCSharpIndentLevel;
                var razorDesiredIndentationLevel           = context.Indentations[i].IndentationLevel;
                if (!context.Indentations[i].StartsInCSharpContext)
                {
                    // This is a non-C# line. Given that the HTML formatter ran before this, we can assume
                    // HTML is already correctly formatted. So we can use the existing indentation as is.
                    razorDesiredIndentationLevel = context.GetIndentationLevelForOffset(context.Indentations[i].ExistingIndentation);
                }
                var effectiveDesiredIndentationLevel = razorDesiredIndentationLevel + effectiveCSharpDesiredIndentationLevel;

                // This will now contain the indentation we ultimately want to apply to this line.
                newIndentations[i] = effectiveDesiredIndentationLevel;
            }

            // Now that we have collected all the indentations for each line, let's convert them to text edits.
            var changes = new List <TextChange>();

            foreach (var item in newIndentations)
            {
                var line             = item.Key;
                var indentationLevel = item.Value;
                Debug.Assert(indentationLevel >= 0, "Negative indent level. This is unexpected.");

                var existingIndentationLength   = context.Indentations[line].ExistingIndentation;
                var spanToReplace               = new TextSpan(context.SourceText.Lines[line].Start, existingIndentationLength);
                var effectiveDesiredIndentation = context.GetIndentationLevelString(indentationLevel);
                changes.Add(new TextChange(spanToReplace, effectiveDesiredIndentation));
            }

            return(changes);
        }
Пример #17
0
        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);
                    }
                }
            }
        }
Пример #18
0
        private static void CleanupSourceMappingEnd(FormattingContext context, Range sourceMappingRange, List <TextChange> changes)
        {
            //
            // We look through every source mapping that intersects with the affected range and
            // bring the content after the last line to its own line and adjust its indentation,
            //
            // E.g,
            //
            // @{
            //     if (true)
            //     {  <div></div>
            //     }
            // }
            //
            // becomes,
            //
            // @{
            //    if (true)
            //    {
            //        </div></div>
            //    }
            // }
            //

            var text = context.SourceText;
            var sourceMappingSpan   = sourceMappingRange.AsTextSpan(text);
            var mappingEndLineIndex = sourceMappingRange.End.Line;

            if (!context.Indentations[mappingEndLineIndex].StartsInCSharpContext)
            {
                // For corner cases like (Position marked with |),
                // It is already in a separate line. It doesn't need cleaning up.
                // @{
                //     if (true}
                //     {
                //         |<div></div>
                //     }
                // }
                //
                return;
            }

            var endSpan = TextSpan.FromBounds(sourceMappingSpan.End, sourceMappingSpan.End);

            if (!ShouldFormat(context, endSpan, allowImplicitStatements: false))
            {
                // We don't want to run cleanup on this range.
                return;
            }

            var contentStartOffset = text.Lines[mappingEndLineIndex].GetFirstNonWhitespaceOffset(sourceMappingRange.End.Character);

            if (contentStartOffset == null)
            {
                // There is no content after the end of this source mapping. No need to clean up.
                return;
            }

            var spanToReplace = new TextSpan(sourceMappingSpan.End, 0);

            if (!context.TryGetIndentationLevel(spanToReplace.End, out var contentIndentLevel))
            {
                // Can't find the correct indentation for this content. Leave it alone.
                return;
            }

            // At this point, `contentIndentLevel` should contain the correct indentation level for `}` in the above example.
            var replacement = context.NewLineString + context.GetIndentationLevelString(contentIndentLevel);

            // After the below change the above example should look like,
            // @{
            //     if (true)
            //     {
            //         <div></div>
            //     }
            // }
            var change = new TextChange(spanToReplace, replacement);

            changes.Add(change);
        }
Пример #19
0
        /// <summary>
        /// Computes line starts faster given already computed line starts from text before the change.
        /// </summary>
        protected override TextLineCollection GetLinesCore()
        {
            SourceText?        oldText;
            TextLineCollection?oldLineInfo;

            if (!_info.WeakOldText.TryGetTarget(out oldText) || !oldText.TryGetLines(out oldLineInfo))
            {
                // no old line starts? do it the hard way.
                return(base.GetLinesCore());
            }

            // compute line starts given changes and line starts already computed from previous text
            var lineStarts = ArrayBuilder <int> .GetInstance();

            lineStarts.Add(0);

            // position in the original document
            var position = 0;

            // delta generated by already processed changes (position in the new document = position + delta)
            var delta = 0;

            // true if last segment ends with CR and we need to check for CR+LF code below assumes that both CR and LF are also line breaks alone
            var endsWithCR = false;

            foreach (var change in _info.ChangeRanges)
            {
                // include existing line starts that occur before this change
                if (change.Span.Start > position)
                {
                    if (endsWithCR && _newText[position + delta] == '\n')
                    {
                        // remove last added line start (it was due to previous CR)
                        // a new line start including the LF will be added next
                        lineStarts.RemoveLast();
                    }

                    var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, change.Span.Start));
                    for (int i = lps.Start.Line + 1; i <= lps.End.Line; i++)
                    {
                        lineStarts.Add(oldLineInfo[i].Start + delta);
                    }

                    endsWithCR = oldText[change.Span.Start - 1] == '\r';

                    // in case change is inserted between CR+LF we treat CR as line break alone,
                    // but this line break might be retracted and replaced with new one in case LF is inserted
                    if (endsWithCR && change.Span.Start < oldText.Length && oldText[change.Span.Start] == '\n')
                    {
                        lineStarts.Add(change.Span.Start + delta);
                    }
                }

                // include line starts that occur within newly inserted text
                if (change.NewLength > 0)
                {
                    var changeStart = change.Span.Start + delta;
                    var text        = GetSubText(new TextSpan(changeStart, change.NewLength));

                    if (endsWithCR && text[0] == '\n')
                    {
                        // remove last added line start (it was due to previous CR)
                        // a new line start including the LF will be added next
                        lineStarts.RemoveLast();
                    }

                    // Skip first line (it is always at offset 0 and corresponds to the previous line)
                    for (int i = 1; i < text.Lines.Count; i++)
                    {
                        lineStarts.Add(changeStart + text.Lines[i].Start);
                    }

                    endsWithCR = text[change.NewLength - 1] == '\r';
                }

                position = change.Span.End;
                delta   += (change.NewLength - change.Span.Length);
            }

            // include existing line starts that occur after all changes
            if (position < oldText.Length)
            {
                if (endsWithCR && _newText[position + delta] == '\n')
                {
                    // remove last added line start (it was due to previous CR)
                    // a new line start including the LF will be added next
                    lineStarts.RemoveLast();
                }

                var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, oldText.Length));
                for (int i = lps.Start.Line + 1; i <= lps.End.Line; i++)
                {
                    lineStarts.Add(oldLineInfo[i].Start + delta);
                }
            }

            return(new LineInfo(this, lineStarts.ToArrayAndFree()));
        }
Пример #20
0
        private async Task CommandCallbackAsync()
        {
            IWpfTextView textView = await ServiceProvider.GetWpfTextViewAsync();

            if (textView == null || Package.DisposalToken.IsCancellationRequested)
            {
                return;
            }

            SnapshotPoint     caretPosition = textView.Caret.Position.BufferPosition;
            ITextSnapshotLine caretLine     = caretPosition.GetContainingLine();

            if (caretLine == null)
            {
                return;
            }

            Document document = caretPosition.Snapshot.GetOpenDocumentInCurrentContextWithChanges();

            if (document == null || Package.DisposalToken.IsCancellationRequested)
            {
                return;
            }

            Task <SyntaxNode>    syntaxRootTask    = document.GetSyntaxRootAsync();
            Task <SemanticModel> semanticModelTask = document.GetSemanticModelAsync();
            await Task.WhenAll(syntaxRootTask, semanticModelTask);

                        #pragma warning disable VSTHRD002, VSTHRD103 // Avoid problematic synchronous waits - the results are already obtained
            SyntaxNode    syntaxRoot    = syntaxRootTask.Result;
            SemanticModel semanticModel = semanticModelTask.Result;
                        #pragma warning restore VSTHRD002, VSTHRD103

            if (syntaxRoot == null || semanticModel == null)
            {
                return;
            }

            TextSpan lineSpan = TextSpan.FromBounds(caretLine.Start.Position, caretLine.End.Position);

            if (!(syntaxRoot.FindNode(lineSpan) is MemberDeclarationSyntax memberNode))
            {
                return;
            }

            PXContext context = new PXContext(semanticModel.Compilation, Acuminator.Utilities.CodeAnalysisSettings.Default);

            if (!context.IsPlatformReferenced || Package.DisposalToken.IsCancellationRequested)
            {
                return;
            }

            ISymbol memberSymbol = GetMemberSymbol(memberNode, semanticModel, caretPosition);

            if (!CheckMemberSymbol(memberSymbol, context))
            {
                return;
            }

            await NavigateToHandlerOrDeclarationAsync(document, textView, memberSymbol, memberNode, semanticModel, context);
        }
Пример #21
0
 /// <summary>
 /// Convert a <see cref="LinePositionSpan"/> to <see cref="TextSpan"/>.
 /// </summary>
 public TextSpan GetTextSpan(LinePositionSpan span)
 {
     return(TextSpan.FromBounds(GetPosition(span.Start), GetPosition(span.End)));
 }