Exemplo n.º 1
0
        private async Task <CompletionItem> PostProcessCompletionItemAsync(
            CompletionItem preResolveCompletionItem,
            CompletionItem resolvedCompletionItem,
            CompletionRequestContext requestContext,
            LSPDocumentSnapshot documentSnapshot,
            CancellationToken cancellationToken)
        {
            // This is a special contract between the Visual Studio LSP platform and language servers where if insert text and text edit's are not present
            // then the "resolve" endpoint is guaranteed to run prior to a completion item's content being comitted. This gives language servers the
            // opportunity to lazily evaluate text edits which in turn we need to remap. Given text edits generated through this mechanism tend to be
            // more exntensive we do a full remapping gesture which includes formatting of said text-edits.
            var shouldRemapTextEdits = preResolveCompletionItem.InsertText is null && preResolveCompletionItem.TextEdit is null;

            if (!shouldRemapTextEdits)
            {
                _logger.LogInformation("No TextEdit remap required.");
                return(resolvedCompletionItem);
            }

            _logger.LogInformation("Start formatting text edit.");

            var formattingOptions = _formattingOptionsProvider.GetOptions(documentSnapshot);

            if (resolvedCompletionItem.TextEdit != null)
            {
                var containsSnippet = resolvedCompletionItem.InsertTextFormat == InsertTextFormat.Snippet;
                var remappedEdits   = await _documentMappingProvider.RemapFormattedTextEditsAsync(
                    requestContext.ProjectedDocumentUri,
                    new[] { resolvedCompletionItem.TextEdit },
                    formattingOptions,
                    containsSnippet,
                    cancellationToken).ConfigureAwait(false);

                // We only passed in a single edit to be remapped
                var remappedEdit = remappedEdits.Single();
                resolvedCompletionItem.TextEdit = remappedEdit;

                _logger.LogInformation("Formatted text edit.");
            }

            if (resolvedCompletionItem.AdditionalTextEdits != null)
            {
                var remappedEdits = await _documentMappingProvider.RemapFormattedTextEditsAsync(
                    requestContext.ProjectedDocumentUri,
                    resolvedCompletionItem.AdditionalTextEdits,
                    formattingOptions,
                    containsSnippet : false, // Additional text edits can't contain snippets
                    cancellationToken).ConfigureAwait(false);

                resolvedCompletionItem.AdditionalTextEdits = remappedEdits;

                _logger.LogInformation("Formatted additional text edit.");
            }

            return(resolvedCompletionItem);
        }
Exemplo n.º 2
0
        public void TryGet_SetRequestContext_ReturnsTrue()
        {
            // Arrange
            var requestContext = new CompletionRequestContext(HostDocumentUri, ProjectedUri, LanguageServerKind);
            var resultId       = Cache.Set(requestContext);

            // Act
            var result = Cache.TryGet(resultId, out var retrievedRequestContext);

            // Assert
            Assert.True(result);
            Assert.Same(requestContext, retrievedRequestContext);
        }
        private static void AssociateRequest(LanguageServerKind requestKind, CompletionItem item, CompletionRequestContextCache cache, object originalData = null)
        {
            var documentUri    = new Uri("C:/path/to/file.razor");
            var projectedUri   = new Uri("C:/path/to/file.razor.g.xyz");
            var requestContext = new CompletionRequestContext(documentUri, projectedUri, requestKind);

            var resultId = cache.Set(requestContext);
            var data     = new CompletionResolveData()
            {
                ResultId     = resultId,
                OriginalData = originalData,
            };

            item.Data = data;
        }
Exemplo n.º 4
0
        public void TryGet_EvictedCompletionList_ReturnsFalse()
        {
            // Arrange
            var initialRequestContext   = new CompletionRequestContext(HostDocumentUri, ProjectedUri, LanguageServerKind);
            var initialRequestContextId = Cache.Set(initialRequestContext);

            for (var i = 0; i < CompletionRequestContextCache.MaxCacheSize; i++)
            {
                // We now fill the completion list cache up until its cache max so that the initial completion list we set gets evicted.
                Cache.Set(new CompletionRequestContext(HostDocumentUri, ProjectedUri, LanguageServerKind));
            }

            // Act
            var result = Cache.TryGet(initialRequestContextId, out var retrievedRequestContext);

            // Assert
            Assert.False(result);
            Assert.Null(retrievedRequestContext);
        }
        public long Set(CompletionRequestContext requestContext)
        {
            if (requestContext is null)
            {
                throw new ArgumentNullException(nameof(requestContext));
            }

            lock (_accessLock)
            {
                // If cache exceeds maximum size, remove the oldest list in the cache
                if (_completionRequests.Count >= MaxCacheSize)
                {
                    _completionRequests.RemoveAt(0);
                }

                var resultId = _nextResultId++;

                var cacheItem = new CompletionRequestCacheItem(resultId, requestContext);
                _completionRequests.Add(cacheItem);

                // Return generated resultId so completion list can later be retrieved from cache
                return(resultId);
            }
        }
 private record CompletionRequestCacheItem(long ResultId, CompletionRequestContext RequestContext);
