private async Task VerifyExclusiveAsync(string markup, bool exclusive) { using var workspace = TestWorkspace.CreateCSharp( markup, exportProvider: ExportProvider ); var hostDocument = workspace.Documents.Single(); var position = hostDocument.CursorPosition.Value; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var service = GetCompletionService(document.Project); var completionList = await GetCompletionListAsync( service, document, position, triggerInfo ); if (completionList != null) { Assert.True( exclusive == completionList.GetTestAccessor().IsExclusive, "group.IsExclusive == " + completionList.GetTestAccessor().IsExclusive ); } }
public async Task TestEnter() { const string markup = @" class c { public int value {set; get; }} class d { void foo() { c foo = new c { v$$ } }"; using (var workspace = await TestWorkspace.CreateCSharpAsync(markup)) { var hostDocument = workspace.Documents.Single(); var position = hostDocument.CursorPosition.Value; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var completionList = await GetCompletionListAsync(document, position, triggerInfo); var item = completionList.Items.First(); var completionRules = CompletionHelper.GetHelper(document); Assert.False(completionRules.SendEnterThroughToEditor(item, string.Empty, workspace.Options), "Expected false from SendEnterThroughToEditor()"); } }
public async Task TestEnter() { const string markup = @" class C { public int value {set; get; }} class D { void goo() { C goo = new C { v$$ } }"; using var workspace = TestWorkspace.CreateCSharp(markup, exportProvider: ExportProvider); var hostDocument = workspace.Documents.Single(); var position = hostDocument.CursorPosition.Value; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var service = GetCompletionService(document.Project); var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); var item = completionList.Items.First(); Assert.False(CommitManager.SendEnterThroughToEditor(service.GetRules(CompletionOptions.Default), item, string.Empty), "Expected false from SendEnterThroughToEditor()"); }
private async Task CheckResultsAsync(Document document, int position, bool isBuilder) { var triggerInfos = new List <CompletionTrigger>(); triggerInfos.Add(CompletionTrigger.CreateInsertionTrigger('a')); triggerInfos.Add(CompletionTrigger.Invoke); triggerInfos.Add(CompletionTrigger.CreateDeletionTrigger('z')); var service = GetCompletionService(document.Project); var provider = Assert.Single(service.GetTestAccessor().GetAllProviders(ImmutableHashSet <string> .Empty)); foreach (var triggerInfo in triggerInfos) { var completionList = await service.GetTestAccessor().GetContextAsync( provider, document, position, triggerInfo, options: CompletionOptions.Default, cancellationToken: CancellationToken.None); if (isBuilder) { Assert.NotNull(completionList); Assert.True(completionList.SuggestionModeItem != null, "Expecting a suggestion mode, but none was present"); } else { if (completionList != null) { Assert.True(completionList.SuggestionModeItem == null, "group.Builder == " + (completionList.SuggestionModeItem != null ? completionList.SuggestionModeItem.DisplayText : "null")); } } } }
public async Task TestEnter() { const string markup = @" class c { public int value {set; get; }} class d { void goo() { c goo = new c { v$$ } }"; using (var workspace = TestWorkspace.CreateCSharp(markup)) { var hostDocument = workspace.Documents.Single(); var position = hostDocument.CursorPosition.Value; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var service = GetCompletionService(workspace); var completionList = await GetCompletionListAsync(service, document, position, triggerInfo); var item = completionList.Items.First(); Assert.False(Controller.SendEnterThroughToEditor(service.GetRules(), item, string.Empty), "Expected false from SendEnterThroughToEditor()"); } }
private async Task CheckResultsAsync(Document document, int position, bool isBuilder) { var triggerInfos = new List <CompletionTrigger>(); triggerInfos.Add(CompletionTrigger.CreateInsertionTrigger('a')); triggerInfos.Add(CompletionTrigger.Invoke); triggerInfos.Add(CompletionTrigger.CreateDeletionTrigger('z')); var service = GetCompletionService(document.Project.Solution.Workspace); foreach (var triggerInfo in triggerInfos) { var completionList = await service.GetTestAccessor().GetContextAsync( service.GetTestAccessor().ExclusiveProviders?[0], document, position, triggerInfo, options: null, cancellationToken: CancellationToken.None); if (isBuilder) { Assert.NotNull(completionList); Assert.True(completionList.SuggestionModeItem != null, "Expecting a suggestion mode, but none was present"); } else { if (completionList != null) { Assert.True(completionList.SuggestionModeItem == null, "group.Builder == " + (completionList.SuggestionModeItem != null ? completionList.SuggestionModeItem.DisplayText : "null")); } } } }
public void DontTriggerInAttributeConstructor_SecondArgument() { // Due to strange default behavior with suggestion non-static members const string source = @" public class Test { [Some(""0"", ] public static bool DoSmth(Test testInstance) { } } public SomeAttribute : System.Attribute { public SomeAttribute(string v1, string v2) { } }"; bool triggerCompletion = Provider.ShouldTriggerCompletion( text: SourceText.From(source), caretPosition: source.IndexOf(", ") + 2, trigger: CompletionTrigger.CreateInsertionTrigger(' '), options: null); Assert.That(!triggerCompletion); }
private async Task VerifyTextualTriggerCharacterWorkerAsync( string markup, bool expectedTriggerCharacter, bool triggerOnLetter) { using (var workspace = await CreateWorkspaceAsync(markup)) { var document = workspace.Documents.Single(); var position = document.CursorPosition.Value; var text = document.TextBuffer.CurrentSnapshot.AsText(); var options = workspace.Options.WithChangedOption( CompletionOptions.TriggerOnTypingLetters, document.Project.Language, triggerOnLetter); var trigger = CompletionTrigger.CreateInsertionTrigger(text[position]); var service = GetCompletionService(workspace); var isTextualTriggerCharacterResult = service.ShouldTriggerCompletion(text, position + 1, trigger, options: options); if (expectedTriggerCharacter) { var assertText = "'" + text.ToString(new TextSpan(position, 1)) + "' expected to be textual trigger character"; Assert.True(isTextualTriggerCharacterResult, assertText); } else { var assertText = "'" + text.ToString(new TextSpan(position, 1)) + "' expected to NOT be textual trigger character"; Assert.False(isTextualTriggerCharacterResult, assertText); } } }
protected async Task CheckResultsAsync( Document document, int position, string expectedItemOrNull, string expectedDescriptionOrNull, bool usePreviousCharAsTrigger, bool checkForAbsence, int?glyph, int?matchPriority, bool?hasSuggestionModeItem) { var code = (await document.GetTextAsync()).ToString(); var trigger = CompletionTrigger.Invoke; if (usePreviousCharAsTrigger) { trigger = CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1)); } var completionService = GetCompletionService(document.Project.Solution.Workspace); var completionList = await GetCompletionListAsync(completionService, document, position, trigger); var items = completionList == null ? ImmutableArray <CompletionItem> .Empty : completionList.Items; if (hasSuggestionModeItem != null) { Assert.Equal(hasSuggestionModeItem.Value, completionList.SuggestionModeItem != null); } if (checkForAbsence) { if (items == null) { return; } if (expectedItemOrNull == null) { Assert.Empty(items); } else { AssertEx.None( items, c => CompareItems(c.DisplayText, expectedItemOrNull) && (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true)); } } else { if (expectedItemOrNull == null) { Assert.NotEmpty(items); } else { AssertEx.Any(items, c => CompareItems(c.DisplayText, expectedItemOrNull) && (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true) && (glyph.HasValue ? c.Tags.SequenceEqual(GlyphTags.GetTags((Glyph)glyph.Value)) : true) && (matchPriority.HasValue ? (int)c.Rules.MatchPriority == matchPriority.Value : true)); } } }
private async Task CheckResultsAsync( Document document, int position, string expectedItemOrNull, string expectedDescriptionOrNull, bool usePreviousCharAsTrigger, bool checkForAbsence, Glyph?glyph) { var code = (await document.GetTextAsync()).ToString(); CompletionTrigger trigger = CompletionTrigger.Default; if (usePreviousCharAsTrigger) { trigger = CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1)); } var completionService = GetCompletionService(document.Project.Solution.Workspace); var completionList = await GetCompletionListAsync(completionService, document, position, trigger); var items = completionList == null ? default(ImmutableArray <CompletionItem>) : completionList.Items; if (checkForAbsence) { if (items == null) { return; } if (expectedItemOrNull == null) { Assert.Empty(items); } else { AssertEx.None( items, c => CompareItems(c.DisplayText, expectedItemOrNull) && (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true)); } } else { if (expectedItemOrNull == null) { Assert.NotEmpty(items); } else { AssertEx.Any(items, c => CompareItems(c.DisplayText, expectedItemOrNull) && (expectedDescriptionOrNull != null ? completionService.GetDescriptionAsync(document, c).Result.Text == expectedDescriptionOrNull : true) && (glyph.HasValue ? CompletionHelper.TagsEqual(c.Tags, GlyphTags.GetTags(glyph.Value)) : true)); } } }
bool TryStartSession(char c, bool isDelete) { if (HasSession) return false; var info = CompletionInfo.Create(textView.TextSnapshot); if (info == null) return false; int pos = textView.Caret.Position.BufferPosition.Position; var completionTrigger = isDelete ? CompletionTrigger.CreateDeletionTrigger(c) : CompletionTrigger.CreateInsertionTrigger(c); if (!info.Value.CompletionService.ShouldTriggerCompletion(info.Value.SourceText, pos, completionTrigger)) return false; StartSession(info, completionTrigger); return HasSession; }
protected async Task <ImmutableArray <CompletionItem> > GetCompletionItemsAsync( string markup, SourceCodeKind sourceCodeKind, bool usePreviousCharAsTrigger = false) { MarkupTestFile.GetPosition(markup.NormalizeLineEndings(), out var code, out int position); var document = WorkspaceFixture.UpdateDocument(code, sourceCodeKind); var trigger = usePreviousCharAsTrigger ? CompletionTrigger.CreateInsertionTrigger(insertedCharacter: code.ElementAt(position - 1)) : CompletionTrigger.Invoke; var completionService = GetCompletionService(document.Project.Solution.Workspace); var completionList = await GetCompletionListAsync(completionService, document, position, trigger); return(completionList == null ? ImmutableArray <CompletionItem> .Empty : completionList.Items); }
private async Task VerifyExclusiveAsync(string markup, bool exclusive) { using (var workspace = await TestWorkspace.CreateCSharpAsync(markup)) { var hostDocument = workspace.Documents.Single(); var position = hostDocument.CursorPosition.Value; var document = workspace.CurrentSolution.GetDocument(hostDocument.Id); var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var completionList = await GetCompletionListContextAsync(document, position, triggerInfo); if (completionList != null) { Assert.True(exclusive == completionList.IsExclusive, "group.IsExclusive == " + completionList.IsExclusive); } } }
private bool IsTextualTriggerCharacter(CompletionService completionService, char ch, OptionSet options) { AssertIsForeground(); // Note: When this function is called we've already guaranteed that // TypeCharWasHandledStrangely returned false. That means we know that the caret is in // our buffer, and is after the character just typed. var caretPosition = this.TextView.GetCaretPoint(this.SubjectBuffer).Value; var previousPosition = caretPosition - 1; Contract.ThrowIfFalse(this.SubjectBuffer.CurrentSnapshot[previousPosition] == ch); var trigger = CompletionTrigger.CreateInsertionTrigger(ch); return(completionService.ShouldTriggerCompletion(previousPosition.Snapshot.AsText(), caretPosition, trigger, _roles, options)); }
public void TriggerCompletionNewKeyword() { const string source = @" public class Test { public static bool DoSmth(Test testInstance) { Test v1 = new } }"; bool triggerCompletion = Provider.ShouldTriggerCompletion( text: SourceText.From(source), caretPosition: source.IndexOf("new ") + 4, trigger: CompletionTrigger.CreateInsertionTrigger(' '), options: null); Assert.That(triggerCompletion); }
public Task ApplyTypedCharAsync(char @char, WorkSession session, ICommandResultSender sender, CancellationToken cancellationToken) { var current = session.CurrentCompletion; if (current.List != null) { return(Task.CompletedTask); } if (current.ChangeEchoPending) { current.PendingChar = @char; return(Task.CompletedTask); } var trigger = CompletionTrigger.CreateInsertionTrigger(@char); return(CheckCompletionAsync(trigger, session, sender, cancellationToken)); }
private async Task CheckResultsAsync(Document document, int position, bool isBuilder) { var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var completionList = await GetCompletionListAsync(document, position, triggerInfo); if (isBuilder) { Assert.NotNull(completionList); Assert.NotNull(completionList.SuggestionModeItem); } else { if (completionList != null) { Assert.True(completionList.SuggestionModeItem == null, "group.Builder == " + (completionList.SuggestionModeItem != null ? completionList.SuggestionModeItem.DisplayText : "null")); } } }
private async Task CheckResultsAsync(Document document, int position, bool isBuilder) { var triggerInfo = CompletionTrigger.CreateInsertionTrigger('a'); var service = GetCompletionService(document.Project.Solution.Workspace); var completionList = await service.GetContextAsync( service.ExclusiveProviders?[0], document, position, triggerInfo, options : null, cancellationToken : CancellationToken.None); if (isBuilder) { Assert.NotNull(completionList); Assert.NotNull(completionList.SuggestionModeItem); } else { if (completionList != null) { Assert.True(completionList.SuggestionModeItem == null, "group.Builder == " + (completionList.SuggestionModeItem != null ? completionList.SuggestionModeItem.DisplayText : "null")); } } }
/// <summary> /// Método utilizado para determinar se o caractere /// digitado pelo usuário pode ser utilizado para /// abrir a janela de sugestões do editor /// </summary> /// <param name="position">Posição atual do cursor</param> /// <returns>True se o caractere digitado é um gatilho, False do contrário</returns> public async Task <bool> ShouldTriggerCompletion(int position) { return(await Task.Run(() => { if (position < 1) { return false; } var completionService = CompletionService.GetService(document); return completionService.ShouldTriggerCompletion(sourceText, position, CompletionTrigger.CreateInsertionTrigger(documentCode[position - 1])); })); }
void ICommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, Action nextHandler) { AssertIsForeground(); // When a character is typed it is *always* sent through to the editor. This way the // editor always represents what would have been typed had completion not been involved // at this point. That means that if we decide to commit, then undo'ing the commit will // return you to the code that you would have typed if completion was not up. // // The steps we follow for commit are as follows: // // 1) send the commit character through to the buffer. // 2) open a transaction. // 2a) roll back the text to before the text was sent through // 2b) commit the item. // 2c) send the commit character through again.* // 2d) commit the transaction. // // 2c is very important. it makes sure that post our commit all our normal features // run depending on what got typed. For example if the commit character was ( // then brace completion may run. If it was ; then formatting may run. But, importantly // this code doesn't need to know anything about that. Furthermore, because that code // runs within this transaction, then the user can always undo and get to what the code // would have been if completion was not involved. // // 2c*: note sending the commit character through to the buffer again can be controlled // by the completion item. For example, completion items that want to totally handle // what gets output into the buffer can ask for this not to happen. An example of this // is override completion. If the user types "override Method(" then we'll want to // spit out the entire method and *not* also spit out "(" again. // In order to support 2a (rolling back), we capture hte state of the buffer before // we send the character through. We then just apply the edits in reverse order to // roll us back. var initialTextSnapshot = this.SubjectBuffer.CurrentSnapshot; var initialCaretPosition = GetCaretPointInViewBuffer(); // Note: while we're doing this, we don't want to hear about buffer changes (since we // know they're going to happen). So we disconnect and reconnect to the event // afterwards. That way we can hear about changes to the buffer that don't happen // through us. // Automatic Brace Completion may also move the caret, so unsubscribe from that too this.TextView.TextBuffer.PostChanged -= OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged -= OnCaretPositionChanged; // In Venus/Razor, the user might be typing on the buffer's seam. This means that, // depending on the character typed, the character may not go into our buffer. var isOnSeam = IsOnSeam(); try { nextHandler(); } finally { this.TextView.TextBuffer.PostChanged += OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged += OnCaretPositionChanged; } // We only want to process typechar if it is a normal typechar and no one else is // involved. i.e. if there was a typechar, but someone processed it and moved the caret // somewhere else then we don't want completion. Also, if a character was typed but // something intercepted and placed different text into the editor, then we don't want // to proceed. if (this.TextView.TypeCharWasHandledStrangely(this.SubjectBuffer, args.TypedChar)) { if (sessionOpt != null) { // If we're on a seam (razor) with a computation, and the user types a character // that goes into the other side of the seam, the character may be a commit character. // If it's a commit character, just commit without trying to check caret position, // since the caret is no longer in our buffer. if (isOnSeam && this.IsCommitCharacter(args.TypedChar)) { this.CommitOnTypeChar(args.TypedChar, initialTextSnapshot, nextHandler); return; } else if (_autoBraceCompletionChars.Contains(args.TypedChar) && this.SubjectBuffer.GetOption(InternalFeatureOnOffOptions.AutomaticPairCompletion) && this.IsCommitCharacter(args.TypedChar)) { // I don't think there is any better way than this. if typed char is one of auto brace completion char, // we don't do multiple buffer change check this.CommitOnTypeChar(args.TypedChar, initialTextSnapshot, nextHandler); return; } else { // If we were computing anything, we stop. We only want to process a typechar // if it was a normal character. this.StopModelComputation(); } } return; } var completionService = this.GetCompletionService(); if (completionService == null) { return; } var options = GetOptions(); Contract.ThrowIfNull(options); var isTextuallyTriggered = IsTextualTriggerCharacter(completionService, args.TypedChar, options); var isPotentialFilterCharacter = IsPotentialFilterCharacter(args); var trigger = CompletionTrigger.CreateInsertionTrigger(args.TypedChar); if (sessionOpt == null) { // No computation at all. If this is not a trigger character, we just ignore it and // stay in this state. Otherwise, if it's a trigger character, start up a new // computation and start computing the model in the background. if (isTextuallyTriggered) { // First create the session that represents that we now have a potential // completion list. Then tell it to start computing. StartNewModelComputation(completionService, trigger, filterItems: true, dismissIfEmptyAllowed: true); return; } else { // No need to do anything. Just stay in the state where we have no session. return; } } else { sessionOpt.UpdateModelTrackingSpan(initialCaretPosition); // If the session is up, it may be in one of many states. It may know nothing // (because it is currently computing the list of completions). Or it may have a // list of completions that it has filtered. // If the user types something which is absolutely known to be a filter character // then we can just proceed without blocking. if (isPotentialFilterCharacter) { if (isTextuallyTriggered) { // The character typed was something like "a". It can both filter a list if // we have computed one, or it can trigger a new list. Ask the computation // to compute again. If nothing has been computed, then it will try to // compute again, otherwise it will just ignore this request. sessionOpt.ComputeModel(completionService, trigger, _roles, options); } // Now filter whatever result we have. sessionOpt.FilterModel( CompletionFilterReason.TypeChar, recheckCaretPosition: false, dismissIfEmptyAllowed: true, filterState: null); } else { // It wasn't a trigger or filter character. At this point, we make our // determination on what to do based on what has actually been computed and // what's being typed. This means waiting on the session and will effectively // block the user. // Again, from this point on we must block on the computation to decide what to // do. // What they type may end up filtering, committing, or else will dismiss. // // For example, we may filter in cases like this: "Color." // // "Color" will have already filtered the list down to some things like // "Color", "Color.Red", "Color.Blue", etc. When we process the 'dot', we // actually want to filter some more. But we can't know that ahead of time until // we have computed the list of completions. if (this.IsFilterCharacter(args.TypedChar)) { // Known to be a filter character for the currently selected item. So just // filter the session. sessionOpt.FilterModel(CompletionFilterReason.TypeChar, recheckCaretPosition: false, dismissIfEmptyAllowed: true, filterState: null); return; } // It wasn't a filter character. We'll either commit what's selected, or we'll // dismiss the completion list. First, ensure that what was typed is in the // buffer. // Now, commit if it was a commit character. if (this.IsCommitCharacter(args.TypedChar)) { // Known to be a commit character for the currently selected item. So just // commit the session. this.CommitOnTypeChar(args.TypedChar, initialTextSnapshot, nextHandler); } else { // Now dismiss the session. this.StopModelComputation(); } // The character may commit/dismiss and then trigger completion again. So check // for that here. if (isTextuallyTriggered) { // First create the session that represents that we now have a potential // completion list. StartNewModelComputation( completionService, trigger, filterItems: true, dismissIfEmptyAllowed: true); return; } } } }
void ICommandHandler <TypeCharCommandArgs> .ExecuteCommand(TypeCharCommandArgs args, Action nextHandler) { Trace.WriteLine("Entered completion command handler for typechar."); AssertIsForeground(); var initialCaretPosition = GetCaretPointInViewBuffer(); // When a character is typed it is *always* sent through to the editor. This way the // editor always represents what would have been typed had completion not been involved // at this point. After we send the character into the buffer we then decide what to do // with the completion set. If we decide to commit it then we will replace the // appropriate span (which will include the character just sent to the buffer) with the // appropriate insertion text *and* the character typed. This way, after we commit, the // editor has the insertion text of the selected item, and the character typed. It // also means that if we then undo that we'll see the text that would have been typed // had no completion been active. // Note: while we're doing this, we don't want to hear about buffer changes (since we // know they're going to happen). So we disconnect and reconnect to the event // afterwards. That way we can hear about changes to the buffer that don't happen // through us. // Automatic Brace Completion may also move the caret, so unsubscribe from that too this.TextView.TextBuffer.PostChanged -= OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged -= OnCaretPositionChanged; // In Venus/Razor, the user might be typing on the buffer's seam. This means that, // depending on the character typed, the character may not go into our buffer. var isOnSeam = IsOnSeam(); try { nextHandler(); } finally { this.TextView.TextBuffer.PostChanged += OnTextViewBufferPostChanged; this.TextView.Caret.PositionChanged += OnCaretPositionChanged; } // We only want to process typechar if it is a normal typechar and no one else is // involved. i.e. if there was a typechar, but someone processed it and moved the caret // somewhere else then we don't want completion. Also, if a character was typed but // something intercepted and placed different text into the editor, then we don't want // to proceed. if (this.TextView.TypeCharWasHandledStrangely(this.SubjectBuffer, args.TypedChar)) { Trace.WriteLine("typechar was handled by someone else, cannot have a completion session."); if (sessionOpt != null) { // If we're on a seam (razor) with a computation, and the user types a character // that goes into the other side of the seam, the character may be a commit character. // If it's a commit character, just commit without trying to check caret position, // since the caret is no longer in our buffer. if (isOnSeam && this.IsCommitCharacter(args.TypedChar)) { Trace.WriteLine("typechar was on seam and a commit char, cannot have a completion session."); this.CommitOnTypeChar(args.TypedChar); return; } else if (_autoBraceCompletionChars.Contains(args.TypedChar) && this.SubjectBuffer.GetOption(InternalFeatureOnOffOptions.AutomaticPairCompletion) && this.IsCommitCharacter(args.TypedChar)) { Trace.WriteLine("typechar was brace completion char and a commit char, cannot have a completion session."); // I don't think there is any better way than this. if typed char is one of auto brace completion char, // we don't do multiple buffer change check this.CommitOnTypeChar(args.TypedChar); return; } else { Trace.WriteLine("we stop model computation, cannot have a completion session."); // If we were computing anything, we stop. We only want to process a typechar // if it was a normal character. this.StopModelComputation(); } } return; } var completionService = this.GetCompletionService(); if (completionService == null) { Trace.WriteLine("handling typechar, completion service is null, cannot have a completion session."); return; } var options = GetOptions(); Contract.ThrowIfNull(options); var isTextuallyTriggered = IsTextualTriggerCharacter(completionService, args.TypedChar, options); var isPotentialFilterCharacter = IsPotentialFilterCharacter(args); var trigger = CompletionTrigger.CreateInsertionTrigger(args.TypedChar); if (sessionOpt == null) { // No computation at all. If this is not a trigger character, we just ignore it and // stay in this state. Otherwise, if it's a trigger character, start up a new // computation and start computing the model in the background. if (isTextuallyTriggered) { Trace.WriteLine("no completion session yet and this is a trigger char, starting model computation."); // First create the session that represents that we now have a potential // completion list. Then tell it to start computing. StartNewModelComputation(completionService, trigger, filterItems: true); return; } else { Trace.WriteLine("no completion session yet and this is NOT a trigger char, we won't have completion."); // No need to do anything. Just stay in the state where we have no session. return; } } else { Trace.WriteLine("we have a completion session."); sessionOpt.UpdateModelTrackingSpan(initialCaretPosition); // If the session is up, it may be in one of many states. It may know nothing // (because it is currently computing the list of completions). Or it may have a // list of completions that it has filtered. // If the user types something which is absolutely known to be a filter character // then we can just proceed without blocking. if (isPotentialFilterCharacter) { if (isTextuallyTriggered) { Trace.WriteLine("computing completion again and filtering..."); // The character typed was something like "a". It can both filter a list if // we have computed one, or it can trigger a new list. Ask the computation // to compute again. If nothing has been computed, then it will try to // compute again, otherwise it will just ignore this request. sessionOpt.ComputeModel(completionService, trigger, _roles, options); } // Now filter whatever result we have. sessionOpt.FilterModel(CompletionFilterReason.TypeChar); } else { // It wasn't a trigger or filter character. At this point, we make our // determination on what to do based on what has actually been computed and // what's being typed. This means waiting on the session and will effectively // block the user. // Again, from this point on we must block on the computation to decide what to // do. // What they type may end up filtering, committing, or else will dismiss. // // For example, we may filter in cases like this: "Color." // // "Color" will have already filtered the list down to some things like // "Color", "Color.Red", "Color.Blue", etc. When we process the 'dot', we // actually want to filter some more. But we can't know that ahead of time until // we have computed the list of completions. if (this.IsFilterCharacter(args.TypedChar)) { Trace.WriteLine("filtering the session..."); // Known to be a filter character for the currently selected item. So just // filter the session. sessionOpt.FilterModel(CompletionFilterReason.TypeChar); return; } // It wasn't a filter character. We'll either commit what's selected, or we'll // dismiss the completion list. First, ensure that what was typed is in the // buffer. // Now, commit if it was a commit character. if (this.IsCommitCharacter(args.TypedChar)) { Trace.WriteLine("committing the session..."); // Known to be a commit character for the currently selected item. So just // commit the session. this.CommitOnTypeChar(args.TypedChar); } else { Trace.WriteLine("dismissing the session..."); // Now dismiss the session. this.StopModelComputation(); } // The character may commit/dismiss and then trigger completion again. So check // for that here. if (isTextuallyTriggered) { Trace.WriteLine("the char commit/dismiss -ed a session and is trigerring completion again. starting model computation."); // First create the session that represents that we now have a potential // completion list. StartNewModelComputation(completionService, trigger, filterItems: true); return; } } } }
private static CompletionTrigger GetCompletionTrigger(char?triggerChar) { return(triggerChar != null ? CompletionTrigger.CreateInsertionTrigger(triggerChar.Value) : CompletionTrigger.Default); }
private static CompletionTrigger GetCompletionTrigger(char?triggerChar) => triggerChar != null ? CompletionTrigger.CreateInsertionTrigger(triggerChar.Value) : CompletionTrigger.Invoke;