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