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); }