Exemplo n.º 1
0
        //=====================================================================

        /// <inheritdoc />
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item,
                                                                     char?commitChar, CancellationToken cancellationToken)
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            if (!item.Properties.TryGetValue(nameof(CommentsElement.TextBeforeCaret), out string replacementText))
            {
                replacementText = item.DisplayText;
            }

            var itemSpan        = item.Span;
            var replacementSpan = TextSpan.FromBounds(text[itemSpan.Start - 1] == '<' && replacementText.Length > 0 &&
                                                      replacementText[0] == '<' ? itemSpan.Start - 1 : itemSpan.Start, itemSpan.End);

            int newPosition = replacementSpan.Start + replacementText.Length;

            // Include the commit character?
            if (commitChar != null && !Char.IsWhiteSpace(commitChar.Value) &&
                commitChar.Value != replacementText[replacementText.Length - 1])
            {
                replacementText += commitChar.Value;
                newPosition++;
            }

            if (item.Properties.TryGetValue(nameof(CommentsElement.TextAfterCaret), out string afterCaretText))
            {
                replacementText += afterCaretText;
            }

            return(CompletionChange.Create(new TextChange(replacementSpan, replacementText), newPosition, true));
        }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitChar = default(char?), CancellationToken cancellationToken = default(CancellationToken))
        {
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var beforeCaretText = XmlDocCommentCompletionItem.GetBeforeCaretText(item);
            var afterCaretText  = XmlDocCommentCompletionItem.GetAfterCaretText(item);

            var itemSpan        = item.Span;
            var replacementSpan = TextSpan.FromBounds(text[itemSpan.Start - 1] == '<' && beforeCaretText[0] == '<' ? itemSpan.Start - 1 : itemSpan.Start, itemSpan.End);

            var replacementText = beforeCaretText;
            var newPosition     = replacementSpan.Start + beforeCaretText.Length;

            if (commitChar.HasValue && !char.IsWhiteSpace(commitChar.Value) && commitChar.Value != replacementText[replacementText.Length - 1])
            {
                // include the commit character
                replacementText += commitChar.Value;

                // The caret goes after whatever commit character we spit.
                newPosition++;
            }

            replacementText += afterCaretText;

            return(CompletionChange.Create(ImmutableArray.Create(new TextChange(replacementSpan, replacementText)), newPosition, includesCommitCharacter: true));
        }
Exemplo n.º 3
0
            public void AddIfMissing(
                string displayText,
                string suffix,
                string description,
                RegexNode parentOpt,
                int?positionOffset   = null,
                string insertionText = null
                )
            {
                var replacementStart = parentOpt != null?parentOpt.GetSpan().Start : Position;

                var replacementSpan = TextSpan.FromBounds(replacementStart, Position);
                var newPosition     = replacementStart + positionOffset;

                insertionText ??= displayText;
                var escapedInsertionText = _language.EscapeText(insertionText, StringToken);

                if (escapedInsertionText != insertionText)
                {
                    newPosition += escapedInsertionText.Length - insertionText.Length;
                }

                AddIfMissing(
                    new RegexItem(
                        displayText,
                        suffix,
                        description,
                        CompletionChange.Create(
                            new TextChange(replacementSpan, escapedInsertionText),
                            newPosition
                            )
                        )
                    );
            }
        private static async Task <CompletionChange> GetConversionChangeAsync(
            Document document, CompletionItem item, CancellationToken cancellationToken)
        {
            var position = SymbolCompletionItem.GetContextPosition(item);
            var text     = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var(dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken);

            var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken
                ? dotToken.GetPreviousToken()
                : (SyntaxToken?)null;

            var expression = (ExpressionSyntax)dotToken.GetRequiredParent();

            expression = expression.GetRootConditionalAccessExpression() ?? expression;

            var replacement = questionToken != null
                ? $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, questionToken.Value.FullSpan.Start))}){questionToken.Value}"
                : $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, dotToken.SpanStart))})";

            // If we're at `x.$$.y` then we only want to replace up through the first dot.
            var tokenOnLeft    = root.FindTokenOnLeftOfPosition(position, includeSkipped: true);
            var fullTextChange = new TextChange(
                TextSpan.FromBounds(
                    expression.SpanStart,
                    tokenOnLeft.Kind() == SyntaxKind.DotDotToken ? tokenOnLeft.SpanStart + 1 : tokenOnLeft.Span.End),
                replacement);

            var newPosition = expression.SpanStart + replacement.Length;

            return(CompletionChange.Create(fullTextChange, newPosition));
        }
        public sealed override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey = null, CancellationToken cancellationToken = default)
        {
            // IsComplexTextEdit is true when we want to add async to the container.
            if (!item.IsComplexTextEdit)
            {
                return(await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false));
            }

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var declaration = GetAsyncSupportingDeclaration(root.FindToken(item.Span.Start));

            if (declaration is null)
            {
                // IsComplexTextEdit should only be true when GetAsyncSupportingDeclaration returns non-null.
                // This is ensured by the ShouldMakeContainerAsync overrides.
                Debug.Assert(false, "Expected non-null value for declaration.");
                return(await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false));
            }

            var syntaxFacts = document.GetRequiredLanguageService <ISyntaxFactsService>();
            var syntaxKinds = document.GetRequiredLanguageService <ISyntaxKindsService>();

            using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder);

            builder.Add(new TextChange(new TextSpan(GetSpanStart(declaration), 0), syntaxFacts.GetText(syntaxKinds.AsyncKeyword) + " "));
            builder.Add(new TextChange(item.Span, item.DisplayText));

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var newText = text.WithChanges(builder);

            return(CompletionChange.Create(Utilities.Collapse(newText, builder.ToImmutableArray())));
        }