Exemplo n.º 7
0
        public async Task <SumType <CompletionItem[], CompletionList>?> HandleRequestAsync(CompletionParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (request is null)
            {
                throw new ArgumentNullException(nameof(request));
            }

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

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

            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
            {
                var completionContext = RewriteContext(request.Context, projectionResult.LanguageKind);

                // This is a valid non-provisional completion request.
                var completionParams = new CompletionParams()
                {
                    Context      = completionContext,
                    Position     = projectionResult.Position,
                    TextDocument = new TextDocumentIdentifier()
                    {
                        Uri = projectionResult.Uri
                    }
                };

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

            if (TryConvertToCompletionList(result, out var completionList))
            {
                var wordExtent = documentSnapshot.Snapshot.GetWordExtent(request.Position.Line, request.Position.Character, _textStructureNavigator);

                if (serverKind == LanguageServerKind.CSharp)
                {
                    completionList = PostProcessCSharpCompletionList(request, documentSnapshot, wordExtent, completionList);
                }

                completionList = TranslateTextEdits(request.Position, projectionResult.Position, wordExtent, completionList);

                var requestContext = new CompletionRequestContext(documentSnapshot.Uri, projectionResult.Uri, serverKind);
                var resultId       = _completionRequestContextCache.Set(requestContext);
                SetResolveData(resultId, completionList);
            }

            return(completionList);
        public async Task <SumType <CompletionItem[], CompletionList>?> HandleRequestAsync(CompletionParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (request is null)
            {
                throw new ArgumentNullException(nameof(request));
            }

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

            _logger.LogInformation($"Starting request for {request.TextDocument.Uri}.");

            if (!_documentManager.TryGetDocument(request.TextDocument.Uri, out var documentSnapshot))
            {
                _logger.LogWarning($"Failed to find document {request.TextDocument.Uri}.");
                return(null);
            }

            if (!TryGetWordExtent(request, documentSnapshot, out var wordExtent))
            {
                return(null);
            }

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

            if (projectionResult == null)
            {
                if (IsRazorCompilerBugWithCSharpKeywords(request, wordExtent))
                {
                    var csharpPolyfilledCompletionList = new CompletionList()
                    {
                        Items        = Array.Empty <CompletionItem>(),
                        IsIncomplete = true,
                    };
                    csharpPolyfilledCompletionList = IncludeCSharpKeywords(csharpPolyfilledCompletionList);
                    return(csharpPolyfilledCompletionList);
                }

                return(null);
            }

            var projectedPosition    = projectionResult.Position;
            var projectedDocumentUri = projectionResult.Uri;
            var serverKind           = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? LanguageServerKind.CSharp : LanguageServerKind.Html;
            var languageServerName   = projectionResult.LanguageKind == RazorLanguageKind.CSharp ? RazorLSPConstants.RazorCSharpLanguageServerName : RazorLSPConstants.HtmlLanguageServerName;

            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.
                serverKind = LanguageServerKind.CSharp;
                if (documentSnapshot.TryGetVirtualDocument <CSharpVirtualDocumentSnapshot>(out var csharpVirtualDocumentSnapshot))
                {
                    projectedDocumentUri = csharpVirtualDocumentSnapshot.Uri;
                }
                else
                {
                    _logger.LogError("Could not acquire C# virtual document snapshot after provisional completion.");
                }
            }
            else if (!TriggerAppliesToProjection(request.Context, projectionResult.LanguageKind))
            {
                _logger.LogInformation("Trigger does not apply to projection.");
                return(null);
            }
            else
            {
                // This is a valid non-provisional completion request.
                _logger.LogInformation("Searching for non-provisional completions, rewriting context.");

                var completionContext = RewriteContext(request.Context, projectionResult.LanguageKind);

                var completionParams = new CompletionParams()
                {
                    Context      = completionContext,
                    Position     = projectedPosition,
                    TextDocument = new TextDocumentIdentifier()
                    {
                        Uri = projectedDocumentUri
                    }
                };

                _logger.LogInformation($"Requesting non-provisional completions for {projectedDocumentUri}.");

                var textBuffer = serverKind.GetTextBuffer(documentSnapshot);
                var response   = await _requestInvoker.ReinvokeRequestOnServerAsync <CompletionParams, SumType <CompletionItem[], CompletionList>?>(
                    textBuffer,
                    Methods.TextDocumentCompletionName,
                    languageServerName,
                    completionParams,
                    cancellationToken).ConfigureAwait(false);

                if (!ReinvocationResponseHelper.TryExtractResultOrLog(response, _logger, languageServerName, out result))
                {
                    return(null);
                }

                _logger.LogInformation("Found non-provisional completion");
            }

            if (TryConvertToCompletionList(result, out var completionList))
            {
                if (serverKind == LanguageServerKind.CSharp)
                {
                    completionList = PostProcessCSharpCompletionList(request, documentSnapshot, wordExtent, completionList);
                }

                completionList = TranslateTextEdits(request.Position, projectedPosition, wordExtent, completionList);

                var requestContext = new CompletionRequestContext(documentSnapshot.Uri, projectedDocumentUri, serverKind);
                var resultId       = _completionRequestContextCache.Set(requestContext);
                SetResolveData(resultId, completionList);
            }

            if (completionList != null)
            {
                completionList = completionList is VSInternalCompletionList vsCompletionList
                    ? new OptimizedVSCompletionList(vsCompletionList)
                    : new OptimizedVSCompletionList(completionList);
            }

            _logger.LogInformation("Returning completion list.");
            return(completionList);