Example #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);
        }
Example #2
0
        public async Task <TextEdit[]> HandleRequestAsync(DocumentOnTypeFormattingParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (!AllTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                // Unexpected trigger character.
                return(null);
            }

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

            var triggerCharacterKind = await GetTriggerCharacterLanguageKindAsync(documentSnapshot, request.Position, request.Character, cancellationToken).ConfigureAwait(false);

            if (triggerCharacterKind == null || triggerCharacterKind != RazorLanguageKind.CSharp)
            {
                return(null);
            }

            if (!IsApplicableTriggerCharacter(request.Character, triggerCharacterKind.Value))
            {
                // We were triggered but the trigger character doesn't make sense for the current cursor position. Bail.
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

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

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

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

            cancellationToken.ThrowIfCancellationRequested();

            var contentType = triggerCharacterKind.Value.ToContainedLanguageContentType();
            var response    = await _requestInvoker.ReinvokeRequestOnServerAsync <DocumentOnTypeFormattingParams, TextEdit[]>(
                Methods.TextDocumentOnTypeFormattingName,
                contentType,
                formattingParams,
                cancellationToken).ConfigureAwait(false);

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

            cancellationToken.ThrowIfCancellationRequested();

            var remappedEdits = await _documentMappingProvider.RemapFormattedTextEditsAsync(projectionResult.Uri, response, request.Options, cancellationToken).ConfigureAwait(false);

            return(remappedEdits);
        }
        public async Task <TextEdit[]> HandleRequestAsync(DocumentOnTypeFormattingParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (!AllTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                // Unexpected trigger character.
                return(null);
            }

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

            var triggerCharacterKind = await GetTriggerCharacterLanguageKindAsync(documentSnapshot, request.Position, request.Character, cancellationToken).ConfigureAwait(false);

            if (triggerCharacterKind == null)
            {
                _logger.LogInformation($"Failed to identify trigger character language context.");
                return(null);
            }
            else if (triggerCharacterKind != RazorLanguageKind.CSharp)
            {
                _logger.LogInformation($"Unsupported trigger character language {triggerCharacterKind:G}.");
                return(null);
            }

            if (!IsApplicableTriggerCharacter(request.Character, triggerCharacterKind.Value))
            {
                // We were triggered but the trigger character doesn't make sense for the current cursor position. Bail.
                _logger.LogInformation("Unsupported trigger character location.");
                return(null);
            }

            cancellationToken.ThrowIfCancellationRequested();

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

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

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

            cancellationToken.ThrowIfCancellationRequested();

            _logger.LogInformation($"Requesting formatting for {projectionResult.Uri}.");

            var contentType = triggerCharacterKind.Value.ToContainedLanguageContentType();
            var textEdits   = await _requestInvoker.ReinvokeRequestOnServerAsync <DocumentOnTypeFormattingParams, TextEdit[]>(
                Methods.TextDocumentOnTypeFormattingName,
                contentType,
                formattingParams,
                cancellationToken).ConfigureAwait(false);

            if (textEdits == null)
            {
                _logger.LogInformation("Received no results.");
                return(null);
            }

            _logger.LogInformation($"Received {textEdits.Length} results, remapping.");

            cancellationToken.ThrowIfCancellationRequested();

            var remappedTextEdits = await _documentMappingProvider.RemapFormattedTextEditsAsync(projectionResult.Uri, textEdits, request.Options, containsSnippet : false, cancellationToken).ConfigureAwait(false);

            _logger.LogInformation($"Returning {remappedTextEdits?.Length} text edits.");
            return(remappedTextEdits);
        }
Example #4
0
        public async Task <DocumentOnAutoInsertResponseItem> HandleRequestAsync(DocumentOnAutoInsertParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (request is null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (!AllAllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                // We haven't built support for this character yet.
                return(null);
            }

            _logger.LogInformation($"Starting request for {request.TextDocument.Uri}, with trigger character {request.Character}.");

            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)
            {
                _logger.LogWarning($"Failed to find document {request.TextDocument.Uri}.");
                return(null);
            }
            else if (projectionResult.LanguageKind == RazorLanguageKind.Razor)
            {
                _logger.LogInformation("OnAutoInsert not supported in Razor context.");
                return(null);
            }
            else if (projectionResult.LanguageKind == RazorLanguageKind.Html &&
                     !HTMLAllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                _logger.LogInformation("Inapplicable HTML trigger char.");
                return(null);
            }
            else if (projectionResult.LanguageKind == RazorLanguageKind.CSharp &&
                     !CSharpAllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                _logger.LogInformation("Inapplicable C# trigger char.");
                return(null);
            }

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

            _logger.LogInformation($"Requesting auto-insert for {projectionResult.Uri}.");

            var contentType = projectionResult.LanguageKind.ToContainedLanguageContentType();
            var response    = await _requestInvoker.ReinvokeRequestOnServerAsync <DocumentOnAutoInsertParams, DocumentOnAutoInsertResponseItem>(
                MSLSPMethods.OnAutoInsertName,
                contentType,
                formattingParams,
                cancellationToken).ConfigureAwait(false);

            if (response == null)
            {
                _logger.LogInformation("Received no results.");
                return(null);
            }

            _logger.LogInformation("Received result, remapping.");

            var containsSnippet = response.TextEditFormat == InsertTextFormat.Snippet;
            var remappedEdits   = await _documentMappingProvider.RemapFormattedTextEditsAsync(
                projectionResult.Uri,
                new[] { response.TextEdit },
                request.Options,
                containsSnippet,
                cancellationToken).ConfigureAwait(false);

            if (!remappedEdits.Any())
            {
                _logger.LogInformation("No edits remain after remapping.");
                return(null);
            }

            var remappedEdit     = remappedEdits.Single();
            var remappedResponse = new DocumentOnAutoInsertResponseItem()
            {
                TextEdit       = remappedEdit,
                TextEditFormat = response.TextEditFormat,
            };

            _logger.LogInformation($"Returning edit.");
            return(remappedResponse);
        }
Example #5
0
        public async Task <DocumentOnAutoInsertResponseItem> HandleRequestAsync(DocumentOnAutoInsertParams request, ClientCapabilities clientCapabilities, CancellationToken cancellationToken)
        {
            if (request is null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (!AllAllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                // We haven't built support for this character yet.
                return(null);
            }

            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 || projectionResult.LanguageKind == RazorLanguageKind.Razor)
            {
                return(null);
            }
            else if (projectionResult.LanguageKind == RazorLanguageKind.Html &&
                     !HTMLAllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                return(null);
            }
            else if (projectionResult.LanguageKind == RazorLanguageKind.CSharp &&
                     !CSharpAllowedTriggerCharacters.Contains(request.Character, StringComparer.Ordinal))
            {
                return(null);
            }

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

            var contentType = projectionResult.LanguageKind.ToContainedLanguageContentType();
            var response    = await _requestInvoker.ReinvokeRequestOnServerAsync <DocumentOnAutoInsertParams, DocumentOnAutoInsertResponseItem>(
                MSLSPMethods.OnAutoInsertName,
                contentType,
                formattingParams,
                cancellationToken).ConfigureAwait(false);

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

            var containsSnippet = response.TextEditFormat == InsertTextFormat.Snippet;

            var edit = response.TextEdit;

            if (containsSnippet && projectionResult.LanguageKind == RazorLanguageKind.CSharp)
            {
                edit = WrapSnippet(edit);
            }

            var remappedEdits = await _documentMappingProvider.RemapFormattedTextEditsAsync(
                projectionResult.Uri,
                new[] { edit },
                request.Options,
                containsSnippet,
                cancellationToken).ConfigureAwait(false);

            if (!remappedEdits.Any())
            {
                return(null);
            }

            var remappedEdit = remappedEdits.Single();

            if (containsSnippet && projectionResult.LanguageKind == RazorLanguageKind.CSharp)
            {
                remappedEdit = UnwrapSnippet(remappedEdit);
            }

            var remappedResponse = new DocumentOnAutoInsertResponseItem()
            {
                TextEdit       = remappedEdit,
                TextEditFormat = response.TextEditFormat,
            };

            return(remappedResponse);
        }