Exemplo n.º 6
0
        private void ProvideEscapeCategoryCompletions(EmbeddedCompletionContext context)
        {
            foreach (var(name, (shortDesc, longDesc)) in RegexCharClass.EscapeCategories)
            {
                var displayText = name;

                // There are some internal escape categories the regex engine has (like _xmlI).
                // Just filter out here so we only show the main documented regex categories.
                // Note: we still include those in RegexCharClass.EscapeCategories because we
                // don't want to report an error on code that does use these since the .net
                // regex engine will allow them.
                if (displayText.StartsWith("_"))
                {
                    continue;
                }

                var description = longDesc.Length > 0
                    ? longDesc
                    : string.Format(Regex_unicode_general_category_0, name);

                context.AddIfMissing(new RegexItem(
                                         displayText, shortDesc, description,
                                         change: CompletionChange.Create(
                                             new TextChange(new TextSpan(context.Position, 0), name), newPosition: null)));
            }
        }
        public override Task <CompletionChange> GetChangeAsync(
            Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            int?spanStart  = null;
            int?spanLength = null;

            if (item.Properties.TryGetValue("GodotSpan.Start", out string spanStartStr))
            {
                if (int.TryParse(spanStartStr, out int startResult))
                {
                    spanStart = startResult;
                }

                if (item.Properties.TryGetValue("GodotSpan.Length", out string spanLengthStr))
                {
                    if (int.TryParse(spanLengthStr, out int lengthResult))
                    {
                        spanLength = lengthResult;
                    }
                }
            }

            var span = spanStart.HasValue && spanLength.HasValue ?
                       new TextSpan(spanStart.Value, spanLength.Value) :
                       item.Span;

            return(Task.FromResult(CompletionChange.Create(new TextChange(span, item.DisplayText))));
        }
Exemplo n.º 8
0
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey = null, CancellationToken cancellationToken = default)
        {
            // IsComplexTextEdit is true when we want to add async to the container.
            if (!item.IsComplexTextEdit)
            {
                return(await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false));
            }

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var declaration = root.FindToken(item.Span.Start).GetAncestor(node => node.IsAsyncSupportingFunctionSyntax());

            if (declaration is null)
            {
                // We already check that in ProvideCompletionsAsync above.
                Debug.Assert(false, "Expected non-null value for declaration.");
                return(await base.GetChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false));
            }

            using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder);

            builder.Add(new TextChange(new TextSpan(GetSpanStart(declaration), 0), "async "));
            builder.Add(new TextChange(item.Span, item.DisplayText));

            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var newText = text.WithChanges(builder);

            return(CompletionChange.Create(CodeAnalysis.Completion.Utilities.Collapse(newText, builder.ToImmutableArray())));
        }
