// Internal for testing internal async Task PublishDiagnosticsAsync(DocumentSnapshot document) { var result = await document.GetGeneratedOutputAsync(); var diagnostics = result.GetCSharpDocument().Diagnostics; lock (_publishedDiagnostics) { if (_publishedDiagnostics.TryGetValue(document.FilePath, out var previousDiagnostics) && diagnostics.SequenceEqual(previousDiagnostics)) { // Diagnostics are the same as last publish return; } _publishedDiagnostics[document.FilePath] = diagnostics; } if (!document.TryGetText(out var sourceText)) { Debug.Fail("Document source text should already be available."); } var convertedDiagnostics = diagnostics.Select(razorDiagnostic => RazorDiagnosticConverter.Convert(razorDiagnostic, sourceText)); PublishDiagnosticsForFilePath(document.FilePath, convertedDiagnostics); if (_logger.IsEnabled(LogLevel.Trace)) { var diagnosticString = string.Join(", ", diagnostics.Select(diagnostic => diagnostic.Id)); _logger.LogTrace($"Publishing diagnostics for document '{document.FilePath}': {diagnosticString}"); } }
public async Task AddEditsForCodeDocumentAsync(List <WorkspaceEditDocumentChange> documentChanges, IReadOnlyList <TagHelperDescriptor> originTagHelpers, string newName, DocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { if (documentSnapshot is null) { return; } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument.IsUnsupported()) { return; } if (!FileKinds.IsComponent(codeDocument.GetFileKind())) { return; } var uri = new UriBuilder { Path = documentSnapshot.FilePath, Host = string.Empty, Scheme = Uri.UriSchemeFile, }.Uri; AddEditsForCodeDocument(documentChanges, originTagHelpers, newName, uri, codeDocument); }
public override async Task <TextEdit[]> ApplyFormattedEditsAsync( DocumentUri uri, DocumentSnapshot documentSnapshot, RazorLanguageKind kind, TextEdit[] formattedEdits, FormattingOptions options, CancellationToken cancellationToken, bool bypassValidationPasses = false) { if (kind == RazorLanguageKind.Html) { // We don't support formatting HTML edits yet. return(formattedEdits); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, isFormatOnType: true); var result = new FormattingResult(formattedEdits, kind); foreach (var pass in _formattingPasses) { if (pass.IsValidationPass && bypassValidationPasses) { continue; } cancellationToken.ThrowIfCancellationRequested(); result = await pass.ExecuteAsync(context, result, cancellationToken); } return(result.Edits); }
public override async Task <TextEdit[]> FormatAsync(Uri uri, DocumentSnapshot documentSnapshot, Range range, FormattingOptions options) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (documentSnapshot is null) { throw new ArgumentNullException(nameof(documentSnapshot)); } if (range is null) { throw new ArgumentNullException(nameof(range)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, range); var result = new FormattingResult(Array.Empty <TextEdit>()); foreach (var pass in _formattingPasses) { result = await pass.ExecuteAsync(context, result); } return(result.Edits); }
public async Task <ImmutableArray <RazorMappedSpanResult> > MapSpansAsync( Document document, IEnumerable <TextSpan> spans, CancellationToken cancellationToken) { if (document == null) { throw new ArgumentNullException(nameof(document)); } if (spans == null) { throw new ArgumentNullException(nameof(spans)); } // Called on an uninitialized document. if (_document == null) { return(ImmutableArray.Create <RazorMappedSpanResult>()); } var source = await _document.GetTextAsync().ConfigureAwait(false); var output = await _document.GetGeneratedOutputAsync().ConfigureAwait(false); var results = ImmutableArray.CreateBuilder <RazorMappedSpanResult>(); foreach (var span in spans) { if (TryGetMappedSpans(span, source, output.GetCSharpDocument(), out var linePositionSpan, out var mappedSpan)) { results.Add(new RazorMappedSpanResult(output.Source.FilePath, linePositionSpan, mappedSpan)); }
public async Task <RazorLanguageQueryResponse> Handle(RazorLanguageQueryParams request, CancellationToken cancellationToken) { long documentVersion = -1; DocumentSnapshot documentSnapshot = null; await Task.Factory.StartNew(() => { _documentResolver.TryResolveDocument(request.Uri.AbsolutePath, out documentSnapshot); if (!_documentVersionCache.TryGetDocumentVersion(documentSnapshot, out documentVersion)) { Debug.Fail("Document should always be available here."); } return(documentSnapshot); }, CancellationToken.None, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler); var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); var sourceText = await documentSnapshot.GetTextAsync(); var linePosition = new LinePosition((int)request.Position.Line, (int)request.Position.Character); var hostDocumentIndex = sourceText.Lines.GetPosition(linePosition); var responsePosition = request.Position; if (codeDocument.IsUnsupported()) { // All language queries on unsupported documents return Html. This is equivalent to what pre-VSCode Razor was capable of. return(new RazorLanguageQueryResponse() { Kind = RazorLanguageKind.Html, Position = responsePosition, PositionIndex = hostDocumentIndex, HostDocumentVersion = documentVersion, }); } var syntaxTree = codeDocument.GetSyntaxTree(); var classifiedSpans = syntaxTree.GetClassifiedSpans(); var tagHelperSpans = syntaxTree.GetTagHelperSpans(); var languageKind = GetLanguageKind(classifiedSpans, tagHelperSpans, hostDocumentIndex); var responsePositionIndex = hostDocumentIndex; if (languageKind == RazorLanguageKind.CSharp) { if (TryGetCSharpProjectedPosition(codeDocument, hostDocumentIndex, out var projectedPosition, out var projectedIndex)) { // For C# locations, we attempt to return the corresponding position // within the projected document responsePosition = projectedPosition; responsePositionIndex = projectedIndex; } else { // It no longer makes sense to think of this location as C#, since it doesn't // correspond to any position in the projected document. This should not happen // since there should be source mappings for all the C# spans. languageKind = RazorLanguageKind.Razor; responsePositionIndex = hostDocumentIndex; } }
public override async Task <TextEdit[]> FormatAsync( DocumentUri uri, DocumentSnapshot documentSnapshot, Range range, FormattingOptions options, CancellationToken cancellationToken) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (documentSnapshot is null) { throw new ArgumentNullException(nameof(documentSnapshot)); } if (range is null) { throw new ArgumentNullException(nameof(range)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, _workspaceFactory); var originalText = context.SourceText; var result = new FormattingResult(Array.Empty <TextEdit>()); foreach (var pass in _formattingPasses) { cancellationToken.ThrowIfCancellationRequested(); result = await pass.ExecuteAsync(context, result, cancellationToken); } var filteredEdits = result.Edits.Where(e => range.LineOverlapsWith(e.Range)); // Make sure the edits actually change something, or its not worth responding var textChanges = filteredEdits.Select(e => e.AsTextChange(originalText)); var changedText = originalText.WithChanges(textChanges); if (changedText.ContentEquals(originalText)) { return(Array.Empty <TextEdit>()); } // Only send back the minimum edits var minimalChanges = SourceTextDiffer.GetMinimalTextChanges(originalText, changedText, lineDiffOnly: false); var finalEdits = minimalChanges.Select(f => f.AsTextEdit(originalText)).ToArray(); return(finalEdits); }
public override async Task <TextEdit[]> ApplyFormattedEditsAsync( DocumentUri uri, DocumentSnapshot documentSnapshot, RazorLanguageKind kind, TextEdit[] formattedEdits, FormattingOptions options, CancellationToken cancellationToken, bool bypassValidationPasses = false, bool collapseEdits = false) { if (kind == RazorLanguageKind.Html) { // We don't support formatting HTML edits yet. return(formattedEdits); } // If we only received a single edit, let's always return a single edit back. // Otherwise, merge only if explicitly asked. collapseEdits |= formattedEdits.Length == 1; var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, _workspaceFactory, isFormatOnType: true); var result = new FormattingResult(formattedEdits, kind); foreach (var pass in _formattingPasses) { if (pass.IsValidationPass && bypassValidationPasses) { continue; } cancellationToken.ThrowIfCancellationRequested(); result = await pass.ExecuteAsync(context, result, cancellationToken); } var originalText = context.SourceText; var edits = result.Edits; if (collapseEdits) { var collapsedEdit = MergeEdits(result.Edits, originalText); edits = new[] { collapsedEdit }; } // Make sure the edits actually change something, or its not worth responding var textChanges = edits.Select(e => e.AsTextChange(originalText)); var changedText = originalText.WithChanges(textChanges); if (changedText.ContentEquals(originalText)) { return(Array.Empty <TextEdit>()); } return(edits); }
private static async Task <RazorCodeDocument?> GetRazorCodeDocumentAsync(DocumentSnapshot documentSnapshot) { var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); if (codeDocument.IsUnsupported()) { return(null); } return(codeDocument); }
internal override async Task <ExcerptResultInternal?> TryGetExcerptInternalAsync( Document document, TextSpan span, ExcerptModeInternal mode, RazorClassificationOptionsWrapper options, CancellationToken cancellationToken) { if (_document is null) { return(null); } var mappedSpans = await _mappingService.MapSpansAsync(document, new[] { span }, cancellationToken).ConfigureAwait(false); if (mappedSpans.Length == 0 || mappedSpans[0].Equals(default(RazorMappedSpanResult))) { return(null); } var project = _document.Project; var razorDocument = project.GetDocument(mappedSpans[0].FilePath); if (razorDocument is null) { return(null); } var razorDocumentText = await razorDocument.GetTextAsync().ConfigureAwait(false); var razorDocumentSpan = razorDocumentText.Lines.GetTextSpan(mappedSpans[0].LinePositionSpan); var generatedDocument = document; // First compute the range of text we want to we to display relative to the primary document. var excerptSpan = ChooseExcerptSpan(razorDocumentText, razorDocumentSpan, mode); // Then we'll classify the spans based on the primary document, since that's the coordinate // space that our output mappings use. var output = await _document.GetGeneratedOutputAsync().ConfigureAwait(false); var mappings = output.GetCSharpDocument().SourceMappings; var classifiedSpans = await ClassifyPreviewAsync( excerptSpan, generatedDocument, mappings, options, cancellationToken).ConfigureAwait(false); var excerptText = GetTranslatedExcerptText(razorDocumentText, ref razorDocumentSpan, ref excerptSpan, classifiedSpans); return(new ExcerptResultInternal(excerptText, razorDocumentSpan, classifiedSpans.ToImmutable(), document, span)); }
public async override Task <TagHelperDescriptor?> TryGetTagHelperDescriptorAsync(DocumentSnapshot documentSnapshot, CancellationToken cancellationToken) { // No point doing anything if its not a component if (documentSnapshot.FileKind != FileKinds.Component) { return(null); } var razorCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (razorCodeDocument is null) { return(null); } var projects = await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync( () => _projectSnapshotManager.Projects.ToArray(), cancellationToken).ConfigureAwait(false); foreach (var project in projects) { // If the document is an import document, then it can't be a component if (project.IsImportDocument(documentSnapshot)) { return(null); } // If the document isn't in this project, then no point searching for components // This also avoids the issue of duplicate components if (!project.DocumentFilePaths.Contains(documentSnapshot.FilePath)) { return(null); } // If we got this far, we can check for tag helpers foreach (var tagHelper in project.TagHelpers) { // Check the typename and namespace match if (IsPathCandidateForComponent(documentSnapshot, tagHelper.GetTypeNameIdentifier()) && ComponentNamespaceMatchesFullyQualifiedName(razorCodeDocument, tagHelper.GetTypeNamespace())) { return(tagHelper); } } } return(null); }
public override async Task <TextEdit[]> ApplyFormattedEditsAsync( DocumentUri uri, DocumentSnapshot documentSnapshot, RazorLanguageKind kind, TextEdit[] formattedEdits, FormattingOptions options, CancellationToken cancellationToken, bool bypassValidationPasses = false, bool collapseEdits = false) { if (kind == RazorLanguageKind.Html) { // We don't support formatting HTML edits yet. return(formattedEdits); } // If we only received a single edit, let's always return a single edit back. // Otherwise, merge only if explicitly asked. collapseEdits |= formattedEdits.Length == 1; var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, isFormatOnType: true); var result = new FormattingResult(formattedEdits, kind); foreach (var pass in _formattingPasses) { if (pass.IsValidationPass && bypassValidationPasses) { continue; } cancellationToken.ThrowIfCancellationRequested(); result = await pass.ExecuteAsync(context, result, cancellationToken); } var edits = result.Edits; if (collapseEdits) { var collapsedEdit = MergeEdits(result.Edits, context.SourceText); edits = new[] { collapsedEdit }; } return(edits); }
public override async Task <TextEdit[]> FormatAsync( DocumentUri uri, DocumentSnapshot documentSnapshot, Range range, FormattingOptions options, CancellationToken cancellationToken) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (documentSnapshot is null) { throw new ArgumentNullException(nameof(documentSnapshot)); } if (range is null) { throw new ArgumentNullException(nameof(range)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); using var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, _workspaceFactory); var result = new FormattingResult(Array.Empty <TextEdit>()); foreach (var pass in _formattingPasses) { cancellationToken.ThrowIfCancellationRequested(); result = await pass.ExecuteAsync(context, result, cancellationToken); } var filteredEdits = result.Edits.Where(e => range.LineOverlapsWith(e.Range)).ToArray(); return(filteredEdits); }
public override async Task <TextEdit[]> ApplyFormattedEditsAsync(Uri uri, DocumentSnapshot documentSnapshot, RazorLanguageKind kind, TextEdit[] formattedEdits, FormattingOptions options) { if (kind == RazorLanguageKind.Html) { // We don't support formatting HTML edits yet. return(formattedEdits); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync(); var context = FormattingContext.Create(uri, documentSnapshot, codeDocument, options, isFormatOnType: true); var result = new FormattingResult(formattedEdits, kind); foreach (var pass in _formattingPasses) { result = await pass.ExecuteAsync(context, result); } return(result.Edits); }
// Maps a span in the primary buffer to the secondary buffer. This is only valid for C# code // that appears in the primary buffer. private async Task <TextSpan> GetSecondarySpanAsync(DocumentSnapshot primary, TextSpan primarySpan, Document secondary) { var output = await primary.GetGeneratedOutputAsync(); var mappings = output.GetCSharpDocument().SourceMappings; for (var i = 0; i < mappings.Count; i++) { var mapping = mappings[i]; if (mapping.OriginalSpan.AsTextSpan().Contains(primarySpan)) { var offset = mapping.GeneratedSpan.AbsoluteIndex - mapping.OriginalSpan.AbsoluteIndex; var secondarySpan = new TextSpan(primarySpan.Start + offset, primarySpan.Length); Assert.Equal( (await primary.GetTextAsync()).GetSubText(primarySpan).ToString(), (await secondary.GetTextAsync()).GetSubText(secondarySpan).ToString()); return(secondarySpan); } } throw new InvalidOperationException("Could not map the primary span to the generated code."); }
private async Task <Range> GetNavigateRangeAsync(DocumentSnapshot documentSnapshot, BoundAttributeDescriptor?attributeDescriptor, CancellationToken cancellationToken) { if (attributeDescriptor is not null) { _logger.LogInformation("Attempting to get definition from an attribute directly."); var originCodeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); var range = await TryGetPropertyRangeAsync(originCodeDocument, attributeDescriptor.GetPropertyName(), _documentMappingService, _logger, cancellationToken).ConfigureAwait(false); if (range is not null) { return(range); } } // When navigating from a start or end tag, we just take the user to the top of the file. // If we were trying to navigate to a property, and we couldn't find it, we can at least take // them to the file for the component. If the property was defined in a partial class they can // at least then press F7 to go there. return(new Range(new Position(0, 0), new Position(0, 0))); }
public async Task <RazorDiagnosticsResponse> Handle(RazorDiagnosticsParams request, CancellationToken cancellationToken) { if (request is null) { throw new ArgumentNullException(nameof(request)); } _logger.LogInformation($"Received {request.Kind:G} diagnostic request for {request.RazorDocumentUri} with {request.Diagnostics.Length} diagnostics."); cancellationToken.ThrowIfCancellationRequested(); int?documentVersion = null; DocumentSnapshot documentSnapshot = null; await Task.Factory.StartNew(() => { _documentResolver.TryResolveDocument(request.RazorDocumentUri.GetAbsoluteOrUNCPath(), out documentSnapshot); Debug.Assert(documentSnapshot != null, "Failed to get the document snapshot, could not map to document ranges."); if (documentSnapshot is null || !_documentVersionCache.TryGetDocumentVersion(documentSnapshot, out documentVersion)) { documentVersion = null; } }, cancellationToken, TaskCreationOptions.None, _foregroundDispatcher.ForegroundScheduler).ConfigureAwait(false); if (documentSnapshot is null) { _logger.LogInformation($"Failed to find document {request.RazorDocumentUri}."); return(new RazorDiagnosticsResponse() { Diagnostics = null, HostDocumentVersion = null }); } var codeDocument = await documentSnapshot.GetGeneratedOutputAsync().ConfigureAwait(false); if (codeDocument?.IsUnsupported() != false) { _logger.LogInformation("Unsupported code document."); return(new RazorDiagnosticsResponse() { Diagnostics = Array.Empty <Diagnostic>(), HostDocumentVersion = documentVersion }); } var unmappedDiagnostics = request.Diagnostics; var filteredDiagnostics = request.Kind == RazorLanguageKind.CSharp ? FilterCSharpDiagnostics(unmappedDiagnostics) : await FilterHTMLDiagnosticsAsync(unmappedDiagnostics, codeDocument, documentSnapshot).ConfigureAwait(false); if (!filteredDiagnostics.Any()) { _logger.LogInformation("No diagnostics remaining after filtering."); return(new RazorDiagnosticsResponse() { Diagnostics = Array.Empty <Diagnostic>(), HostDocumentVersion = documentVersion }); } _logger.LogInformation($"{filteredDiagnostics.Length}/{unmappedDiagnostics.Length} diagnostics remain after filtering."); var mappedDiagnostics = MapDiagnostics( request, filteredDiagnostics, codeDocument); _logger.LogInformation($"Returning {mappedDiagnostics.Length} mapped diagnostics."); return(new RazorDiagnosticsResponse() { Diagnostics = mappedDiagnostics, HostDocumentVersion = documentVersion, }); }
protected virtual Task ProcessDocument(DocumentSnapshot document) { return(document.GetGeneratedOutputAsync()); }
public async Task <ExcerptResult?> TryExcerptAsync( Document document, TextSpan span, ExcerptMode mode, CancellationToken cancellationToken) { if (_document == null) { return(null); } var mapped = await _mapper.MapSpansAsync(document, new[] { span }, cancellationToken).ConfigureAwait(false); if (mapped.Length == 0 || mapped[0].Equals(default(MappedSpanResult))) { return(null); } var project = _document.Project; var primaryDocument = project.GetDocument(mapped[0].FilePath); if (primaryDocument == null) { return(null); } var primaryText = await primaryDocument.GetTextAsync().ConfigureAwait(false); var primarySpan = primaryText.Lines.GetTextSpan(mapped[0].LinePositionSpan); var secondaryDocument = document; var secondarySpan = span; // First compute the range of text we want to we to display relative to the primary document. var excerptSpan = ChooseExcerptSpan(primaryText, primarySpan, mode); // Then we'll classify the spans based on the primary document, since that's the coordinate // space that our output mappings use. var output = await _document.GetGeneratedOutputAsync().ConfigureAwait(false); var mappings = output.GetCSharpDocument().SourceMappings; var classifiedSpans = await ClassifyPreviewAsync( primaryText, excerptSpan, secondaryDocument, mappings, cancellationToken).ConfigureAwait(false); // Now translate everything to be relative to the excerpt var offset = 0 - excerptSpan.Start; var excerptText = primaryText.GetSubText(excerptSpan); excerptSpan = new TextSpan(excerptSpan.Start + offset, excerptSpan.Length); for (var i = 0; i < classifiedSpans.Count; i++) { var classifiedSpan = classifiedSpans[i]; var updated = new TextSpan(classifiedSpan.TextSpan.Start + offset, classifiedSpan.TextSpan.Length); Debug.Assert(excerptSpan.Contains(updated)); classifiedSpans[i] = new ClassifiedSpan(classifiedSpan.ClassificationType, updated); } return(new ExcerptResult(excerptText, excerptSpan, classifiedSpans.ToImmutable(), document, span)); }
public async Task <RazorLanguageQueryResponse> Handle(RazorLanguageQueryParams request, CancellationToken cancellationToken) { int?documentVersion = null; DocumentSnapshot documentSnapshot = null; await _projectSnapshotManagerDispatcher.RunOnDispatcherThreadAsync(() => { _documentResolver.TryResolveDocument(request.Uri.GetAbsoluteOrUNCPath(), out documentSnapshot); Debug.Assert(documentSnapshot != null, "Failed to get the document snapshot, could not map to document ranges."); if (!_documentVersionCache.TryGetDocumentVersion(documentSnapshot, out documentVersion)) { // This typically happens for closed documents. documentVersion = null; } return(documentSnapshot); }, cancellationToken).ConfigureAwait(false); 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); var responsePosition = request.Position; if (codeDocument.IsUnsupported()) { // All language queries on unsupported documents return Html. This is equivalent to what pre-VSCode Razor was capable of. return(new RazorLanguageQueryResponse() { Kind = RazorLanguageKind.Html, Position = responsePosition, PositionIndex = hostDocumentIndex, HostDocumentVersion = documentVersion, }); } var responsePositionIndex = hostDocumentIndex; var languageKind = _documentMappingService.GetLanguageKind(codeDocument, hostDocumentIndex); if (languageKind == RazorLanguageKind.CSharp) { if (_documentMappingService.TryMapToProjectedDocumentPosition(codeDocument, hostDocumentIndex, out var projectedPosition, out var projectedIndex)) { // For C# locations, we attempt to return the corresponding position // within the projected document responsePosition = projectedPosition; responsePositionIndex = projectedIndex; } else { // It no longer makes sense to think of this location as C#, since it doesn't // correspond to any position in the projected document. This should not happen // since there should be source mappings for all the C# spans. languageKind = RazorLanguageKind.Razor; responsePositionIndex = hostDocumentIndex; } }