Exemplo n.º 1
0
        public override async Task <Range> TryResolveBreakpointRangeAsync(ITextBuffer textBuffer, int lineIndex, int characterIndex, CancellationToken cancellationToken)
        {
            if (textBuffer is null)
            {
                throw new ArgumentNullException(nameof(textBuffer));
            }

            if (!_fileUriProvider.TryGet(textBuffer, out var documentUri))
            {
                // Not an addressable Razor document. Do not allow a breakpoint here. In practice this shouldn't happen, just being defensive.
                return(null);
            }

            if (!_documentManager.TryGetDocument(documentUri, out var documentSnapshot))
            {
                // No associated Razor document. Do not allow a breakpoint here. In practice this shouldn't happen, just being defensive.
                return(null);
            }

            if (!documentSnapshot.TryGetVirtualDocument <CSharpVirtualDocumentSnapshot>(out var virtualDocument))
            {
                Debug.Fail($"Some how there's no C# document associated with the host Razor document {documentUri.OriginalString} when validating breakpoint locations.");
                return(null);
            }

            if (virtualDocument.HostDocumentSyncVersion != documentSnapshot.Version)
            {
                // C# document isn't up-to-date with the Razor document. Because VS' debugging tech is synchronous on the UI thread we have to bail. Ideally we'd wait
                // for the C# document to become "updated"; however, that'd require the UI thread to see that the C# buffer is updated. Because this call path blocks
                // the UI thread the C# document will never update until this path has exited. This means as a user types around the point of interest data may get stale
                // but will re-adjust later.
                return(null);
            }

            var cacheKey = new CacheKey(documentSnapshot.Uri, documentSnapshot.Version, lineIndex, characterIndex);

            if (_cache.TryGetValue(cacheKey, out var cachedRange))
            {
                // We've seen this request before, no need to go async.
                return(cachedRange);
            }

            var lspPosition      = new Position(lineIndex, characterIndex);
            var projectionResult = await _projectionProvider.GetProjectionAsync(documentSnapshot, lspPosition, cancellationToken).ConfigureAwait(false);

            if (projectionResult == null)
            {
                // Can't map the position, invalid breakpoint location.
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            if (projectionResult.LanguageKind != RazorLanguageKind.CSharp)
            {
                // We only allow breakpoints in C#
                return(null);
            }

            var syntaxTree = await virtualDocument.GetCSharpSyntaxTreeAsync(_workspace, cancellationToken).ConfigureAwait(false);

            if (!RazorBreakpointSpans.TryGetBreakpointSpan(syntaxTree, projectionResult.PositionIndex, cancellationToken, out var csharpBreakpointSpan))
            {
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            virtualDocument.Snapshot.GetLineAndCharacter(csharpBreakpointSpan.Start, out var startLineIndex, out var startCharacterIndex);
            virtualDocument.Snapshot.GetLineAndCharacter(csharpBreakpointSpan.End, out var endLineIndex, out var endCharacterIndex);

            var projectedRange = new[]
            {
                new Range()
                {
                    Start = new Position(startLineIndex, startCharacterIndex),
                    End   = new Position(endLineIndex, endCharacterIndex),
                },
            };
            var hostDocumentMapping = await _documentMappingProvider.MapToDocumentRangesAsync(RazorLanguageKind.CSharp, documentUri, projectedRange, cancellationToken).ConfigureAwait(false);

            if (hostDocumentMapping == null)
            {
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            var hostDocumentRange = hostDocumentMapping.Ranges.FirstOrDefault();

            // Cache range so if we're asked again for this document/line/character we don't have to go async.
            _cache.Set(cacheKey, hostDocumentRange);

            return(hostDocumentRange);
        }
        public async Task <RazorBreakpointSpanResponse?> Handle(RazorBreakpointSpanParams request, CancellationToken cancellationToken)
        {
            var documentSnapshot = await TryGetDocumentSnapshotAndVersionAsync(request.Uri.GetAbsoluteOrUNCPath(), cancellationToken).ConfigureAwait(false);

            if (documentSnapshot is null)
            {
                return(null);
            }

            var codeDocument = await documentSnapshot.GetGeneratedOutputAsync();

            var sourceText = await documentSnapshot.GetTextAsync();

            var linePosition      = new LinePosition(request.Position.Line, request.Position.Character);
            var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition);

            if (codeDocument.IsUnsupported())
            {
                return(null);
            }

            var projectedIndex = hostDocumentIndex;
            var languageKind   = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex);

            // If we're in C#, then map to the right position in the generated document
            if (languageKind == RazorLanguageKind.CSharp &&
                !_documentMappingService.TryMapToProjectedDocumentPosition(codeDocument, hostDocumentIndex, out _, out projectedIndex))
            {
                return(null);
            }
            // Otherwise see if there is more C# on the line to map to
            else if (languageKind == RazorLanguageKind.Html &&
                     !_documentMappingService.TryMapToProjectedDocumentOrNextCSharpPosition(codeDocument, hostDocumentIndex, out _, out projectedIndex))
            {
                return(null);
            }
            else if (languageKind == RazorLanguageKind.Razor)
            {
                return(null);
            }

            // Now ask Roslyn to adjust the breakpoint to a valid location in the code
            var csharpDocument = codeDocument.GetCSharpDocument();
            var syntaxTree     = CSharpSyntaxTree.ParseText(csharpDocument.GeneratedCode, cancellationToken: cancellationToken);

            if (!RazorBreakpointSpans.TryGetBreakpointSpan(syntaxTree, projectedIndex, cancellationToken, out var csharpBreakpointSpan))
            {
                return(null);
            }

            var csharpText = codeDocument.GetCSharpSourceText();

            csharpText.GetLineAndOffset(csharpBreakpointSpan.Start, out var startLineIndex, out var startCharacterIndex);
            csharpText.GetLineAndOffset(csharpBreakpointSpan.End, out var endLineIndex, out var endCharacterIndex);

            var projectedRange = new Range()
            {
                Start = new Position(startLineIndex, startCharacterIndex),
                End   = new Position(endLineIndex, endCharacterIndex),
            };

            // Now map that new C# location back to the host document
            var mappingBehavior = GetMappingBehavior(documentSnapshot);

            if (!_documentMappingService.TryMapFromProjectedDocumentRange(codeDocument, projectedRange, mappingBehavior, out var hostDocumentRange))
            {
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            _logger.LogTrace($"Breakpoint span request for ({request.Position.Line}, {request.Position.Character}) = ({hostDocumentRange.Start.Line}, {hostDocumentRange.Start.Character}");

            return(new RazorBreakpointSpanResponse()
            {
                Range = hostDocumentRange
            });
        }
        public override async Task <Range> TryResolveBreakpointRangeAsync(ITextBuffer textBuffer, int lineIndex, int characterIndex, CancellationToken cancellationToken)
        {
            if (textBuffer is null)
            {
                throw new ArgumentNullException(nameof(textBuffer));
            }

            if (!_fileUriProvider.TryGet(textBuffer, out var documentUri))
            {
                // Not an addressable Razor document. Do not allow a breakpoint here. In practice this shouldn't happen, just being defensive.
                return(null);
            }

            if (!_documentManager.TryGetDocument(documentUri, out var documentSnapshot))
            {
                // No associated Razor document. Do not allow a breakpoint here. In practice this shouldn't happen, just being defensive.
                return(null);
            }

            var cacheKey = new CacheKey(documentSnapshot.Uri, documentSnapshot.Version, lineIndex, characterIndex);

            if (_cache.TryGetValue(cacheKey, out var cachedRange))
            {
                // We've seen this request before, no need to go async.
                return(cachedRange);
            }

            var lspPosition      = new Position(lineIndex, characterIndex);
            var projectionResult = await _projectionProvider.GetProjectionAsync(documentSnapshot, lspPosition, cancellationToken).ConfigureAwait(false);

            if (projectionResult == null)
            {
                // Can't map the position, invalid breakpoint location.
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            if (projectionResult.LanguageKind != RazorLanguageKind.CSharp)
            {
                // We only allow breakpoints in C#
                return(null);
            }

            if (!documentSnapshot.TryGetVirtualDocument <CSharpVirtualDocumentSnapshot>(out var virtualDocument))
            {
                Debug.Fail($"Some how there's no C# document associated with the host Razor document {documentUri.OriginalString} when validating breakpoint locations.");
                return(null);
            }

            _workspaceAccessor.TryGetWorkspace(textBuffer, out var workspace);

            var syntaxTree = await virtualDocument.GetCSharpSyntaxTreeAsync(workspace, cancellationToken).ConfigureAwait(false);

            if (!RazorBreakpointSpans.TryGetBreakpointSpan(syntaxTree, projectionResult.PositionIndex, cancellationToken, out var csharpBreakpointSpan))
            {
                return(null);
            }

            virtualDocument.Snapshot.GetLineAndCharacter(csharpBreakpointSpan.Start, out var startLineIndex, out var startCharacterIndex);
            virtualDocument.Snapshot.GetLineAndCharacter(csharpBreakpointSpan.End, out var endLineIndex, out var endCharacterIndex);

            var projectedRange = new[]
            {
                new Range()
                {
                    Start = new Position(startLineIndex, startCharacterIndex),
                    End   = new Position(endLineIndex, endCharacterIndex),
                },
            };
            var hostDocumentMapping = await _documentMappingProvider.MapToDocumentRangesAsync(RazorLanguageKind.CSharp, documentUri, projectedRange, cancellationToken).ConfigureAwait(false);

            if (hostDocumentMapping == null)
            {
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

            var hostDocumentRange = hostDocumentMapping.Ranges.FirstOrDefault();

            // Cache range so if we're asked again for this document/line/character we don't have to go async.
            _cache.Set(cacheKey, hostDocumentRange);

            return(hostDocumentRange);
        }