Exemplo n.º 9
0
            private void Add(
                string displayText,
                string suffix,
                string description,
                bool standard,
                bool isDefault
                )
            {
                using var _1 = PooledStringBuilder.GetInstance(out var descriptionBuilder);
                using var _2 = ArrayBuilder <string> .GetInstance(out var examples);

                AddExamples(examples, standard, displayText);

                descriptionBuilder.AppendLine(
                    examples.Count == 1 ? FeaturesResources.Example : FeaturesResources.Examples
                    );
                foreach (var example in examples)
                {
                    descriptionBuilder.AppendLine(example);
                }

                descriptionBuilder.AppendLine();
                descriptionBuilder.Append(description);

                _items.Add(
                    new DateAndTimeItem(
                        displayText,
                        suffix,
                        descriptionBuilder.ToString(),
                        CompletionChange.Create(new TextChange(_replacementSpan, displayText)),
                        isDefault
                        )
                    );
            }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            var change = (await GetTextChangeAsync(document, item, commitKey, cancellationToken).ConfigureAwait(false))
                         ?? new TextChange(item.Span, item.DisplayText);

            return(CompletionChange.Create(change));
        }
Exemplo n.º 11
0
        public override async Task<CompletionChange> GetChangeAsync(Document document, CompletionItem item, char? commitKey = default, CancellationToken cancellationToken = default)
        {
            var newDocument = await DetermineNewDocumentAsync(document, item, cancellationToken).ConfigureAwait(false);
            var newText = await newDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
            var newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            int? newPosition = null;

            // Attempt to find the inserted node and move the caret appropriately
            if (newRoot != null)
            {
                var caretTarget = newRoot.GetAnnotatedNodesAndTokens(_annotation).FirstOrNullable();
                if (caretTarget != null)
                {
                    var targetPosition = GetTargetCaretPosition(caretTarget.Value.AsNode());

                    // Something weird happened and we failed to get a valid position.
                    // Bail on moving the caret.
                    if (targetPosition > 0 && targetPosition <= newText.Length)
                    {
                        newPosition = targetPosition;
                    }
                }
            }

            var changes = await newDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);
            var change = Utilities.Collapse(newText, changes.ToImmutableArray());

            return CompletionChange.Create(change, newPosition, includesCommitCharacter: true);
        }
Exemplo n.º 12
0
        public override Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            var insertText = item.DisplayText;
            var textChange = new TextChange(item.Span, insertText + " KEY");

            return(Task.FromResult(CompletionChange.Create(textChange)));
        }
        public static async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, CancellationToken cancellationToken)
        {
            var insertText = item.DisplayText;

            int?newPosition = null;

            if (item.Properties.TryGetValue(CompletionItemProperties.NewPositionOffset, out string positionOffsetString) &&
                int.TryParse(positionOffsetString, out int positionOffset) &&
                positionOffset != 0)
            {
                int originalNewPosition = item.Span.End + insertText.Length;
                newPosition = originalNewPosition + positionOffset;
            }

            var textChange = new TextChange(item.Span, insertText);

            // Create TextChange with added using
            if (item.Properties.TryGetValue(CompletionItemProperties.NamespaceToImport, out string nsName))
            {
                int position       = item.Span.End;
                var sourceTextTask = document.GetTextAsync(cancellationToken).ConfigureAwait(false);
                var docWithUsing   = await _namespaceResolver.AddNamespaceImportAsync(nsName, document, position, cancellationToken).ConfigureAwait(false);

                var usingChange = await docWithUsing.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);

                var changes    = usingChange.Union(new[] { textChange }).ToList();
                var sourceText = await sourceTextTask;
                sourceText = sourceText.WithChanges(changes);

                textChange = Collapse(sourceText, changes);
            }

            return(CompletionChange.Create(textChange, newPosition));
        }
