public async override Task <RazorMapToDocumentRangeResponse> MapToDocumentRangeAsync(RazorLanguageKind languageKind, Uri razorDocumentUri, Range projectedRange, CancellationToken cancellationToken)
        {
            if (razorDocumentUri is null)
            {
                throw new ArgumentNullException(nameof(razorDocumentUri));
            }

            if (projectedRange is null)
            {
                throw new ArgumentNullException(nameof(projectedRange));
            }

            var mapToDocumentRangeParams = new RazorMapToDocumentRangeParams()
            {
                Kind             = languageKind,
                RazorDocumentUri = razorDocumentUri,
                ProjectedRange   = new Range()
                {
                    Start = new Position(projectedRange.Start.Line, projectedRange.Start.Character),
                    End   = new Position(projectedRange.End.Line, projectedRange.End.Character)
                }
            };

            var documentMappingResponse = await _requestInvoker.RequestServerAsync <RazorMapToDocumentRangeParams, RazorMapToDocumentRangeResponse>(
                LanguageServerConstants.RazorMapToDocumentRangeEndpoint,
                LanguageServerKind.Razor,
                mapToDocumentRangeParams,
                cancellationToken).ConfigureAwait(false);

            return(documentMappingResponse);
        }
        public async Task <CompletionItem> HandleRequestAsync(CompletionItem request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (request?.Data == null)
            {
                return(request);
            }

            CompletionResolveData resolveData;

            if (request.Data is CompletionResolveData data)
            {
                resolveData = data;
            }
            else
            {
                resolveData = ((JToken)request.Data).ToObject <CompletionResolveData>();
            }

            // Set the original resolve data back so the language server deserializes it correctly.
            request.Data = resolveData.OriginalData;

            if (resolveData.LanguageServerKind != LanguageServerKind.CSharp)
            {
                // We currently only want to resolve C# completion items.
                return(request);
            }

            var result = await _requestInvoker.RequestServerAsync <CompletionItem, CompletionItem>(
                Methods.TextDocumentCompletionResolveName,
                resolveData.LanguageServerKind,
                request,
                cancellationToken).ConfigureAwait(false);

            return(result);
        }
        public async Task <Hover> HandleRequestAsync(TextDocumentPositionParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            if (!_documentManager.TryGetDocument(request.TextDocument.Uri, out var documentSnapshot))
            {
                return(null);
            }

            // Switch to a background thread.
            await TaskScheduler.Default;

            var projectionResult = await _projectionProvider.GetProjectionAsync(documentSnapshot, request.Position, cancellationToken).ConfigureAwait(false);

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

            var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;

            var textDocumentPositionParams = new TextDocumentPositionParams()
            {
                Position     = projectionResult.Position,
                TextDocument = new TextDocumentIdentifier()
                {
                    Uri = projectionResult.Uri
                }
            };

            Hover result;

            try
            {
                result = await _requestInvoker.RequestServerAsync <TextDocumentPositionParams, Hover>(
                    Methods.TextDocumentHoverName,
                    serverKind,
                    textDocumentPositionParams,
                    cancellationToken).ConfigureAwait(false);
            }
            catch
            {
                // Ensure we fail silently (Temporary till roslyn update is live)
                return(null);
            }

            if (result?.Range == null || result?.Contents == null)
            {
                return(null);
            }

            var mappingResult = await _documentMappingProvider.MapToDocumentRangeAsync(projectionResult.LanguageKind, request.TextDocument.Uri, result.Range, cancellationToken).ConfigureAwait(false);

            if (mappingResult == null)
            {
                // Couldn't remap the edits properly. Returning hover at initial request position.
                return(new Hover
                {
                    Contents = result.Contents,
                    Range = new Range()
                    {
                        Start = request.Position,
                        End = request.Position
                    }
                });
            }
            else if (mappingResult.HostDocumentVersion != documentSnapshot.Version)
            {
                // Discard hover if document has changed.
                return(null);
            }

            return(new Hover
            {
                Contents = result.Contents,
                Range = mappingResult.Range
            });
        }
        public override async Task <ProjectionResult> GetProjectionAsync(LSPDocumentSnapshot documentSnapshot, Position position, CancellationToken cancellationToken)
        {
            if (documentSnapshot is null)
            {
                throw new ArgumentNullException(nameof(documentSnapshot));
            }

            if (position is null)
            {
                throw new ArgumentNullException(nameof(position));
            }

            var languageQueryParams = new RazorLanguageQueryParams()
            {
                Position = new Position(position.Line, position.Character),
                Uri      = documentSnapshot.Uri
            };

            var languageResponse = await _requestInvoker.RequestServerAsync <RazorLanguageQueryParams, RazorLanguageQueryResponse>(
                LanguageServerConstants.RazorLanguageQueryEndpoint,
                LanguageServerKind.Razor,
                languageQueryParams,
                cancellationToken).ConfigureAwait(false);

            VirtualDocumentSnapshot virtualDocument;

            if (languageResponse.Kind == RazorLanguageKind.CSharp &&
                documentSnapshot.TryGetVirtualDocument <CSharpVirtualDocumentSnapshot>(out var csharpDoc))
            {
                virtualDocument = csharpDoc;
            }
            else if (languageResponse.Kind == RazorLanguageKind.Html &&
                     documentSnapshot.TryGetVirtualDocument <HtmlVirtualDocumentSnapshot>(out var htmlDoc))
            {
                virtualDocument = htmlDoc;
            }
            else
            {
                return(null);
            }

            if (languageResponse.HostDocumentVersion == UndefinedDocumentVersion)
            {
                // There should always be a document version attached to an open document.
                // Log it and move on as if it was synchronized.
                _logger.LogVerbose($"Could not find a document version associated with the document '{documentSnapshot.Uri}'");
            }
            else
            {
                var synchronized = await _documentSynchronizer.TrySynchronizeVirtualDocumentAsync(documentSnapshot.Version, virtualDocument, cancellationToken).ConfigureAwait(false);

                if (!synchronized)
                {
                    // Could not synchronize
                    return(null);
                }
            }

            var result = new ProjectionResult()
            {
                Uri                 = virtualDocument.Uri,
                Position            = languageResponse.Position,
                PositionIndex       = languageResponse.PositionIndex,
                LanguageKind        = languageResponse.Kind,
                HostDocumentVersion = languageResponse.HostDocumentVersion
            };

            return(result);
        }
        public async Task <SumType <CompletionItem[], CompletionList>?> HandleRequestAsync(CompletionParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            if (!_documentManager.TryGetDocument(request.TextDocument.Uri, out var documentSnapshot))
            {
                return(null);
            }

            // Switch to a background thread.
            await TaskScheduler.Default;

            var projectionResult = await _projectionProvider.GetProjectionAsync(documentSnapshot, request.Position, cancellationToken).ConfigureAwait(false);

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



            var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;

            var(succeeded, result) = await TryGetProvisionalCompletionsAsync(request, documentSnapshot, projectionResult, cancellationToken).ConfigureAwait(false);

            if (succeeded)
            {
                // This means the user has just typed a dot after some identifier such as (cursor is pipe): "DateTime.| "
                // In this case Razor interprets after the dot as Html and before it as C#.
                // We use this criteria to provide a better completion experience for what we call provisional changes.
            }
            else if (!TriggerAppliesToProjection(request.Context, projectionResult.LanguageKind))
            {
                return(null);
            }
            else
            {
                // This is a valid non-provisional completion request.
                var completionParams = new CompletionParams()
                {
                    Context      = request.Context,
                    Position     = projectionResult.Position,
                    TextDocument = new TextDocumentIdentifier()
                    {
                        Uri = projectionResult.Uri
                    }
                };

                result = await _requestInvoker.RequestServerAsync <CompletionParams, SumType <CompletionItem[], CompletionList>?>(
                    Methods.TextDocumentCompletionName,
                    serverKind,
                    completionParams,
                    cancellationToken).ConfigureAwait(false);
            }

            if (result.HasValue)
            {
                // Set some context on the CompletionItem so the CompletionResolveHandler can handle it accordingly.
                result = SetResolveData(result.Value, serverKind);
            }

            return(result);
        }
