private bool TryInvokeSnippetCompletion(TabKeyCommandArgs args) { var subjectBuffer = args.SubjectBuffer; var caretPoint = args.TextView.GetCaretPoint(subjectBuffer).Value.Position; var text = subjectBuffer.AsTextContainer().CurrentText; // If the user types "<line start><spaces><question><tab>" // then the editor takes over and shows the normal *full* snippet picker UI. // i.e. the picker with all the folders and snippet organization. // // However, if the user instead has something like // // "<line start><spaces><identifier><question><tab>" // // Then we take over and we show a completion list with all snippets in it // in a flat list. This enables simple browsing and filtering of all items // based on what the user typed so far. // // If we detect this pattern, then we delete the previous character (the // question mark) and we don't send the tab through to the editor. In // essence, the <quesiton><tab> acts as the trigger, and we act as if that // text never makes it into the buffer. if (!Workspace.TryGetWorkspace(subjectBuffer.AsTextContainer(), out var workspace)) { return(false); } var documentId = workspace.GetDocumentIdInCurrentContext(subjectBuffer.AsTextContainer()); if (documentId == null) { return(false); } var document = workspace.CurrentSolution.GetDocument(documentId); if (document == null) { return(false); } // There's was a buffer-Document mapping. We should be able // to get a CompletionService. var completionService = GetCompletionService(); Contract.ThrowIfNull(completionService, nameof(completionService)); var rules = completionService.GetRules(); if (rules.SnippetsRule != SnippetsRule.IncludeAfterTypingIdentifierQuestionTab) { return(false); } var syntaxFactsOpt = document.GetLanguageService <ISyntaxFactsService>(); if (syntaxFactsOpt == null || caretPoint < 2 || text[caretPoint - 1] != '?' || !CompletionSource.QuestionMarkIsPrecededByIdentifierAndWhitespace(text, caretPoint - 1, syntaxFactsOpt)) { return(false); } // Because <question><tab> is actually a command to bring up snippets, // we delete the last <question> that was typed. var textChange = new TextChange(TextSpan.FromBounds(caretPoint - 1, caretPoint), string.Empty); workspace.ApplyTextChanges(documentId, textChange, CancellationToken.None); this.StartNewModelComputation( completionService, new CompletionTrigger(CompletionTriggerKind.Snippets)); return(true); }