Exemplo n.º 14
0
        public override Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            // These values have always been added by us.
            var startString  = item.Properties[StartKey];
            var lengthString = item.Properties[LengthKey];
            var newText      = item.Properties[NewTextKey];

            return(Task.FromResult(CompletionChange.Create(new TextChange(new TextSpan(int.Parse(startString), int.Parse(lengthString)), newText))));
        }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            var tree = await document.GetSyntaxTreeAsync();

            var node = GetCurrentLiteral(tree, item.Span.Start);

            // insert the insertion text property
            return(CompletionChange.Create(ImmutableArray <TextChange> .Empty.Add(new TextChange(node.Span, item.Properties[InsertionTextKey]))));
        }
        public override Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            if (item.Properties.TryGetValue("Start", out var start) &&
                int.TryParse(start, out var s) &&
                item.Properties.TryGetValue("Length", out var length) &&
                int.TryParse(length, out var l))
            {
                return(Task.FromResult(CompletionChange.Create(new TextChange(new TextSpan(s, l), item.SortText))));
            }

            return(base.GetChangeAsync(document, item, commitKey, cancellationToken));
        }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            string insertText;

            if (!item.Properties.TryGetValue(CompletionItemProperties.InsertText, out insertText))
            {
                insertText = item.DisplayText;
            }

            // Add using for required symbol.
            // Any better place to put this?
            if (TryGetSymbolMapping(item, out ISymbol symbol) && IsCommitContext())
            {
                if (symbol.IsStaticImportable())
                {
                    if (Options.StaticSuggestionsAsCodeFixes)
                    {
                        var ns = symbol.GetNamespace();

                        var fullname = symbol.ToDisplayString();
                        insertText = fullname               //change insertText so that it includes Class name and import namespace if necessary
                                     .Replace(ns + ".", "") //remove namespace from the fully qualified name
                        ;
                        //remove (list of params)
                        var index_of_parenthesis = insertText.IndexOf('(');
                        if (index_of_parenthesis > -1)
                        {
                            insertText = insertText.Remove(index_of_parenthesis);
                        }

                        //we may still need to import namespace for the class, so we check for that here
                        var syntaxTree = await document.GetSyntaxTreeAsync().ConfigureAwait(false);

                        var importedNamespaces = syntaxTree.GetImportedNamespaces();
                        if (!importedNamespaces.Contains(ns))
                        {
                            _namespaceResolver.AddNamespaceOrStatic(ns, true);
                        }
                    }
                    else
                    {
                        _namespaceResolver.AddNamespaceOrStatic(symbol.ContainingType.ToDisplayString(), false);
                    }
                }
                else
                {
                    _namespaceResolver.AddNamespaceOrStatic(symbol.GetNamespace(), true);
                }
            }

            return(CompletionChange.Create(new TextChange(item.Span, insertText)));
        }
Exemplo n.º 18
0
            public override Task <CompletionChange> GetChangeAsync(
                Document document,
                CodeAnalysis.Completion.CompletionItem item,
                char?commitCharacter = null,
                CancellationToken cancellationToken = default)
            {
                var textChange = new TextChange(span: new TextSpan(start: 77, length: 9), newText: @"public override void M()
    {
        throw new System.NotImplementedException();
    }");

                return(Task.FromResult(CompletionChange.Create(textChange, newPosition: 0)));
            }
        public override async Task <CompletionChange> GetChangeAsync(Document doc, CompletionItem item, char?commitKey = default(char?), CancellationToken cancellationToken = default(CancellationToken))
        {
            (string beforeText, string afterText, string newMethod) = await GetInsertText(item.Properties);

            TextChange change;

            if (newMethod != null)
            {
                change = new TextChange(new TextSpan(item.Span.Start, item.Span.Length), item.Properties [MethodNameKey] + ";");
                var semanticModel = await doc.GetSemanticModelAsync(cancellationToken);

                if (!doc.IsOpen() || await doc.IsForkedDocumentWithSyntaxChangesAsync(cancellationToken))
                {
                    return(CompletionChange.Create(change));
                }

                await Runtime.RunInMainThread(delegate {
                    var document = IdeApp.Workbench.ActiveDocument;
                    var editor   = document.Editor;
                    if (editor.EditMode != EditMode.Edit)
                    {
                        return;
                    }
                    var parsedDocument  = document.ParsedDocument;
                    var declaringType   = semanticModel.GetEnclosingSymbolMD <INamedTypeSymbol> (item.Span.Start, default(CancellationToken));
                    var insertionPoints = InsertionPointService.GetInsertionPoints(
                        document.Editor,
                        semanticModel,
                        declaringType,
                        editor.CaretOffset
                        );
                    var options = new InsertionModeOptions(
                        GettextCatalog.GetString("Create new method"),
                        insertionPoints,
                        point => {
                        if (!point.Success)
                        {
                            return;
                        }
                        point.InsertionPoint.Insert(document.Editor, document, newMethod);
                    }
                        );
                    editor.StartInsertionMode(options);
                });

                return(CompletionChange.Create(change));
            }
            change = new TextChange(new TextSpan(item.Span.Start, item.Span.Length), beforeText + afterText);

            return(CompletionChange.Create(change, item.Span.Start + beforeText.Length));
        }
Exemplo n.º 20
0
        public override Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            // These values have always been added by us.
            var startString  = item.Properties[StartKey];
            var lengthString = item.Properties[LengthKey];
            var newText      = item.Properties[NewTextKey];

            // This value is optionally added in some cases and may not always be there.
            item.Properties.TryGetValue(NewPositionKey, out var newPositionString);

            return(Task.FromResult(CompletionChange.Create(
                                       new TextChange(new TextSpan(int.Parse(startString), int.Parse(lengthString)), newText),
                                       newPositionString == null ? default(int?) : int.Parse(newPositionString))));
        }