Exemplo n.º 6
0
        public async Task <TextEdit[]> HandleRequestAsync(DocumentOnTypeFormattingParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (!AllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                // We haven't built support for this character yet.
                return(EmptyEdits);
            }

            await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            if (!_documentManager.TryGetDocument(request.TextDocument.Uri, out var documentSnapshot))
            {
                return(EmptyEdits);
            }

            await SwitchToBackgroundThread().ConfigureAwait(false);

            var projectionResult = await _projectionProvider.GetProjectionAsync(documentSnapshot, request.Position, cancellationToken).ConfigureAwait(false);

            if (projectionResult == null || projectionResult.LanguageKind != RazorLanguageKind.Html)
            {
                return(EmptyEdits);
            }

            if (request.Options.OtherOptions == null)
            {
                request.Options.OtherOptions = new Dictionary <string, object>();
            }
            request.Options.OtherOptions[LanguageServerConstants.ExpectsCursorPlaceholderKey] = true;

            var formattingParams = new DocumentOnTypeFormattingParams()
            {
                Character    = request.Character,
                Options      = request.Options,
                Position     = projectionResult.Position,
                TextDocument = new TextDocumentIdentifier()
                {
                    Uri = projectionResult.Uri
                }
            };

            var serverKind = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;
            var edits      = await _requestInvoker.RequestServerAsync <DocumentOnTypeFormattingParams, TextEdit[]>(
                Methods.TextDocumentOnTypeFormattingName,
                serverKind,
                formattingParams,
                cancellationToken).ConfigureAwait(false);

            if (edits == null)
            {
                return(EmptyEdits);
            }

            var mappedEdits = new List <TextEdit>();

            foreach (var edit in edits)
            {
                if (edit.Range == null || edit.NewText == null)
                {
                    // Sometimes the HTML language server returns invalid edits like these. We should just ignore those.
                    continue;
                }

                var mappingResult = await _documentMappingProvider.MapToDocumentRangeAsync(projectionResult.LanguageKind, request.TextDocument.Uri, edit.Range, cancellationToken).ConfigureAwait(false);

                if (mappingResult == null || mappingResult.HostDocumentVersion != documentSnapshot.Version)
                {
                    // Couldn't remap the edits properly. Discard this request.
                    return(EmptyEdits);
                }

                var mappedEdit = new TextEdit()
                {
                    NewText = edit.NewText,
                    Range   = mappingResult.Range
                };
                mappedEdits.Add(mappedEdit);
            }

            if (mappedEdits.Count == 0)
            {
                return(EmptyEdits);
            }

            await _joinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

            if (!_documentManager.TryGetDocument(request.TextDocument.Uri, out var newDocumentSnapshot) ||
                newDocumentSnapshot.Version != documentSnapshot.Version)
            {
                // The document changed while were working on the background. Discard this request.
                return(EmptyEdits);
            }

            await _editorService.ApplyTextEditsAsync(documentSnapshot.Uri, documentSnapshot.Snapshot, mappedEdits).ConfigureAwait(false);

            // We would have already applied the edits and moved the cursor. Return empty.
            return(EmptyEdits);
        }