示例#1
0
        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
                    );
            }
        }
示例#2
0
        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()");
        }
示例#4
0
        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"));
                    }
                }
            }
        }
示例#5
0
        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"));
                    }
                }
            }
        }
示例#7
0
        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));
                }
            }
        }
示例#10
0
        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));
                }
            }
        }
示例#11
0
		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);
        }
示例#13
0
        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);
                }
            }
        }
示例#14
0
        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));
        }
示例#15
0
        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"));
                }
            }
        }
示例#19
0
        /// <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]));
            }));
        }
示例#20
0
        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;
                    }
                }
            }
        }
示例#21
0
        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);
 }
示例#23
0
 private static CompletionTrigger GetCompletionTrigger(char?triggerChar)
 => triggerChar != null
         ? CompletionTrigger.CreateInsertionTrigger(triggerChar.Value)
         : CompletionTrigger.Invoke;