Exemplo n.º 21
0
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            // custom completion logic
            var model = await document.GetSemanticModelAsync();

            var tree         = model.SyntaxTree;
            var root         = tree.GetRoot();
            var memberAccess = tree.GetRoot().GetCurrentMemberAccess(item.Span.Start);

            if (!item.Properties.TryGetValue(CurrentSnipperProperty, out var currentSnippet))
            {
                return(CompletionChange.Create(ImmutableArray <TextChange> .Empty));
            }

            if (memberAccess != null)
            {
                var      snip         = snippets.FirstOrDefault(s => s.GetType().ToString() == currentSnippet);
                var      newRoot      = snip.ChangeTree(document, model, memberAccess, commitKey);
                string   expectedText = null;
                TextSpan nodeSpan     = default(TextSpan);
                if (snip is IWorkspaceUpdatingSnippet wsnip)
                {
                    ll = async(workspace, solution) => {
                        var doc = solution.GetDocument(document.Id);
                        if ((await doc.GetTextAsync()).ToString() == expectedText)
                        {
                            wsnip.Update(doc, (await doc.GetSyntaxRootAsync()).FindNode(nodeSpan) as ExpressionSyntax, commitKey);
                        }
                    };
                }

                //if (newRoot == null) newRoot = root.ReplaceNode(memberAccess, memberAccess.Expression);
                if (newRoot == null)
                {
                    newRoot = root.ReplaceNode(memberAccess, memberAccess.WithName(SyntaxFactory.IdentifierName(item.DisplayText)));
                }
                // format tree
                var newTree = tree.WithRootAndOptions(Formatter.Format(newRoot, Formatter.Annotation, document.Project.Solution.Workspace), tree.Options);
                // return changes done in the new tree
                var changes = newTree.GetChanges(tree).Select(c => TrimWhitespaceChnage(c, tree.GetText())).Select(c => MoveToNode(c, tree.GetText(), memberAccess.Expression.Span)).ToArray();
                ImmutableArray <TextChange> finalChanges = ImmutableArray.Create(MergeChanges(changes, tree.GetText()));
                expectedText = tree.GetText().WithChanges(finalChanges).ToString();
                nodeSpan     = MoveSpan(memberAccess.Expression.Span, finalChanges);
                return(CompletionChange.Create(finalChanges, includesCommitCharacter: false));
            }
            return(await base.GetChangeAsync(document, item, commitKey, cancellationToken));
        }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey = default(char?), CancellationToken cancellationToken = default(CancellationToken))
        {
            var projectIdGuid = item.Properties[ProjectGuidKey];
            var projectId     = ProjectId.CreateFromSerialized(new System.Guid(projectIdGuid));
            var project       = document.Project.Solution.GetProject(projectId);
            var assemblyName  = item.DisplayText;
            var publicKey     = await GetPublicKeyOfProjectAsync(project, cancellationToken).ConfigureAwait(false);

            if (!string.IsNullOrEmpty(publicKey))
            {
                assemblyName += $", PublicKey={ publicKey }";
            }

            var textChange = new TextChange(item.Span, assemblyName);

            return(CompletionChange.Create(textChange));
        }
