/// <summary> /// Attempts to convert VS Completion trigger into Roslyn completion trigger /// </summary> /// <param name="trigger">VS completion trigger</param> /// <param name="triggerLocation">Character. /// VS provides Backspace and Delete characters inside the trigger while Roslyn needs the char deleted by the trigger. /// Therefore, we provide this character separately and use it for Delete and Backspace cases only. /// We retrieve this character from triggerLocation. /// </param> /// <returns>Roslyn completion trigger</returns> internal static RoslynTrigger GetRoslynTrigger(EditorAsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation) { var completionTriggerKind = GetRoslynTriggerKind(trigger); if (completionTriggerKind == CompletionTriggerKind.Deletion) { var snapshotBeforeEdit = trigger.ViewSnapshotBeforeTrigger; char characterRemoved; if (triggerLocation.Position >= 0 && triggerLocation.Position < snapshotBeforeEdit.Length) { // If multiple characters were removed (selection), this finds the first character from the left. characterRemoved = snapshotBeforeEdit[triggerLocation.Position]; } else { characterRemoved = (char)0; } return(RoslynTrigger.CreateDeletionTrigger(characterRemoved)); } else { return(new RoslynTrigger(completionTriggerKind, trigger.Character)); } }
/// <summary> /// Attempts to convert VS Completion trigger into Roslyn completion trigger /// </summary> /// <param name="trigger">VS completion trigger</param> /// <param name="triggerLocation">Character. /// VS provides Backspace and Delete characters inside the trigger while Roslyn needs the char deleted by the trigger. /// Therefore, we provide this character separately and use it for Delete and Backspace cases only. /// We retrieve this character from triggerLocation. /// </param> /// <returns>Roslyn completion trigger</returns> internal static RoslynTrigger GetRoslynTrigger(AsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation) { switch (trigger.Reason) { case AsyncCompletionData.CompletionTriggerReason.InvokeAndCommitIfUnique: return(new RoslynTrigger(CompletionTriggerKind.InvokeAndCommitIfUnique)); case AsyncCompletionData.CompletionTriggerReason.Insertion: return(RoslynTrigger.CreateInsertionTrigger(trigger.Character)); case AsyncCompletionData.CompletionTriggerReason.Deletion: case AsyncCompletionData.CompletionTriggerReason.Backspace: var snapshotBeforeEdit = trigger.ViewSnapshotBeforeTrigger; char characterRemoved; if (triggerLocation.Position >= 0 && triggerLocation.Position < snapshotBeforeEdit.Length) { // If multiple characters were removed (selection), this finds the first character from the left. characterRemoved = snapshotBeforeEdit[triggerLocation.Position]; } else { characterRemoved = (char)0; } return(RoslynTrigger.CreateDeletionTrigger(characterRemoved)); case AsyncCompletionData.CompletionTriggerReason.SnippetsMode: return(new RoslynTrigger(CompletionTriggerKind.Snippets)); default: return(RoslynTrigger.Invoke); } }
private bool ShouldTriggerCompletion( AsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation, SourceText sourceText, Document document, CompletionService completionService) { // The trigger reason guarantees that user wants a completion. if (trigger.Reason == AsyncCompletionData.CompletionTriggerReason.Invoke || trigger.Reason == AsyncCompletionData.CompletionTriggerReason.InvokeAndCommitIfUnique) { return(true); } //The user may be trying to invoke snippets through question-tab. // We may provide a completion after that. // Otherwise, tab should not be a completion trigger. if (trigger.Reason == AsyncCompletionData.CompletionTriggerReason.Insertion && trigger.Character == '\t') { return(TryInvokeSnippetCompletion(completionService, document, sourceText, triggerLocation.Position)); } var roslynTrigger = Helpers.GetRoslynTrigger(trigger, triggerLocation); // The completion service decides that user may want a completion. if (completionService.ShouldTriggerCompletion(sourceText, triggerLocation.Position, roslynTrigger)) { return(true); } return(false); }
public AsyncCompletionData.CompletionStartData InitializeCompletion( AsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken cancellationToken) { // We take sourceText from document to get a snapshot span. // We would like to be sure that nobody changes buffers at the same time. AssertIsForeground(); if (_textView.Selection.Mode == TextSelectionMode.Box) { // No completion with multiple selection return(AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); } var document = triggerLocation.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return(AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); } var service = document.GetLanguageService <CompletionService>(); if (service == null) { return(AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); } // The Editor supports the option per textView. // There could be mixed desired behavior per textView and even per same completion session. // The right fix would be to send this information as a result of the method. // Then, the Editor would choose the right behavior for mixed cases. _textView.Options.GlobalOptions.SetOptionValue(NonBlockingCompletionEditorOption, !document.Project.Solution.Workspace.Options.GetOption(CompletionOptions.BlockForCompletionItems2, service.Language)); // In case of calls with multiple completion services for the same view (e.g. TypeScript and C#), those completion services must not be called simultaneously for the same session. // Therefore, in each completion session we use a list of commit character for a specific completion service and a specific content type. _textView.Properties[PotentialCommitCharacters] = service.GetRules().DefaultCommitCharacters; // Reset a flag which means a snippet triggered by ? + Tab. // Set it later if met the condition. _snippetCompletionTriggeredIndirectly = false; CheckForExperimentStatus(_textView, document); var sourceText = document.GetTextSynchronously(cancellationToken); return(ShouldTriggerCompletion(trigger, triggerLocation, sourceText, document, service) ? new AsyncCompletionData.CompletionStartData( participation: AsyncCompletionData.CompletionParticipation.ProvidesItems, applicableToSpan: new SnapshotSpan( triggerLocation.Snapshot, service.GetDefaultCompletionListSpan(sourceText, triggerLocation.Position).ToSpan())) : AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion);
public AsyncCompletionData.CompletionStartData InitializeCompletion( AsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken cancellationToken) { // We take sourceText from document to get a snapshot span. // We would like to be sure that nobody changes buffers at the same time. AssertIsForeground(); if (_textView.Selection.Mode == TextSelectionMode.Box) { // No completion with multiple selection return(AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); } var document = triggerLocation.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return(AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); } var service = document.GetLanguageService <CompletionService>(); if (service == null) { return(AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); } if (!document.Project.Solution.Workspace.Options.GetOption(CompletionOptions.BlockForCompletionItems, service.Language)) { _textView.Options.GlobalOptions.SetOptionValue(NonBlockingCompletionEditorOption, true); } // In case of calls with multiple completion services for the same view (e.g. TypeScript and C#), those completion services must not be called simultaneously for the same session. // Therefore, in each completion session we use a list of commit character for a specific completion service and a specific content type. _textView.Properties[PotentialCommitCharacters] = service.GetRules().DefaultCommitCharacters; // For telemetry reporting during the completion session var experimentationService = document.Project.Solution.Workspace.Services.GetService <IExperimentationService>(); _textView.Properties[TargetTypeFilterExperimentEnabled] = experimentationService.IsExperimentEnabled(WellKnownExperimentNames.TargetTypedCompletionFilter); var sourceText = document.GetTextSynchronously(cancellationToken); return(ShouldTriggerCompletion(trigger, triggerLocation, sourceText, document, service) ? new AsyncCompletionData.CompletionStartData( participation: AsyncCompletionData.CompletionParticipation.ProvidesItems, applicableToSpan: new SnapshotSpan( triggerLocation.Snapshot, service.GetDefaultCompletionListSpan(sourceText, triggerLocation.Position).ToSpan())) : AsyncCompletionData.CompletionStartData.DoesNotParticipateInCompletion); }
internal static CompletionFilterReason GetFilterReason(AsyncCompletionData.CompletionTrigger trigger) { switch (trigger.Reason) { case AsyncCompletionData.CompletionTriggerReason.Insertion: return(CompletionFilterReason.Insertion); case AsyncCompletionData.CompletionTriggerReason.Deletion: case AsyncCompletionData.CompletionTriggerReason.Backspace: return(CompletionFilterReason.Deletion); default: return(CompletionFilterReason.Other); } }
internal static CompletionTriggerKind GetRoslynTriggerKind(EditorAsyncCompletionData.CompletionTrigger trigger) { switch (trigger.Reason) { case EditorAsyncCompletionData.CompletionTriggerReason.InvokeAndCommitIfUnique: return(CompletionTriggerKind.InvokeAndCommitIfUnique); case EditorAsyncCompletionData.CompletionTriggerReason.Insertion: return(CompletionTriggerKind.Insertion); case EditorAsyncCompletionData.CompletionTriggerReason.Deletion: case EditorAsyncCompletionData.CompletionTriggerReason.Backspace: return(CompletionTriggerKind.Deletion); case EditorAsyncCompletionData.CompletionTriggerReason.SnippetsMode: return(CompletionTriggerKind.Snippets); default: return(CompletionTriggerKind.Invoke); } }
public async Task <AsyncCompletionData.CompletionContext> GetCompletionContextAsync( IAsyncCompletionSession session, AsyncCompletionData.CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken cancellationToken) { var document = triggerLocation.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); if (document == null) { return(new AsyncCompletionData.CompletionContext(ImmutableArray <VSCompletionItem> .Empty)); } var completionService = document.GetLanguageService <CompletionService>(); var roslynTrigger = Helpers.GetRoslynTrigger(trigger, triggerLocation); var workspace = document.Project.Solution.Workspace; var completionList = await completionService.GetCompletionsAsync( document, triggerLocation, roslynTrigger, _roles, _isDebuggerTextView?workspace.Options.WithDebuggerCompletionOptions() : workspace.Options, cancellationToken).ConfigureAwait(false); if (completionList == null) { return(new AsyncCompletionData.CompletionContext(ImmutableArray <VSCompletionItem> .Empty)); } var itemsBuilder = new ArrayBuilder <VSCompletionItem>(completionList.Items.Length); foreach (var roslynItem in completionList.Items) { cancellationToken.ThrowIfCancellationRequested(); var item = Convert(document, roslynItem); itemsBuilder.Add(item); } var items = itemsBuilder.ToImmutableAndFree(); var suggestionItemOptions = completionList.SuggestionModeItem != null ? new AsyncCompletionData.SuggestionItemOptions( completionList.SuggestionModeItem.DisplayText, completionList.SuggestionModeItem.Properties.TryGetValue(Description, out var description) ? description : string.Empty) : null; // Have to store the snapshot to reuse it in some projections related scenarios // where data and session in further calls are able to provide other snapshots. session.Properties.AddProperty(TriggerSnapshot, triggerLocation.Snapshot); // Store around the span this completion list applies to. We'll use this later // to pass this value in when we're committing a completion list item. session.Properties.AddProperty(CompletionListSpan, completionList.Span); // This is a code supporting original completion scenarios: // Controller.Session_ComputeModel: if completionList.SuggestionModeItem != null, then suggestionMode = true // If there are suggestionItemOptions, then later HandleNormalFiltering should set selection to SoftSelection. session.Properties.AddProperty(HasSuggestionItemOptions, suggestionItemOptions != null); session.Properties.AddProperty(InitialTriggerKind, roslynTrigger.Kind); var excludedCommitCharacters = GetExcludedCommitCharacters(completionList.Items); if (excludedCommitCharacters.Length > 0) { session.Properties.AddProperty(ExcludedCommitCharacters, excludedCommitCharacters); } return(new AsyncCompletionData.CompletionContext( items, suggestionItemOptions, suggestionItemOptions == null ? AsyncCompletionData.InitialSelectionHint.RegularSelection : AsyncCompletionData.InitialSelectionHint.SoftSelection)); }