Exemple #1
0
        // 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);
        }
Exemple #3
0
        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);
        }
Exemple #4
0
        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);
        }
Exemple #5
0
        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;
                }
            }
Exemple #7
0
        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);
        }
Exemple #8
0
        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);
        }
Exemple #10
0
        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);
        }
Exemple #12
0
        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);
        }
Exemple #13
0
        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);
        }
Exemple #14
0
        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);
        }
Exemple #15
0
        // 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,
            });
        }
Exemple #18
0
 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;
                }
            }