Exemplo n.º 23
0
        // Точка расширения для замены подставляемого при completion текста
        public override Task <CompletionChange> GetChangeAsync(
            Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            // Если это свойство DTO-класса, то экранируем его квадратными скобками (escaping)
            if (item.Tags.Contains(WellKnownTags.Property))
            {
                string[] splitted = item.DisplayText.Split('.');
                string   newText  = splitted.Length > 1
                                        ? $"[{splitted[0]}].[{splitted[1]}]"
                                        : $"[{splitted[0]}]";

                return(Task.FromResult(
                           CompletionChange.Create(new TextChange(item.Span, newText))));
            }

            return(base.GetChangeAsync(document, item, commitKey, cancellationToken));
        }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey = null, CancellationToken cancellationToken = default)
        {
            // This retrieves the document without the text used to invoke completion
            // as well as the new cursor position after that has been removed.
            var(strippedDocument, position) = await GetDocumentWithoutInvokingTextAsync(document, SnippetCompletionItem.GetInvocationPosition(item), cancellationToken).ConfigureAwait(false);

            var service           = strippedDocument.GetRequiredLanguageService <ISnippetService>();
            var snippetIdentifier = SnippetCompletionItem.GetSnippetIdentifier(item);
            var snippetProvider   = service.GetSnippetProvider(snippetIdentifier);

            // This retrieves the generated Snippet
            var snippet = await snippetProvider.GetSnippetAsync(strippedDocument, position, cancellationToken).ConfigureAwait(false);

            var strippedText = await strippedDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);

            // This introduces the text changes of the snippet into the document with the completion invoking text
            var allChangesText = strippedText.WithChanges(snippet.TextChanges);

            // This retrieves ALL text changes from the original document which includes the TextChanges from the snippet
            // as well as the clean up.
            var allChangesDocument = document.WithText(allChangesText);
            var allTextChanges     = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);

            var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable());

            // Converts the snippet to an LSP formatted snippet string.
            var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change, item.Span.Start, cancellationToken).ConfigureAwait(false);

            // If the TextChanges retrieved starts after the trigger point of the CompletionItem,
            // then we need to move the bounds backwards and encapsulate the trigger point.
            if (change.Span.Start > item.Span.Start)
            {
                var textSpan    = TextSpan.FromBounds(item.Span.Start, change.Span.End);
                var snippetText = change.NewText;
                Contract.ThrowIfNull(snippetText);
                change = new TextChange(textSpan, snippetText);
            }

            var props = ImmutableDictionary <string, string> .Empty
                        .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet);

            return(CompletionChange.Create(change, allTextChanges.AsImmutable(), properties: props, snippet.CursorPosition, includesCommitCharacter: true));
        }
        public override Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item, char?commitKey, CancellationToken cancellationToken)
        {
            string insertText;

            if (!item.Properties.TryGetValue(CompletionItemProperties.InsertText, out insertText))
            {
                insertText = item.DisplayText;
            }
            var change = Task.FromResult(CompletionChange.Create(new TextChange(item.Span, insertText)));

            // Add using for required symbol.
            // Any better place to put this?
            if (TryGetSymbolMapping(item, out ISymbol symbol) && IsCommitContext())
            {
                _namespaceResolver.AddNamespace(symbol.GetNamespace());
            }

            return(change);
        }
        public override async Task <CompletionChange> GetChangeAsync(Document doc, CompletionItem item, char?commitKey = default(char?), CancellationToken cancellationToken = default(CancellationToken))
        {
            TextChange change;

            if (item.Properties.ContainsKey("NewMethod"))
            {
                change = new TextChange(new TextSpan(item.Span.Start, item.Span.Length), item.Properties ["MethodName"] + ";");
                var document       = IdeApp.Workbench.ActiveDocument;
                var editor         = document.Editor;
                var parsedDocument = document.ParsedDocument;
                var semanticModel  = await doc.GetSemanticModelAsync(cancellationToken);

                var declaringType   = semanticModel.GetEnclosingSymbolMD <INamedTypeSymbol> (item.Span.Start, default(CancellationToken));
                var insertionPoints = InsertionPointService.GetInsertionPoints(
                    document.Editor,
                    parsedDocument,
                    declaringType,
                    editor.CaretOffset
                    );
                var options = new InsertionModeOptions(
                    GettextCatalog.GetString("Create new method"),
                    insertionPoints,
                    point => {
                    if (!point.Success)
                    {
                        return;
                    }
                    point.InsertionPoint.Insert(document.Editor, document, item.Properties ["NewMethod"]);
                }
                    );

                editor.StartInsertionMode(options);

                return(CompletionChange.Create(change));
            }
            var beforeText = item.Properties ["InsertBefore"];
            var afterText  = item.Properties ["InsertAfter"];

            change = new TextChange(new TextSpan(item.Span.Start, item.Span.Length), beforeText + afterText);

            return(CompletionChange.Create(change, item.Span.Start + beforeText.Length));
        }
        public override async Task <CompletionChange> GetChangeAsync(Document document, CompletionItem item,
                                                                     char?commitKey, CancellationToken cancellationToken)
        {
            if (await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) is CompilationUnitSyntax
                rootNode)
            {
                if (rootNode.Usings.All(u => u.Name.GetText().ToString() != $"System.Reactive.Linq"))
                {
                    rootNode = rootNode.InsertNodesAfter(rootNode.Usings.Last(),
                                                         new[]
                    {
                        SyntaxFactory.UsingDirective(SyntaxFactory.ParseName($"System.Reactive.Linq"))
                        .NormalizeWhitespace().WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed)
                    });
                }

                if (rootNode.Usings.All(u => u.Name.GetText().ToString() != "RxMethodGenerator"))
                {
                    rootNode = rootNode.InsertNodesAfter(rootNode.Usings.Last(),
                                                         new[]
                    {
                        SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("RxMethodGenerator"))
                        .NormalizeWhitespace().WithTrailingTrivia(SyntaxFactory.CarriageReturnLineFeed)
                    });
                }

                Document?newDocument = document.WithSyntaxRoot(rootNode);
                document.Project.Solution.Workspace.TryApplyChanges(newDocument.Project.Solution);
            }

            string   newText = $".{item.DisplayText}()";
            TextSpan newSpan = new TextSpan(item.Span.Start - 1, 1);

            TextChange textChange = new TextChange(newSpan, newText);

            return(await Task.FromResult(CompletionChange.Create(textChange)));
        }
Exemplo n.º 28
0
            private void Add(string displayText, string suffix, string description, bool standard)
            {
                using var _ = PooledStringBuilder.GetInstance(out var descriptionBuilder);

                // Single letter custom strings need a %, or else they're interpreted as a format
                // standard format string (and will throw a format exception).
                var formatString = !standard && displayText.Length == 1
                    ? "%" + displayText
                    : displayText;

                descriptionBuilder.AppendLine(@$ "{s_primaryCulture.Name}: {s_exampleDateTime.ToString(formatString)}");
                if (s_secondaryCulture != null)
                {
                    descriptionBuilder.AppendLine(@$ "{s_secondaryCulture.Name}: {s_exampleDateTime.ToString(formatString)}");
                }

                descriptionBuilder.AppendLine();
                descriptionBuilder.Append(description);

                _items.Add(new DateAndTimeItem(
                               displayText, suffix, descriptionBuilder.ToString(),
                               CompletionChange.Create(
                                   new TextChange(_replacementSpan, displayText))));
            }
        public override async Task <CompletionChange> GetChangeAsync(
            Document document, CompletionItem completionItem, char?commitKey, CancellationToken cancellationToken)
        {
            var containingNamespace          = ImportCompletionItem.GetContainingNamespace(completionItem);
            var provideParenthesisCompletion = await ShouldProvideParenthesisCompletionAsync(
                document,
                completionItem,
                commitKey,
                cancellationToken).ConfigureAwait(false);

            var insertText = completionItem.DisplayText;

            if (provideParenthesisCompletion)
            {
                insertText += "()";
                CompletionProvidersLogger.LogCustomizedCommitToAddParenthesis(commitKey);
            }

            if (await ShouldCompleteWithFullyQualifyTypeName().ConfigureAwait(false))
            {
                var completionText = $"{containingNamespace}.{insertText}";
                return(CompletionChange.Create(new TextChange(completionItem.Span, completionText)));
            }

            // Find context node so we can use it to decide where to insert using/imports.
            var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);

            var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false);

            var addImportContextNode = root.FindToken(completionItem.Span.Start, findInsideTrivia: true).Parent;

            // Add required using/imports directive.
            var addImportService = document.GetRequiredLanguageService <IAddImportsService>();
            var generator        = document.GetRequiredLanguageService <SyntaxGenerator>();

            var addImportsOptions = await AddImportPlacementOptions.FromDocumentAsync(document, cancellationToken).ConfigureAwait(false);

            var formattingOptions = await SyntaxFormattingOptions.FromDocumentAsync(document, cancellationToken).ConfigureAwait(false);

            var importNode = CreateImport(document, containingNamespace);

            var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);

            var rootWithImport     = addImportService.AddImport(compilation, root, addImportContextNode !, importNode, generator, addImportsOptions, cancellationToken);
            var documentWithImport = document.WithSyntaxRoot(rootWithImport);
            // This only formats the annotated import we just added, not the entire document.
            var formattedDocumentWithImport = await Formatter.FormatAsync(documentWithImport, Formatter.Annotation, formattingOptions, cancellationToken).ConfigureAwait(false);

            using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder);

            // Get text change for add import
            var importChanges = await formattedDocumentWithImport.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false);

            builder.AddRange(importChanges);

            // Create text change for complete type name.
            //
            // Note: Don't try to obtain TextChange for completed type name by replacing the text directly,
            //       then use Document.GetTextChangesAsync on document created from the changed text. This is
            //       because it will do a diff and return TextChanges with minimum span instead of actual
            //       replacement span.
            //
            //       For example: If I'm typing "asd", the completion provider could be triggered after "a"
            //       is typed. Then if I selected type "AsnEncodedData" to commit, by using the approach described
            //       above, we will get a TextChange of "AsnEncodedDat" with 0 length span, instead of a change of
            //       the full display text with a span of length 1. This will later mess up span-tracking and end up
            //       with "AsnEncodedDatasd" in the code.
            builder.Add(new TextChange(completionItem.Span, insertText));

            // Then get the combined change
            var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var newText = text.WithChanges(builder);

            var changes = builder.ToImmutable();
            var change  = Utilities.Collapse(newText, changes);

            return(CompletionChange.Create(change, changes));

            async Task <bool> ShouldCompleteWithFullyQualifyTypeName()
            {
                if (!IsAddingImportsSupported(document))
                {
                    return(true);
                }

                // We might need to qualify unimported types to use them in an import directive, because they only affect members of the containing
                // import container (e.g. namespace/class/etc. declarations).
                //
                // For example, `List` and `StringBuilder` both need to be fully qualified below:
                //
                //      using CollectionOfStringBuilders = System.Collections.Generic.List<System.Text.StringBuilder>;
                //
                // However, if we are typing in an C# using directive that is inside a nested import container (i.e. inside a namespace declaration block),
                // then we can add an using in the outer import container instead (this is not allowed in VB).
                //
                // For example:
                //
                //      using System.Collections.Generic;
                //      using System.Text;
                //
                //      namespace Foo
                //      {
                //          using CollectionOfStringBuilders = List<StringBuilder>;
                //      }
                //
                // Here we will always choose to qualify the unimported type, just to be consistent and keeps things simple.
                return(await IsInImportsDirectiveAsync(document, completionItem.Span.Start, cancellationToken).ConfigureAwait(false));
            }
        }
        private static async Task <CompletionChange> GetConversionChangeAsync(
            Document document, CompletionItem item, CancellationToken cancellationToken)
        {
            var position = SymbolCompletionItem.GetContextPosition(item);
            var text     = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var(dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken);

            var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken
                ? dotToken.GetPreviousToken()
                : (SyntaxToken?)null;

            var expression = (ExpressionSyntax)dotToken.GetRequiredParent();

            expression = expression.GetRootConditionalAccessExpression() ?? expression;

            using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder);

            // First, add the cast prior to the expression.
            var castText = $"(({item.DisplayText})";

            builder.Add(new TextChange(new TextSpan(expression.SpanStart, 0), castText));

            // The expression went up to either a `.`, `..`, `?.` or `?..`
            //
            // In the case of `expr.` produce `((T)expr)$$`
            //
            // In the case of `expr..` produce ((T)expr)$$.
            //
            // In the case of `expr?.` produce `((T)expr)?$$`
            if (questionToken == null)
            {
                // Always eat the first dot in `.` or `..` and replace that with the paren.
                builder.Add(new TextChange(new TextSpan(dotToken.SpanStart, 1), ")"));
            }
            else
            {
                // Place a paren before the question.
                builder.Add(new TextChange(new TextSpan(questionToken.Value.SpanStart, 0), ")"));
                // then remove the first dot that comes after.
                builder.Add(new TextChange(new TextSpan(dotToken.SpanStart, 1), ""));
            }

            // If the user partially wrote out the conversion type, delete what they've written.
            var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true);

            if (CSharpSyntaxFacts.Instance.IsWord(tokenOnLeft))
            {
                builder.Add(new TextChange(tokenOnLeft.Span, ""));
            }

            var newText    = text.WithChanges(builder);
            var allChanges = builder.ToImmutable();

            // Collapse all text changes down to a single change (for clients that only care about that), but also keep
            // all the individual changes around for clients that prefer the fine-grained information.
            return(CompletionChange.Create(
                       CodeAnalysis.Completion.Utilities.Collapse(newText, allChanges),
                       allChanges));
        }